不知道你有没有这种疑惑,如果在程序中有多个任务,每个人任务都会调用同一个串口发送数据,那会不会在上一次发送还没完毕之后就开始了下一次发送,那是将上次的数据发完还是直接开始新的数据发送呢。当然得等到上次数据发完啦。不然发送的数据很大概率会丢失一部分,导致错误解析失败。
如何解决。
1调整结构,在某一个函数中专门用来发送,例如modbus的解析,会在解析到相应的数据后统一调用串口发送,而不是到处发送,send乱飞。
2增加一个标志,判断当前的发送有没有结束,如果没有结束就等待当前发送结束,在发送。嗯。。。。那会不会有点笨,不是得一直延时,等待,查询。。。
3当然还有一个最简单的办法,那就是最原始的while轮训发送。但是这样会很大程度牺牲效率。尤其大数据量的时候更加明显。
假如可以这样,在发送时先将数据存起来,在某一个地方专门取出来发送,输入加1,还没发完但是又有新的数据要发送的时候,就像新的数据存起来,输入再加1。搞个队列,当上一条数据发送完,输出加1,当输入只要不等于输出,说明此时有新的待发送数据,然后从队列中取出对应的队列数据开始发送即可,新的数据不影响当前数据的发送,发送内容只会填充到队列,再由发送进程来取出。
那么可以这样设计,定义一个结构体。
typedef struct
{
uint8_t* pdata;//发送地址指针
UART_HandleTypeDef *uart_p;//绑定的硬件串口指针
uint8_t send_in_item;//填充序列
uint8_t send_out_item;//发送序列
enum state uart_state;//串口状态
uint16_t len[MAX_ITEM];//发送长度记录
uint8_t send_buff[MAX_ITEM][MAX_LEN];//128字节发送缓存 32组
} uart_tx_st;
uart_tx_st cmd;
定义状态枚举
enum state
{
ready,
runing,
} ;
几个功能函数
初始化。绑定硬件串口,将输入输出的序列号清零。
void usart_send_init(UART_HandleTypeDef *huart)
{
cmd.uart_p = huart;
cmd.send_out_item = 0;
cmd.send_in_item = 0;
}
发送实体,获取字符串的长度,并将字符串复制到发送队列。并记录长度以及输入序列号
void usart_send_str(char *p, uart_tx_st *uart)
{
uint8_t len = strlen((char *)p);
memcpy((char *)&cmd.send_buff[cmd.send_in_item][0], p, len);
uart->len[uart->send_in_item] = strlen((char *)&cmd.send_buff[cmd.send_in_item][0]);
uart->send_in_item = get_circlr_in(MAX_ITEM);
}
发送进程实体
判断输入输出的序列是否相等,在空闲状态下则启动一次发送,改变串口状态,设置dma发送的硬件地址,数据地址,以及长度。
void usart_send_task(uart_tx_st *uart)
{
if(uart->send_in_item != uart->send_out_item && uart->uart_state == ready)
{
uart->uart_state = runing;
HAL_UART_Transmit_DMA(uart->uart_p, (uint8_t *)&uart->send_buff[uart->send_out_item][0], uart->len[uart->send_out_item]);
}
}
在数据队列满了的时候需要从头开始下一次存放,所以序列号会重新开始计算。
uint8_t get_circlr_in(uint8_t max)
{
cmd.send_in_item = cmd.send_in_item < (max - 1) ? cmd.send_in_item + 1 : 0;
return cmd.send_in_item ;
}
uint8_t get_circlr_out(uint8_t max)
{
cmd.send_out_item = cmd.send_out_item < (max - 1) ? cmd.send_out_item + 1 : 0;
return cmd.send_out_item ;
}
在发送完毕时,输出序列号加1,并将串口状态恢复成ready。
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
cmd.send_out_item = get_circlr_out(MAX_ITEM );
cmd.uart_state = ready;
}
在freertos中创建三个任务,一个任务循环执行发送进程usart_send_task。一个任务定时100ms发送,一个任务定时200ms发送。因为发送进程需要更加频繁的进行,所以定时间隔很短,如果定时时间长了,会导致队列一直是满的,取的速度比存的速度慢,导致数据不能及时发送,在裸机上,这里可以放在while(1)中。
for(;;)
{
osDelay(1);
usart_send_task(&cmd);
}
for(;;)
{
HAL_GPIO_TogglePin( GPIOC, GPIO_PIN_13);
usart_send_str("StartTask02\r\n", &cmd );
osDelay(100);
}
for(;;)
{
osDelay(200);
usart_send_str("StartTask03\r\n", &cmd );
}
效果
在调试时的状态
可以看到StartTask02比StartTask03频率高一倍,当前发送时不会影响上一次发送。可以在调试窗口看到数据内容都在队列中存放。发送时使用dma,在大数量时,也不会影响程序运行,输出频率很高,内容很多时,可以尝试提高波特率或者增加队列缓存大小。这种方式比较简单且灵活的解决了程序中异步打印存在的问题,实现方式直观且简单,易于理解。当然也可以使用环形队列来进一步提高内存的利用率,但处理会更加复杂,也难以调试,不够直观。没必要啊主要是,这样多简单,浪费了一点点,怎么了。
完整的cube工程在微信关注公众号(芯片家)发送串口就可以获取下载链接了。实际体验这个结构很方便好用。