rt-thread------串口(二)发送篇
创始人
2024-06-02 06:47:41
0

系列文章目录

rt-thread 之 fal移植
rt-thread 之 生成工程模板
STM32------串口理论篇
rt-thread------串口V1版本(一)配置

文章目录

  • 系列文章目录
  • 前言
    • 轮询发送
    • 中断发送
    • DMA发送


前言

这篇文章主要讲一下rt-thread的串口的三种发送,轮询、中断、DMA发送。rt-thread提供的串口驱动V1版本中断发送有问题,这边只提供中断的发送思路,或者大家可以去研究一下V2版本。后续会有V2版本的文章更新。首先需要提出两个概念阻塞非阻塞,阻塞顾名思义程序会在这里等待到阻塞结束。非阻塞则会立马执行结束跑到后面的程序。


下面是发送程序对应用层的接口:

rt_size_t rt_device_write(rt_device_t dev,rt_off_t    pos,const void *buffer,rt_size_t   size)

里面本质上会调用rt_serial_write()函数往串口设备写数据,里面又细分了三个函数是这篇文章的主角:_serial_int_tx()(中断发送) _serial_dma_tx()(dma发送)_serial_poll_tx()轮询发送。

轮询发送

轮询发送是最简单的发送模式,rt-thread提供的是阻塞的发送函数。程序会将需要发送的数据一字节一字节的写到串口的DR寄存器,然后查询发送完成标志,直到数据完全发送结束。优点:程序简单易懂,缺点:浪费cpu资源,容易被高优先级任务打断,一帧数据发送断断续续的。
_serial_poll_tx()的具体实现(省略流处理代码):

rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{int size;RT_ASSERT(serial != RT_NULL);size = length;while (length){serial->ops->putc(serial, *data);++ data;-- length;}return size - length;
}

很简单就是每次发送1字节数据循环length长度。再看看putc函数的实现,我这边代码是stm32的所以是以下这个函数:

static int stm32_putc(struct rt_serial_device *serial, char c)
{struct stm32_uart *uart;RT_ASSERT(serial != RT_NULL);uart = rt_container_of(serial, struct stm32_uart, serial);UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32WL) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \|| defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) || defined(SOC_SERIES_STM32L5) \|| defined(SOC_SERIES_STM32G4) || defined(SOC_SERIES_STM32MP1) || defined(SOC_SERIES_STM32WB) || defined(SOC_SERIES_STM32F3)  \|| defined(SOC_SERIES_STM32U5)uart->handle.Instance->TDR = c;
#elseuart->handle.Instance->DR = c;
#endifwhile (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);return 1;
}

代码很简单,根据单片机型号将数据写入DR或者TDR寄存器中,然后等待发送完成标志。按照这种方式,代码需要在这阻塞到一帧数据完全发送结束才会执行后面的代码。再此期间代码会被高优先级任务和中断打断。像modbus这种没有没有帧长度协议靠字符间间隔区分帧结束的情况不是很推荐这种写法。当然这种写法配合RS485拉低拉高使能引脚就特别简单。

中断发送

中断发送函数:

rt_inline int _serial_int_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{int size;struct rt_serial_tx_fifo *tx;RT_ASSERT(serial != RT_NULL);size = length;tx = (struct rt_serial_tx_fifo*) serial->serial_tx;RT_ASSERT(tx != RT_NULL);while (length){if (serial->ops->putc(serial, *(char*)data) == -1){rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);continue;}data ++; length --;}return size - length;
}

与轮询发送区别是多了一个等待完成信号量的内容。bug是里面字符发送函数与轮询发送函数一样,永远返回1,所以等待完成函数不会被触发。rt-thread bspV1版本这边是有问题的,感兴趣的同学可以看下V2版本的串口驱动函数。
一般中断思路是发送第一字节,然后进入中断函数,在中断函数中将剩余字节发送完成。优点不会出现轮询发送中的缺点期间代码会被高优先级任务和低优先级中断打断,可用于modbus这类无长度的协议。这种模式也存在缺点,发送期间全程会平凡进入中断,若波特率特别高则使用DMA发送更加合理。

DMA发送

dma发送函数:

rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{rt_base_t level;rt_err_t result;struct rt_serial_tx_dma *tx_dma;tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);if (result == RT_EOK){level = rt_hw_interrupt_disable();if (tx_dma->activated != RT_TRUE){tx_dma->activated = RT_TRUE;rt_hw_interrupt_enable(level);/* make a DMA transfer */serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);}else{rt_hw_interrupt_enable(level);}return length;}else{rt_set_errno(result);return 0;}
}

其中比较重要的是以下两个函数:
rt_data_queue_push()
将数据放入dma数据队列中,这里需要特别注意一下直接是应用层写的数据,没有发送缓存区,若是局部变量,在dma发送是可能已经释放掉,无法发送正确数据。其次在发送过程中修改应用层数据,dma会直接发送修改后的数据,这点也需要特别注意。
serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);
在函数指针背后调用的是对hal库分装一层的dma传输函数,其代码如下(有删减):

static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
{if (RT_SERIAL_DMA_TX == direction){if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK){return size;}else{return 0;}}return 0;
}

特别注意HAL_UART_Transmit_DMA()是个无阻塞函数,也就是执行完这一行时,数据开始发送,但是不会等到数据完全发送结束。若rs485的串口从轮询发送切换成dma发送需要特别注意这一点,否则只能发送1-2字节。那么如何优雅的控制485的使能信号呢?在dma中断中有个回调函数 hdma->XferCpltCallback(hdma);DMA中断代码如下(有删减):

void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{/* Transfer Complete Interrupt management ***********************************/else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET)){if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U){/* Disable the transfer complete and error interrupt */__HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC);  /* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}/* Clear the transfer complete flag */__HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));/* Process Unlocked */__HAL_UNLOCK(hdma);if(hdma->XferCpltCallback != NULL){/* Transfer complete callback */hdma->XferCpltCallback(hdma);}}}

hdma->XferCpltCallback(hdma);回调函数本质上会调用这个函数static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma),其中
/* Enable the UART Transmit Complete Interrupt */
SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);
会通过软件触发串口的完成中断。

static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;/* DMA Normal mode*/if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U){huart->TxXferCount = 0x00U;/* Disable the DMA transfer for transmit request by setting the DMAT bitin the UART CR3 register */CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);/* Enable the UART Transmit Complete Interrupt */SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);}
}

发送完成去拉485使能引脚可以在串口中断中的回调函数中实现,也可以在回调函数中通过信号量的释放完成阻塞的DMA发送。但是阻塞指的是任务阻塞,cpu资源会被该任务释放,执行其他任务。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...