Hello Raspberry Pi Community,
I am working on transitioning my scientific instrument project from a standard Raspberry Pi 4B to a Compute Module 4 (CM4). In this project, I've traditionally used the BOARD numbering system for GPIO, but I've switched to the BCM system for compatibility with the CM4.
Overall, the transition has been largely successful, especially concerning SPI communication parts, which are working flawlessly. However, I am facing a persistent issue with GPIO4, which acts similarly to a switch for a temperature controller in my setup.
Here are the details:
On the Raspberry Pi 4B, with the BOARD numbering, the entire setup, including GPIO4, worked without significant issues, although there were occasional problems that I couldn't fully diagnose at the time.
Since moving to the CM4 and adopting the BCM numbering system, all other GPIO functionalities have transitioned smoothly except for GPIO4.
When I attempt to initialize and set GPIO4 as OUTPUT and then HIGH, I encounter an error stating, "The GPIO channel has not been set up as an OUTPUT."
Intriguingly, in the past development phase, there were sporadic issues where the switch (connected to GPIO4) would get inadvertently triggered, especially when switching between different Kivy GUI screens, leading to the unintended shutdown of the temperature controller.
Additional steps I've taken:
Isolation tests for GPIO4 to ensure no script or hardware-related issues.
Ensuring all physical connections are intact and correctly set up.
Updating all software components and dependencies to their latest versions.
Testing and confirming the functionality of other GPIO pins, which have no issues.
Double-checking the transition from BOARD to BCM numbering to eliminate any misconfiguration.
This specific issue with GPIO4 is perplexing, especially since it was somewhat problematic even before the transition, and now seems to exacerbate with the CM4 setup. Furthermore, the fact that SPI communications are working perfectly indicates that the problem is isolated to this particular GPIO.
Has anyone experienced similar issues, particularly with GPIO misbehaviors during GUI interactions or specifically with CM4? Any insights, troubleshooting tips, or solutions would be greatly appreciated. I am wondering if this is a known issue or if there are specific considerations for GPIO on the CM4 that I might be missing.
Thank you all for your time and assistance!
I am working on transitioning my scientific instrument project from a standard Raspberry Pi 4B to a Compute Module 4 (CM4). In this project, I've traditionally used the BOARD numbering system for GPIO, but I've switched to the BCM system for compatibility with the CM4.
Overall, the transition has been largely successful, especially concerning SPI communication parts, which are working flawlessly. However, I am facing a persistent issue with GPIO4, which acts similarly to a switch for a temperature controller in my setup.
Here are the details:
On the Raspberry Pi 4B, with the BOARD numbering, the entire setup, including GPIO4, worked without significant issues, although there were occasional problems that I couldn't fully diagnose at the time.
Since moving to the CM4 and adopting the BCM numbering system, all other GPIO functionalities have transitioned smoothly except for GPIO4.
Code:
import RPi.GPIO as GPIOimport spidevimport timeimport csvfrom scipy.stats import zscorefrom kivy.app import Appfrom kivy.uix.button import Buttonfrom kivy.uix.label import Labelfrom kivy.uix.slider import Sliderfrom kivy.uix.boxlayout import BoxLayoutfrom kivy.clock import Clockfrom kivy.uix.screenmanager import ScreenManager, Screen, SlideTransitionfrom kivy.uix.scrollview import ScrollViewfrom kivy.core.window import Windowfrom kivy.uix.gridlayout import GridLayoutfrom mcp_test_v6 import TemperatureSensor # 确保这个模块正确导入from TEC_test_v8 import TECController # 确保这个模块正确导入# Initialize SPI and GPIORELAY_PIN = 13CS_PIN = 8SPI = spidev.SpiDev(0, 0)SPI.max_speed_hz = 20000000 SPI.mode = 0b01def setup_gpio(): GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(4, GPIO.OUT) # MAX1978相关 GPIO.output(4, GPIO.HIGH) # 将MAX1978设置为ON GPIO.setup(RELAY_PIN, GPIO.OUT) # RELAY_PIN GPIO.output(RELAY_PIN, False) # 设置RELAY_PIN的初始状态为OFF GPIO.setup(CS_PIN, GPIO.OUT) # AD7924的CS_PIN GPIO.setup(17, GPIO.OUT) # TECController的CS_PIN GPIO.setup(16, GPIO.OUT) # TemperatureSensor的CS_PINdef export_to_csv(data_records, filename='data_records.csv'): with open(filename, 'w', newline='') as csvfile: # 扩展列名以容纳所有的平均值 fieldnames = ['elapsed', 'avg_value1', 'avg_value2', 'avg_value3'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() # 写入列名 for record in data_records: # 扩展行数据以容纳分离的值 row_data = {'elapsed': record['elapsed'], 'avg_value1': record['avg_values'][0], 'avg_value2': record['avg_values'][1], 'avg_value3': record['avg_values'][2]} writer.writerow(row_data)class AD7924: def __init__(self): self.cs_pin = CS_PIN # GPIO.setup(self.cs_pin, GPIO.OUT) def read_adc(self, channel): # 断言:确保传入的通道号是0、1或2中的一个 assert channel in [0, 1, 2], "Invalid Channel" # 为每个通道定义特定的命令字节,这些命令用于与ADC通信 channels_command_bytes = [[0x87, 0x30], [0x8b, 0x30], [0x8f, 0x30]] # 根据传入的通道号选择相应的命令字节 command_bytes = channels_command_bytes[channel] # 将CS(片选)引脚设置为低电平,以开始与ADC的通信 GPIO.output(self.cs_pin, GPIO.LOW) # 通过SPI发送特定通道的命令字节 SPI.writebytes(command_bytes) # 从SPI接口读取4字节的数据 data = SPI.readbytes(4) # 将CS(片选)引脚设置为高电平,结束与ADC的通信 GPIO.output(self.cs_pin, GPIO.HIGH) # 将读取的第一和第二字节转换为二进制字符串,并填充到8位 out = bin(data[0])[2:].zfill(8) + bin(data[1])[2:].zfill(8) # 返回从第五位开始到最后的二进制数所对应的整数值 return int(out[4:], 2)class MainScreen(Screen): def __init__(self, tec_controller, **kwargs): super(MainScreen, self).__init__(**kwargs) self.layout = BoxLayout(orientation='vertical', padding=[20, 0, 0, 0]) self.tec_controller = tec_controller # 创建按钮的网格布局 self.button_grid = GridLayout(rows=1, cols=4, size_hint=(1, None), height=50) # Toggle Laser Button self.laser_button = Button(text="Toggle Laser") self.laser_button.bind(on_press=self.toggle_laser) self.button_grid.add_widget(self.laser_button) # Read ADC Button self.adc_button = Button(text="Read All ADC Channels") self.adc_button.bind(on_press=self.read_adc) self.button_grid.add_widget(self.adc_button) # 将按钮的网格布局添加到主布局中 self.layout.add_widget(self.button_grid) # Interval Slider and Label self.interval_slider = Slider(min=1, max=100, value=30, step=1) self.interval_label = Label(text=f"Set Interval (sec): {int(self.interval_slider.value)}") self.interval_slider.bind(value=self.on_interval_value_change) self.layout.add_widget(self.interval_label) self.layout.add_widget(self.interval_slider) # Cycle Slider and Label self.cycle_slider = Slider(min=1, max=100, value=10) self.cycle_label = Label(text=f"Set Cycles: {int(self.cycle_slider.value)}") self.cycle_slider.bind(value=self.on_cycle_value_change) self.layout.add_widget(self.cycle_label) self.layout.add_widget(self.cycle_slider) # Start Recording Button self.start_recording_button = Button(text="Start Recording") self.start_recording_button.bind(on_press=self.start_recording) self.layout.add_widget(self.start_recording_button) # Status Label self.status_label = Label(text='') self.layout.add_widget(self.status_label) # Switch to Data Screen Button self.switch_button = Button(text="Go to Data Screen") self.switch_button.bind(on_press=self.go_to_data_screen) self.button_grid.add_widget(self.switch_button) # Go to Temp Control Screen Button self.temp_control_button = Button(text="Go to Temp Control") self.temp_control_button.bind(on_press=self.go_to_temp_control) self.button_grid.add_widget(self.temp_control_button) # 创建紧急停止MAX1978按钮 self.emergency_stop_button = Button(text="Emergency STOP MAX1978") self.emergency_stop_button.bind(on_press=self.emergency_toggle_max1978) self.layout.add_widget(self.emergency_stop_button) # 创建MAX1978状态标签 self.max1978_status_label = Label(text=f"MAX1978 status: {self.tec_controller.get_max1978_state()}") self.layout.add_widget(self.max1978_status_label) # AD7924 instance for reading ADC self.num_samples = 10000 # 或任何您需要的数量!!!!!!!!!!!! self.adc = AD7924() self.adc_labels = [Label(text=f"Channel {i}: ADC Value: ") for i in range(3)] self.data_records = [] # 创建一个网格布局来组合ADC标签和温度标签 self.adc_temp_grid = GridLayout(cols=6, padding=[20, 0, 0, 0]) # 为三个 ADC 标签各留出两列空间 # 初始化温度标签 self.set_temp_label = Label(text="Set Temp: --°C") self.current_temp_label = Label(text="Current Temp: --°C") # 添加ADC标签和温度标签到网格布局中 for i in range(3): self.adc_temp_grid.add_widget(self.adc_labels[i]) # 添加ADC标签 # 添加额外的空标签以保持布局对齐 self.adc_temp_grid.add_widget(Label(text="")) # 在第一个和第二个ADC标签的下一列添加温度标签 if i == 0: self.adc_temp_grid.add_widget(self.set_temp_label) elif i == 1: self.adc_temp_grid.add_widget(self.current_temp_label) # 在第三列添加空标签以保持布局对齐 if i < 2: self.adc_temp_grid.add_widget(Label(text="")) # 将网格布局添加到主布局中 self.layout.add_widget(self.adc_temp_grid) # 将主布局添加到当前的屏幕中 self.add_widget(self.layout) # def sync_max1978_state(self): # 此方法将根据需要同步MAX1978的状态 # if self.manager.current == 'temp_control': # 如果我们当前在温度控制屏幕 # if self.tec_controller.get_max1978_state() == 'OFF': # 如果MAX1978应该是开启的,确保它是开启的 # self.tec_controller.toggle_max1978() # elif self.manager.current == 'main': # 如果我们回到主屏幕,确保MAX1978的状态与之前保持一致 # pass # 这里可能不需要做任何事,因为我们不期望在主屏幕改变MAX1978的状态 def emergency_toggle_max1978(self, instance): # 切换MAX1978的状态 self.tec_controller.toggle_max1978() new_state = self.tec_controller.get_max1978_state() self.max1978_status_label.text = f"MAX1978 status: {new_state}" print(f"MAX1978 has been switched to {new_state} state under emergency.") def update_temperature_labels(self, set_temp, current_temp): self.set_temp_label.text = f"Set Temp: {set_temp}°C" self.current_temp_label.text = f"Current Temp: {current_temp}°C" def toggle_laser(self, instance): # GPIO.setmode(GPIO.BOARD) # 重新设置GPIO编号模式 # GPIO.setup(RELAY_PIN, GPIO.OUT) # 确保设置了正确的引脚模式 GPIO.output(RELAY_PIN, not GPIO.input(RELAY_PIN)) state = "ON" if GPIO.input(RELAY_PIN) else "OFF" print(f"Laser is {state}") def read_adc(self, instance): for i, label in enumerate(self.adc_labels): adc_value = self.adc.read_adc(i) label.text = f"Channel {i}: ADC Value {adc_value}" def start_recording(self, instance): # 初始化数据结构和状态变量 self.status_label.text = 'system start...' self.total_cycles = int(self.cycle_slider.value) self.current_cycle = 0 self.data_records = [] # 存储所有周期的数据 self.moving_average = [0, 0, 0] # 用于计算移动平均的列表 self.moving_sum = [0, 0, 0] # 移动平均的累加和 self.moving_count = 0 # 移动平均的计数器 self.window_size = 10 # 滑动窗口的大小 Clock.schedule_interval(self.record_data, float(self.interval_slider.value)) def record_data(self, dt): current_time = time.time() if self.current_cycle == 0: self.start_time = current_time # 记录开始时间 elapsed_time = current_time - self.start_time # 开启激光器并等待200毫秒 GPIO.output(RELAY_PIN, True) time.sleep(0.2) adc_values_all_samples = [[] for _ in range(3)] # 用于存储每个通道的所有样本的列表 print(f"Cycle {self.current_cycle + 1}/{self.total_cycles}:") # 打印当前周期编号 for sample_num in range(self.num_samples): adc_values = [self.adc.read_adc(i) for i in range(3)] for i, val in enumerate(adc_values): adc_values_all_samples[i].append(val) # print(f"Sample {sample_num + 1}: {adc_values}") # 关闭激光器 GPIO.output(RELAY_PIN, False) # 计算每个通道的z-score z_scores = [zscore(samples) if len(samples) > 1 else [0] * len(samples) for samples in adc_values_all_samples] # 过滤掉异常值 filtered_values = [] filtered_avg_values = [] # 存储每个通道过滤后的平均值 for channel_idx in range(3): filtered_channel_values = [val for val, z in zip(adc_values_all_samples[channel_idx], z_scores[channel_idx]) if abs(z) < 3] filtered_values.append(filtered_channel_values) # 计算过滤后的平均值,并存储在filtered_avg_values列表中 avg_val = sum(filtered_channel_values) / len(filtered_channel_values) if filtered_channel_values else 0 filtered_avg_values.append(avg_val) # 更新显示和数据记录 self.adc_labels[channel_idx].text = f"Channel {channel_idx}: Filtered ADC Value {avg_val:.2f}" if filtered_channel_values else "Channel {channel_idx}: No valid values (all filtered)" print(f"Channel {channel_idx}: Filtered ADC Value {avg_val:.2f}") if filtered_channel_values else print(f"Channel {channel_idx}: No valid values (all filtered)") # 更新状态标签以显示当前周期 self.status_label.text = f"Recording... Cycle {self.current_cycle + 1}/{self.total_cycles}" # 将本次记录添加到记录列表 self.data_records.append({'elapsed': round(elapsed_time, 1), 'avg_values': filtered_avg_values}) # 检查是否达到总周期数 self.current_cycle += 1 if self.current_cycle >= self.total_cycles: Clock.unschedule(self.record_data) self.status_label.text = 'Recording done...' export_to_csv(self.data_records, 'data_records.csv') def on_interval_value_change(self, instance, value): self.interval_label.text = f"Set Interval (sec): {int(value)}" def on_cycle_value_change(self, instance, value): self.cycle_label.text = f"Set Cycles: {int(value)}" def go_to_data_screen(self, instance): self.manager.current = 'data' def go_to_temp_control(self, instance): # self.sync_max1978_state() # Switch to the temperature control screen self.manager.transition.direction = 'left' self.manager.current = 'temp_control' class DataScreen(Screen): def __init__(self, **kwargs): super(DataScreen, self).__init__(**kwargs) # 创建 ScrollView scroll_view = ScrollView(size_hint=(1, 1), do_scroll_x=False) # 原来的布局成为 ScrollView 的子组件 self.layout = BoxLayout(orientation='vertical', size_hint_y=None) # 确保布局的高度足够容纳所有子组件 self.layout.bind(minimum_height=self.layout.setter('height')) self.data_label = Label(size_hint_y=None, height=2000, text='Data will be displayed here', font_size='20sp') self.layout.add_widget(self.data_label) switch_button = Button(size_hint_y=None, height=50, text="Back to Main Screen") switch_button.bind(on_press=self.go_to_main_screen) self.layout.add_widget(switch_button) # 将布局添加到 ScrollView 中 scroll_view.add_widget(self.layout) # 将 ScrollView 添加到屏幕中 self.add_widget(scroll_view) def on_pre_enter(self, *args): self.update_data_display() def update_data_display(self): data_records = self.manager.get_screen('main').data_records data_text = '\n'.join( [f"Time: {record['elapsed']}s, " f"Avg Values: [{', '.join(f'{val:.1f}' for val in record['avg_values'])}]" for record in data_records]) self.data_label.text = data_text def go_to_main_screen(self, instance): self.manager.current = 'main'class TempControlScreen(Screen): def __init__(self, tec_controller, **kwargs): super(TempControlScreen, self).__init__(**kwargs) self.tec_controller = tec_controller layout = BoxLayout(orientation='vertical', padding=10, spacing=10) # 初始化TemperatureSensor和TECController self.sensor = TemperatureSensor() # 确保TemperatureSensor被正确导入 # 添加用于显示和设置温度的控件 self.temperature_label = Label(text='Current Temperature: --°C') layout.add_widget(self.temperature_label) self.slider = Slider(min=40, max=80, value=50, step=1) self.slider_value_label = Label(text=f'Temperature: {self.slider.value}°C') self.slider.bind(value=self.on_slider_value_change) self.set_temp_button = Button(text='Set Temperature') self.set_temp_button.bind(on_press=self.set_temperature) # 添加MAX1978控制按钮 # max1978_state = self.tec_controller.get_max1978_state() # self.max1978_button = Button(text=f'MAX1978 {max1978_state}') # self.max1978_button.bind(on_press=self.toggle_max1978) self.back_button = Button(text='Back to Main Screen') self.back_button.bind(on_press=self.go_to_main_screen) layout.add_widget(self.slider_value_label) layout.add_widget(self.slider) layout.add_widget(self.set_temp_button) # layout.add_widget(self.max1978_button) # 将MAX1978按钮添加到布局中 layout.add_widget(self.back_button) self.add_widget(layout) # 每秒更新温度 Clock.schedule_interval(self.update_temperature, 1) # def sync_max1978_state(self): # 此方法将根据需要同步MAX1978的状态 # if self.manager.current == 'temp_control': # 如果我们当前在温度控制屏幕 # if self.tec_controller.get_max1978_state() == 'OFF': # 如果MAX1978应该是开启的,确保它是开启的 # self.tec_controller.toggle_max1978() # elif self.manager.current == 'main': # 如果我们回到主屏幕,确保MAX1978的状态与之前保持一致 # pass # 这里可能不需要做任何事,因为我们不期望在主屏幕改变MAX1978的状态 def on_slider_value_change(self, instance, value): self.slider_value_label.text = f'Temperature: {int(value)}°C' def set_temperature(self, instance): # print("Setting temperature...") # 添加的打印语句 temperature = int(self.slider.value) self.tec_controller.set_temperature(temperature) dac_value = self.tec_controller.temperature_to_dac_value.get(temperature, 'Unknown') # print(f"Set temperature to {temperature}°C with DAC value {dac_value}") # print(f"[TempControlScreen] Set temperature to {temperature}°C with DAC value {dac_value}") # 获取MainScreen实例 main_screen = self.manager.get_screen('main') # 读取并转换MainScreen当前温度标签的值 current_temp_text = main_screen.current_temp_label.text.split(': ')[1] if current_temp_text != '--°C': current_temp = int(current_temp_text.replace('°C', '')) else: current_temp = 0 # 或者是一个默认的温度值,如0 # 更新MainScreen的温度标签 main_screen.update_temperature_labels(f"{temperature}°C", current_temp) # def toggle_max1978(self, instance): # 使用TECController实例来切换MAX1978状态 # self.tec_controller.toggle_max1978() # 更新按钮文本来反映当前状态 # new_state = self.tec_controller.get_max1978_state() # instance.text = f'MAX1978 {new_state}' # print(f"MAX1978 turned {new_state}") def update_temperature(self, dt): temperature = self.sensor.read_temperature() self.temperature_label.text = f"Current Temperature: {temperature}°C" # 获取MainScreen实例 main_screen = self.manager.get_screen('main') # 读取并转换MainScreen设定温度标签的值 set_temp_text = main_screen.set_temp_label.text.split(': ')[1] if set_temp_text != '--°C': set_temp = int(set_temp_text.replace('°C', '')) else: set_temp = 0 # 或者是一个默认的温度值,如0 # 更新MainScreen的温度标签 main_screen.update_temperature_labels(set_temp, f"{temperature}°C") def go_to_main_screen(self, instance): # 在切换到主屏幕之前同步MAX1978的状态 # self.sync_max1978_state() self.manager.transition.direction = 'right' self.manager.current = 'main' def on_leave(self): # 离开界面时清理资源 # self.tec_controller.cleanup() passclass MyApp(App): def build(self): # 创建 TECController 实例 tec_controller_instance = TECController() # 更明确的命名 sm = ScreenManager(transition=SlideTransition()) main_screen = MainScreen(name='main', tec_controller=tec_controller_instance) # 传递实例 temp_control_screen = TempControlScreen(name='temp_control', tec_controller=tec_controller_instance) # 传递实例 sm.add_widget(main_screen) sm.add_widget(DataScreen(name='data')) sm.add_widget(temp_control_screen) return smdef main_function_v5(): setup_gpio() try: MyApp().run() except Exception as e: print(e) finally: # GPIO.setmode(GPIO.BOARD) # 再次确保设置了正确的GPIO编号模式 GPIO.cleanup()if __name__ == "__main__": main_function_v5()
Intriguingly, in the past development phase, there were sporadic issues where the switch (connected to GPIO4) would get inadvertently triggered, especially when switching between different Kivy GUI screens, leading to the unintended shutdown of the temperature controller.
Additional steps I've taken:
Isolation tests for GPIO4 to ensure no script or hardware-related issues.
Ensuring all physical connections are intact and correctly set up.
Updating all software components and dependencies to their latest versions.
Testing and confirming the functionality of other GPIO pins, which have no issues.
Double-checking the transition from BOARD to BCM numbering to eliminate any misconfiguration.
This specific issue with GPIO4 is perplexing, especially since it was somewhat problematic even before the transition, and now seems to exacerbate with the CM4 setup. Furthermore, the fact that SPI communications are working perfectly indicates that the problem is isolated to this particular GPIO.
Has anyone experienced similar issues, particularly with GPIO misbehaviors during GUI interactions or specifically with CM4? Any insights, troubleshooting tips, or solutions would be greatly appreciated. I am wondering if this is a known issue or if there are specific considerations for GPIO on the CM4 that I might be missing.
Thank you all for your time and assistance!
Statistics: Posted by dgy411852 — Fri Mar 01, 2024 7:30 pm