最近做的一个项目上使用了74HC595作为指示灯板使用;
这个灯板与驱动板是通过排线连接,排线约25cm长;
在实验室测试一切正常,发到客户手上使用就出现了某个LED跳动情况;
跳动原因:传输线收到干扰。
这种显示方式抗干扰能力非常差且没有校验功能,满足不了需求;
因为传输线是必须要有的,所以只能通过增加校验的方式来处理干扰。
解决方法:
因为驱动板与灯板连接的IO口没有UART外设功能,只能通过软件模拟UART使用;
代码来自于:发个软件UART代码吧 (amobbs.com 阿莫电子技术论坛)
#include "SimComIO.h"
#include
//为了提高接收的准确率和纠错性能,采用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 step
sc1_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 avalid
return;
}
}
if(sc1_rxd_scan_step>=2){
sc1_rxd_scan_ct++;
if(sc1_rxd_scan_ct>=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 0
sc_txd_data=sc1_rxd_dat;
sc_txd_ready=1;
#endif
#if 0
Test_Uart1(sc1_rxd_dat);
#endif
SC_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 Ready
if(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引脚反转周期,符合需求。
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的优先级是多少呢?
是绑定的,比如你将 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 作者 提供的帮助;