STM32F401CCU6外部中断字节拆分发送数据异常及bitCounter重置疑问
STM32外部中断引脚输出异常问题分析
问题背景
使用42MHz的STM32F401CCU6芯片,PB0引脚连接50kHz外部中断源,PB10引脚需要在每次下降沿事件时切换状态。初始代码运行时输出值持续异常,但改用二进制数组存储数据后恢复正常,修改代码后虽正常运行,但对其中一处逻辑存在疑问。
初始代码异常原因
先看初始异常代码:
uint8_t dataSND[4] = {0xA0, 0xB0, 0xC0, 0xD0};//data uint8_t mode = 0; void sendIO_Data(uint8_t state) { if (state == 0) GPIOB->BSRR = GPIO_BSRR_BR_10;//set low //HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); else GPIOB->BSRR = GPIO_BSRR_BS_10;//set high //HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); } void setIO_In() { GPIOB->MODER &= ~(GPIO_MODER_MODE10);//I/O to input } void setIO_Out() { GPIOB->MODER |= GPIO_MODER_MODE10_0;//I/O to output } void sendDATA()//send bit 0-7 of "dataSND" values 0-3 { sendIO_Data((dataSND[byteAddress] >> bitCntr) & 1); if (byteAddress == 3 && bitCntr == 7) { byteAddress = 0; bitCntr = 0; mode = 0; setIO_In(); return; } bitCntr++; if (bitCntr == 8) { bitCntr = 0; byteAddress++; } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_0) { uint8_t clock_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0); //GPIO_PIN_SET - rising, GPIO_PIN_RESET - falling uint8_t reset_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1); if (reset_state == GPIO_PIN_SET) { setIO_Out(); cntr = 0; mode = 1; } if (clock_state == GPIO_PIN_RESET && mode == 1) { sendDATA(); } } }
异常的核心原因:
- 变量未声明且缺失
volatile修饰:byteAddress、bitCntr、cntr三个关键计数变量均未声明,属于未定义行为,运行时会取随机值;同时这些变量在中断回调和sendDATA函数中交叉使用,必须加volatile修饰,防止编译器将其优化为寄存器变量,导致内存值和寄存器值不一致。 - 计数变量重置逻辑错误:启动传输时(
reset_state == GPIO_PIN_SET),代码重置的是cntr,但sendDATA实际使用的是byteAddress和bitCntr,等于没重置计数,第一次触发时计数变量为随机值,直接导致输出错位。 - 逻辑分支的潜在问题:
sendDATA中满足byteAddress ==3 && bitCntr ==7时直接return,虽逻辑上能重置变量,但如果之前计数变量异常,会导致后续传输逻辑混乱。
二进制数组版本正常的原因
改用二进制数组后代码正常,代码如下:
uint8_t dataSND[32] = {0,0,0,0,0,1,0,1,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1,0,1,1}; void sendDATA() { sendIO_Data(dataSND[cntr]); if (cntr >= 32)//+1 { cntr = 0; mode = 0; setIO_In(); } cntr++; }
原因在于:
- 逻辑简化,出错点减少:直接按顺序读取数组中预存的每一位值,不需要移位、位运算和多变量计数,避免了因多变量同步错误导致的输出异常。
- 计数逻辑直接:仅用
cntr一个变量跟踪当前输出位置,重置逻辑清晰,只要cntr正确初始化并加volatile,就能保证每次传输从起始位开始。
修改后为何要显式设置bitCounter = 0
修改后的代码给所有全局变量加了volatile,并在启动传输时设置bitCounter =0,代码如下:
volatile uint8_t byteAddress = 0; volatile uint8_t bitCounter = 0; volatile uint8_t dataSND[4] = {0xA0, 0xB0, 0xC0, 0xD0};//data volatile uint8_t mode = 0; void sendIO_Data(uint8_t state) { if (state == 0) GPIOB->BSRR = GPIO_BSRR_BR_10;//set low else GPIOB->BSRR = GPIO_BSRR_BS_10;//set high } void setIO_In() { GPIOB->MODER &= ~(GPIO_MODER_MODE10); } void setIO_Out() { GPIOB->MODER |= GPIO_MODER_MODE10_0; } void sendDATA() { sendIO_Data((dataSND[byteAddress] >> bitCounter) & 1); bitCounter++; if (bitCounter > 7) { bitCounter = 0; byteAddress++; } if (byteAddress > 3) { byteAddress = 0; bitCounter = 0; mode = 0; setIO_In(); } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_0) { uint8_t clock_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0); //GPIO_PIN_SET - rising, GPIO_PIN_RESET - falling uint8_t reset_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1); if (reset_state == GPIO_PIN_SET) { setIO_Out(); bitCounter = 0; mode = 1; } if (clock_state == GPIO_PIN_RESET && mode == 1) { sendDATA(); } } }
显式设置bitCounter =0的原因:
- 规避未初始化或异常残留值:系统复位后、异常中断嵌套或其他代码意外修改都可能导致
bitCounter不为0,显式重置能确保每次启动传输时从数据的第0位开始,避免输出错位。 - 保证传输的一致性:无论上一次传输是否正常结束,只要触发启动信号,就强制将计数变量复位,确保每一轮传输的起始状态一致。
- 配合
volatile的可靠性:虽然加了volatile保证变量值实时更新,但显式初始化能消除任何潜在的不确定状态,让代码鲁棒性更强。
内容的提问来源于stack exchange,提问作者Sem
相关产品推荐
相关产品推荐

