嵌入式软件代码开发或者代码优化的一些方法(持续更新)
一、消抖这段代码为例消抖前判断一次消抖后判断一次Delay之后记得重新读取数据。uint8_t servo_change HAL_GPIO_ReadPin(GPIOE,trash_servo); uint8_t trash_change HAL_GPIO_ReadPin(GPIOE,monitor1); while (1) { uint8_t new_servo_state HAL_GPIO_ReadPin(GPIOE,trash_servo); uint8_t new_trash_state HAL_GPIO_ReadPin(GPIOE,monitor1); if(trash_change!new_trash_state) { HAL_Delay(10); new_trash_state HAL_GPIO_ReadPin(GPIOE,monitor1); if(trash_change!new_trash_state) { if(new_trash_stateGPIO_PIN_RESET) //monitor1 catch the sign of full { HAL_GPIO_WritePin(GPIOE,LED_Warning,GPIO_PIN_RESET); //light up the warning LED printf(FULL LED_ON\r\n); } else if(new_trash_stateGPIO_PIN_SET) //normal situation { HAL_GPIO_WritePin(GPIOE,LED_Warning,GPIO_PIN_SET); printf(nomal LED_OFF\r\n); } } trash_changenew_trash_state; } if(servo_change!new_servo_state) { HAL_Delay(50); new_servo_state HAL_GPIO_ReadPin(GPIOE,trash_servo); if(servo_change!new_servo_state) // { if(new_servo_stateGPIO_PIN_RESET) { Servo_Set_angle(angle_left); printf(left!\r\n); HAL_Delay(2000); servo_stop(); } else if(new_servo_stateGPIO_PIN_SET) { Servo_Set_angle(angle_right); printf(right!\r\n); HAL_Delay(2000); servo_stop(); } servo_changenew_servo_state;//״̬ } } }二、初级状态机防止连续转换导致崩溃的技巧也是以上面的代码为例合理利用2个变量来体现状态转换的时机。保证代码不会因为频繁转换状态控制器件反复转换状态导致器件崩溃或者发热损坏。上面的代码就利用 servo_change和new_servo_state 这2个变量来读取舵机状态转换的瞬间只读取状态变化的瞬间而不是一味的识别高电平或者低电平来控制舵机有效防止舵机崩溃。三、状态机初级状态机和状态机的共同点都是读取变化的瞬间而不是读取数据前者更适用于状态只有2种的时候后者适合需要转换多种状态的时候一般是利用枚举还有switch结构组成。枚举用于列举各种状态switch用于读取各种状态下应该完成什么事。例如下面的例子定义一个枚举以及一个枚举类型的变量typedef enum { LED_OFF, // 熄灭 LED_ON, // 常亮 LED_BLINK_SLOW, // 慢闪 LED_BLINK_FAST // 快闪 } LED_State; LED_State current_state LED_OFF;结合switch结构实现按一下转换一次状态的功能while (1) { // 按键检测状态变化才切换 if (key_press_flag) { key_press_flag 0; // 状态切换按一下变一个模式 switch (current_state) { case LED_OFF: current_state LED_ON; break; case LED_ON: current_state LED_BLINK_SLOW; break; case LED_BLINK_SLOW: current_state LED_BLINK_FAST; break; case LED_BLINK_FAST: current_state LED_OFF; break; } } // 根据当前状态做动作 switch (current_state) { case LED_OFF: HAL_GPIO_WritePin(GPIOx, GPIO_PIN_0, GPIO_PIN_RESET); break; case LED_ON: HAL_GPIO_WritePin(GPIOx, GPIO_PIN_0, GPIO_PIN_SET); break; case LED_BLINK_SLOW: HAL_GPIO_TogglePin(GPIOx, GPIO_PIN_0); HAL_Delay(500); break; case LED_BLINK_FAST: HAL_GPIO_TogglePin(GPIOx, GPIO_PIN_0); HAL_Delay(100); break; } }四、阅读数据手册的心得2026.7.11大二小学期期间最近是在搞LVGLCANIAP的那个项目虽然自己手搓出来没看教程但是其实中间不少依赖AI最大的问题就是遇到自己不知道怎么解决的问题时不清楚从什么地方下手最近通过AI帮我排查错误时学到一招就是依靠硬件寄存器说话当然排查代码错误首先还是得从正常的思路去排查比如说我做的仪表盘的项目发现的问题是2个CAN收发器不通讯最好最简单的办法就是切半调试缩小问题的范围找到出问题的地方。大家都经常这么调试但是总会遇到一些奇奇怪怪的问题于是就靠数据手册里的状态寄存器来排查问题了。下面是AI告诉我的排查方法我自己复盘了一下当时我遇到的问题是CAN不通后面AI告诉我让我不要猜哪里出问题先测试c8t6有没有问题让我打开回环模式如果CAN的回环模式能通说明问题不在c8t6的软件代码层面如果CAN的回环模式不通说明大概率是c8t6这边出了问题或者是硬件的问题了。然后就是读取CAN的主状态寄存器了AI加了中间的读取状态寄存器的代码我查了数据手册发现读取的是发送状态寄存器CAN_TSRTXKO0这一位是为了看看邮箱0的数据到底有没有发送出去。还读取了错误状态寄存器ESR的LEC位为的是看看CAN通信过程中到底是哪一步出了问题。终于在这这几行代码的调试帮助下我发现问题了打印的日志显示错误的地方是在CAN 控制器尝试发送隐性位高电平/逻辑1时监控到总线上的电平却是显性低电平/逻辑0。总线上有东西在强行把电平拉低说明总线电气特性异常。然后AI就让我测试回环模式看看是不是CAN控制器的问题结果发现回环模式能正常进行则大概率是CAN收发器的问题了。AI就让我检测引脚有没有接对让我检查供电是不是5V结果最后发现居然是c8t6的板子的CAN收发器供电不足5V导致的一系列问题uint32_t Can_Send_Msg(uint32_t id,uint32_t len,uint8_t *buf) { CAN_TxHeaderTypeDef TxMessage; uint32_t Tx_Mail CAN_TX_MAILBOX0; TxMessage.StdId id; TxMessage.IDECAN_ID_STD; TxMessage.DLClen; TxMessage.RTRCAN_RTR_DATA; if(HAL_OK!HAL_CAN_AddTxMessage(hcan,TxMessage,buf,Tx_Mail)) return 1; printf(Sending\r\n); while(HAL_CAN_GetTxMailboxesFreeLevel(hcan)!3); uint32_t tsr hcan.Instance-TSR; printf(TSR0x%08lX, TXOK0%d\r\n, tsr, (tsr CAN_TSR_TXOK0) ? 1 : 0); uint32_t esr hcan.Instance-ESR; printf(ESR0x%08lX, LEC%d\r\n, esr, (esr 4) 7); printf(Send_OK\r\n); return 0; }以上排查流程都是我从来没想到的其实也是我不太会使用数据手册现在知道数据手册的重要性了到真正的项目开发没有教程给你指导可能出现的错误或者教程压根就不会让你碰这些可能出现的错误我就一直不会想到使用数据手册就会导致我一离开教程或者手把手教学就啥都做不出来。总的来看虽然只是一个小小的供电的问题但是其中的排查过程可谓是十分艰辛这也体现细节的重要性我之前犯过这个错好几次了但是也让我学会很多让我意识到数据手册的重要性。