如题,为什么要这样做?
最近做的一个项目上使用了74HC595作为指示灯板使用;
这个灯板与驱动板是通过排线连接,排线约25cm长;
在实验室测试一切正常,发到客户手上使用就出现了某个LED跳动情况;
跳动原因:传输线收到干扰。
这种显示方式抗干扰能力非常差且没有校验功能,满足不了需求;
因为传输线是必须要有的,所以只能通过增加校验的方式来处理干扰。
解决方法:
- 指示灯板增加MCU;
- 驱动板与灯板改为UART通讯增加校验功能;
因为驱动板与灯板连接的IO口没有UART外设功能,只能通过软件模拟UART使用;
代码
代码来自于:发个软件UART代码吧 (amobbs.com 阿莫电子技术论坛)
#include "SimComIO.h"
#include <string.h>//为了提高接收的准确率和纠错性能,采用3倍采样率,定时中断的频率是波特率的3倍。例如:需要波特率9600bps的话,需要1/(9600*3)=34.72us的定时中断。//#define SC_RXD 6
//#define SC_RXD_SET_INPUT() {DDRD&=(~BIT(SC_RXD));}
//#define SC_RXD_GET() ((PIND&(BIT(SC_RXD))))
#define SC_RXD_GET() (HAL_GPIO_ReadPin(SIM_UART_RX_GPIO_Port, SIM_UART_RX_Pin))//#define SC_TXD 7
//#define SC_TXD_0() {PORTD&=(~BIT(SC_TXD));}
//#define SC_TXD_1() {PORTD|=BIT(SC_TXD);}
//#define SC_TXD_SET_OUTPUT() {DDRD|=BIT(SC_TXD);}
#define SC_TXD_0() {HAL_GPIO_WritePin(SIM_UART_TX_GPIO_Port, SIM_UART_TX_Pin, GPIO_PIN_RESET);}
#define SC_TXD_1() {HAL_GPIO_WritePin(SIM_UART_TX_GPIO_Port, SIM_UART_TX_Pin, GPIO_PIN_SET);}uint8_t sc1_rxd_scan_ct=0;
uint8_t sc1_rxd_scan_next_time=0;
uint8_t sc1_rxd_scan_step=0;
uint8_t sc1_rxd_dat;
uint8_t sc1_rxd_ready=0;
uint8_t sc1_rxd_tmpdat;volatile uint8_t sc_txd_ready=0;//模拟串口变量
volatile uint8_t sc_txd_bit_pt=0;
volatile uint8_t sc_txd_data=0;
volatile uint8_t sc_txd_ct=0;// typedef sim_uart_def{// uint8_t sc1_rxd_scan_ct;// uint8_t sc1_rxd_scan_next_time;// uint8_t sc1_rxd_scan_step;// uint8_t sc1_rxd_dat;// uint8_t sc1_rxd_ready;// uint8_t sc1_rxd_tmpdat;// uint8_t sc_txd_ready;//模拟串口变量// uint8_t sc_txd_bit_pt;// uint8_t sc_txd_data;// uint8_t sc_txd_ct;
// }TYPE_SIM_UART_DEF;// TYPE_SIM_UART_DEF su1,su2,su3,su4,su5,su6;// void SC_RxdSrv(TYPE_SIM_UART_DEF *pSU);
// void SC_TxdSrv(TYPE_SIM_UART_DEF *pSU);void SC_Recv_Pro(uint8_t dat){}void SC_RxdSrv(void){if(sc1_rxd_scan_step==0){if(!SC_RXD_GET()){sc1_rxd_scan_step=1;return;}}if(sc1_rxd_scan_step==1){if(!SC_RXD_GET()){sc1_rxd_scan_step=2; //rxd start bit ok,goto next stepsc1_rxd_scan_ct=0;sc1_rxd_scan_next_time=3;sc1_rxd_tmpdat=0;return;}else{sc1_rxd_scan_step=0; //rxd start bit is avalidreturn;}}if(sc1_rxd_scan_step>=2){sc1_rxd_scan_ct++;if(sc1_rxd_scan_ct<sc1_rxd_scan_next_time) return;sc1_rxd_scan_ct=0;if(sc1_rxd_scan_step<10){sc1_rxd_tmpdat>>=1;if(SC_RXD_GET()){sc1_rxd_tmpdat|=0x80;}sc1_rxd_scan_step++; return;}if(sc1_rxd_scan_step==10){if(SC_RXD_GET()){sc1_rxd_dat=sc1_rxd_tmpdat;sc1_rxd_ready=1;//Receive a byte OK #if 0sc_txd_data=sc1_rxd_dat;sc_txd_ready=1;#endif#if 0Test_Uart1(sc1_rxd_dat);#endifSC_Recv_Pro(sc1_rxd_dat);}sc1_rxd_scan_step=0;return;}}}void SC_TxdSrv(void){sc_txd_ct++;if(sc_txd_ct<3) return;sc_txd_ct=0;if(sc_txd_ready){ //Data Readyif(sc_txd_bit_pt<10){if(sc_txd_bit_pt==0){SC_TXD_0(); //Start BIT}else{if(sc_txd_bit_pt>=9){SC_TXD_1(); //End BIT}else{ //数据位if((sc_txd_data>>(sc_txd_bit_pt-1))&0x01){ SC_TXD_1();}else{SC_TXD_0();}} }} if(sc_txd_bit_pt>10){ sc_txd_bit_pt=0; //发送完后延时两个时钟,复位各标志sc_txd_ready=0; }else{sc_txd_bit_pt++; //位指针自加 }}}void SC_send_char(uint8_t b){sc_txd_data=b;sc_txd_ready=1;while(sc_txd_ready==1);}
void SC_send_str(uint8_t *str){while((*str)!=0){SC_send_char(*str);str++;}}void SC_send_arr(uint8_t *arr,uint8_t len){while(len--){SC_send_char(*arr);}
}void init_SimComIO(void){// SC_TXD_SET_OUTPUT();SC_TXD_1();
// SC_RXD_SET_INPUT();}// #pragma interrupt_handler ISR_T1:8
// void ISR_T1(void)
// {
// //compare occured TCNT1=OCR1A// SC_RxdSrv();// SC_TxdSrv();
// }
移植
定时器
波特率9600,3倍采样34.72us,使用定时器2完成
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if (htim->Instance == TIM4) {HAL_IncTick();}/* USER CODE BEGIN Callback 1 */if (htim->Instance == TIM2) {
// HAL_GPIO_TogglePin(SIM_UART_TX_GPIO_Port, SIM_UART_TX_Pin);SC_TxdSrv();
// SC_RxdSrv();}/* USER CODE END Callback 1 */
}
示波器观察IO引脚反转周期,符合需求。
Freertos
HAL_TIM_Base_Start_IT(&htim2);
在开启定时器的时候,整个任务调度接近瘫痪。
问题分析:
freertos心跳是1ms,35us这样频繁中断会影响调度的。
现在的问题是:freertos 能用35us这样的中断吗?如果能应该怎么用?
于是chatgpt开始对话,问题主要是问gpt freertos中这种35us 频繁中断应该怎么使用?
没有得到想要的答案;
网络上没得到答案就咨询身边大佬吧;
转义:
就是咱们这个发送也不是连续不断发送,实际使用中1S发送1帧就行了。
使用
taskENTER_CRITICAL();
//code
taskEXIT_CRITICAL();
加入代码测试,发现问题:进入临界区之后定时器2中断不进了,被关了。
于是想,那就使用停止调度的呗
vTaskSuspendAll(); /* 开启调度锁 */ printf("任务vTaskLed1正在运行\r\n"); if(!xTaskResumeAll()) /* 关闭调度锁,如果需要任务切换,此函数返回pdTRUE,否则返回pdFALSE */{taskYIELD ();}
测试还是不行。
二问大佬:
于是查代码,freertos在哪里管理中断的;
taskENTER_CRITICAL();
进这个接口查源码
最后查到了下面那个地方
打开cubemx 找修改优先级的地方
上面是修改优先级的地方,那定时器2的优先级是多少呢?
发现这个优先级是灰色的,是与freertos的
是绑定的,比如你将 LIBRARY MAX SYSCALL INTERRUPT PRIORITY修改为6,定时器的优先级也会跟着修改为6;
那好办,在代码中手动修改定时器2优先级
HAL_NVIC_SetPriority(TIM2_IRQn, 4, 0);HAL_NVIC_GetPriority(TIM2_IRQn, TIM2_PriorityGroup, &TIM2_PreemptPriority, &TIM2_SubPriority);printf(" HAL_NVIC_GetPriority TIM2_PriorityGroup:%d, TIM2_PreemptPriority:%d,TIM2_SubPriority:%d \r\n" ,TIM2_PriorityGroup,TIM2_PreemptPriority,TIM2_SubPriority);
验证修改成功。
验证
SC_send_char(0x55);
发现使用上面代码会卡在这个while里面,单步调试查看 sc_txd_ready 变量已经变为0了,为什么还会卡在这里?
void SC_send_char(uint8_t b){sc_txd_data=b;sc_txd_ready=1;while(sc_txd_ready==1);}
解决方法
加入volatile 修饰符;
volatile uint8_t sc_txd_ready=0;
总结
通过上面学习使用方法与故障排除,STM32 freertos 使用软件模拟串口uart已经正常可以使用了。通过这个测试也对freertos 有了进一步认识。
感谢
babyos 作者 提供的帮助;