STM32 Nucleo-F302R8单总线DS18B20检测GPIO配置问题排查
STM32 单总线驱动DS18B20检测故障排查
问题背景
- 开发平台:Nucleo-F302R8开发板,做STM32底层开发,不依赖第三方功能库实现外设驱动,目标是通过PC0引脚的OneWire单总线检测DS18B20温度传感器:检测到传感器则点亮PB13引脚连接的板载LED,未检测到则熄灭LED。
- 硬件状态:接线、DS18B20传感器已通过Arduino平台实测可正常工作,排除硬件损坏、接线错误问题。
- 实现逻辑:对照DS18B20数据手册的初始化时序编写逻辑:将PC0配置为输出模式拉低总线480us,之后切换为输入模式释放总线,等待60us后读取总线电平,按手册说明读到低电平代表传感器存在,高电平则无传感器。
- 故障现象:代码始终无法正常检测到传感器,排障过程中观测到输出模式下总线连接的LED微亮,GPIO模式切换时LED会出现极短时间的闪烁,初步怀疑故障点在GPIO输入输出配置、或者微秒级延时精度异常。
现有代码
1. GPIO输入输出切换函数
void ONEWIRE_OUTPUT(GPIO_TypeDef* GPIOx,uint32_t GPIO_Pin) { GPIO_InitTypeDef GPIO_InitStructOUT = {0}; // 存储引脚配置参数 GPIO_InitStructOUT.Pin = GPIO_PIN_0; GPIO_InitStructOUT.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructOUT.Speed = GPIO_SPEED_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStructOUT); } void ONEWIRE_INPUT(GPIO_TypeDef* GPIOx,uint32_t GPIO_Pin) { GPIO_InitTypeDef gpio_prms = {0}; gpio_prms.Pin = GPIO_Pin; gpio_prms.Mode = GPIO_MODE_INPUT; gpio_prms.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOx, &gpio_prms); }
2. 基于TIM1的微秒延时函数(暂未通过示波器验证延时精度)
void Delay_us(uint16_t delay) { __HAL_TIM_SET_COUNTER(&htim1,0); // 计数器清零 while (__HAL_TIM_GET_COUNTER(&htim1) < delay ); // 等待计数到目标值 }
3. 主循环逻辑
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* 复位所有外设,初始化Flash接口、Systick */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* 配置系统时钟 */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* 初始化所有配置的外设 */ MX_GPIO_Init(); MX_USART2_UART_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* 无限循环 */ /* USER CODE BEGIN WHILE */ HAL_TIM_Base_Start(&htim1); // 启动定时器1 int timer_val; while (1) { // 配置为输出模式 ONEWIRE_OUTPUT(GPIOC, GPIO_PIN_0); // 拉低总线保持480us HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, 0); Delay_us(480); timer_val = __HAL_TIM_GET_COUNTER(&htim1); uart_buf_len = sprintf(uart_buf, "%u us -- 目标值约480 us\r\n" , timer_val ); HAL_UART_Transmit(&huart2, (uint8_t *)uart_buf, uart_buf_len, 100); // 释放总线,切换为输入模式 ONEWIRE_INPUT(GPIOC, GPIO_PIN_0); // 等待60us Delay_us(60); // 读取电平,低电平代表传感器存在,高电平代表无传感器 timer_val = __HAL_TIM_GET_COUNTER(&htim1); uart_buf_len = sprintf(uart_buf, "%u us -- 目标值约60 us\r\n" , timer_val ); HAL_UART_Transmit(&huart2, (uint8_t *)uart_buf, uart_buf_len, 100); // 更新LED状态 if ( HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_0) ) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); }else { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); } HAL_Delay(1000); // 间隔1秒检测一次 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
故障排查思路
- 先查GPIO配置逻辑错误:
ONEWIRE_OUTPUT函数没有使用传入的形参GPIOx和GPIO_Pin,而是硬编码写死了GPIOC和GPIO_PIN_0,当前调用虽然参数匹配不会触发问题,但这种写法很容易在后续修改时出现引脚配置错位的问题,建议统一改成使用传入参数配置。另外当前输出模式配置的速度等级是GPIO_SPEED_LOW,压摆率不足会导致电平上升/下降沿过缓,无法满足OneWire微秒级的时序要求,建议改成GPIO_SPEED_FREQ_HIGH。 - 校准延时精度:你已经在代码里加了串口打印定时器计数值的逻辑,先看串口输出的两个计数值是否和目标值匹配。如果TIM1的预分频系数配置错误,1个计数单位不是1us,实际延时长度会完全偏离DS18B20的时序要求。另外注意
HAL_GPIO_Init函数执行本身会消耗数微秒的时间,会吃掉一部分你预留的等待窗口,计算延时时要把这部分时间算进去。 - 调整存在脉冲检测逻辑:按照DS18B20手册,传感器收到主机的复位脉冲后,会在总线释放后的1560us区间内拉低总线60240us作为存在应答。你现在固定等待60us读一次电平的逻辑容错性很差,很容易错过低电平窗口:比如总线释放慢、延时有偏差,60us时传感器已经释放总线回到高电平,就会误判为无传感器。建议改成释放总线后等待15us,之后连续轮询总线电平100us,只要在这个窗口内读到低电平就判定传感器存在。
- 排查总线负载问题:你提到输出模式下总线上接的LED微亮,先确认这个LED是不是直接并联在OneWire总线上。单总线本身驱动能力很弱,额外挂LED这类负载会拉低总线高电平值,导致传感器无法识别总线状态,建议检测时暂时去掉总线上多余的负载,只保留DS18B20和4.7k上拉电阻。
STM32底层编程学习资源推荐
- 对应芯片的官方参考手册:STM32F302系列的参考手册是最权威的开发资料,所有寄存器定义、外设工作逻辑、时序要求、电气参数都以手册描述为准,写底层驱动时要随时对照。
- 官方HAL/LL库源码:不需要完全依赖库函数实现逻辑,但可以参考库函数里的外设初始化流程,对照参考手册看每一行操作对应哪个寄存器的哪一位,能快速掌握外设的配置方法,比零散看教程效率高很多。
- ARM Cortex-M4内核编程指南:内核层面的中断机制、内存映射、位带操作、指令周期等内容是所有Cortex-M芯片通用的底层知识,掌握之后不管用哪个型号的STM32都能快速上手。
内容的提问来源于stack exchange,提问作者Spider999
相关产品推荐
相关产品推荐

