STM32F4 串口DMA缓冲实现之同步接收 异步发送
程序员文章站
2024-02-22 18:28:22
...
目录
绪论
本文针对STM32F4基于FreeRTOS操作系统的串口收发实现。
对于操作系统来说,通过while死等一个状态或者频繁的中断,都是一种奢侈,说白了都是不明智的选择。会大大降低操作系统的实时性和运行效率。
所以我不希望我的串口收发函数存在这些情况。采用DMA传输是一个最为合理的方案。
串口DMA接收
DMA可以自动传输串口数据到内存中,当接收满整个存储空间上限的时候,DMA可以自动从头开始循环传输数据。采用循环链表的实现方式如下。
- DMA的传输长度为链表的总长度LEN
- DMA设置为循环模式
- 链表的头HEAD = LEN – DMA_NDTR (等待传输数据的数量)
- 链表的尾 TAIL 自己控制。根据读取函数自加
- DMA接收启动后,无需中断处理也不许任何配置,只需要判断NDTR寄存器即可。
/***********************************************************************************************************************
功能:串口缓冲接收数据1
输入:
*pBuff:
待接收存入的数组
iLen:
IN:需要读取的字节个数
iTimedelay:
读取缓冲区等待超时时间,单位ms 。0时如果缓冲区无数据立即退出 建议值 10
*Status:
状态返回 ,如果不使用 NULL
输出:
实际读取到的字节个数
*************************************************************************************************************************/
KERNEL_TYPE int dma_uart_read_datas(unsigned char *pBuff, unsigned int iLen, unsigned int iTimedelay, int *Status)
{
//unsigned long lTimer;
unsigned int i;
if (iLen > 1024)
return K_FAIL;
for (i = 0; i < iLen; i++)
{
rx_head = RX_BUFF_SIZE - DMA_GetCurrDataCounter(RX_DMA_STREAM);
if (rx_head != rx_tail) //说明有数据
{
*pBuff = rx_buff[rx_tail]; //++;
pBuff++;
rx_tail++;
if (rx_tail >= RX_BUFF_SIZE)
rx_tail = 0;
}
else
{
return i;
}
}
return iLen;
}
串口发送
- 发送串口数据时,将数据写入缓冲区(循环链表)
- 判断有无数据待传输。无则设置传输内存的地址和待传输的长度,启动DMA传输
- 有则退出,无需启动DMA(会在DMA传输完成中断再启动)。
- 当进入DMA传输完成中断,判断循环链表缓冲区中有误数据。有则启动一次传输。无则直接退出中断
-
启动一次DMA发送
static void start_transport()
{
if (tx_tail == tx_head)
{
return;
}
//继续下一次DMA传输
TX_DMA_STREAM->M0AR = (unsigned int)(&(tx_buff[tx_tail])); //
if (tx_tail > tx_head) //说明缓冲区返回初始化坐标点
{
TX_DMA_STREAM->NDTR = TX_BUFF_SIZE - tx_tail; //发送到缓冲区的最大坐标点,传输完后再启动下一次回零起始的传输
tx_tail = 0;
}
else
{
TX_DMA_STREAM->NDTR = tx_head - tx_tail;
tx_tail += TX_DMA_STREAM->NDTR;
}
DMA_Cmd(TX_DMA_STREAM, ENABLE); //启动当前传输
}
-
串口发送的接口函数
/***********************************************************************************************************************
功能:串口缓冲发送数据
输入:
uart:
uart串口选择 uart1 uart2 uart3 uart4
*pBuff:
待写入的数组
iLen:
IN:写入数组的大小
iTimedelay:
写入缓冲区等待超时时间,单位ms 。0时如果无足够缓冲区直接退出
*Status:
状态返回 ,如果不使用 NULL
输出:
实际写入缓冲区的字节个数
time:2017-3-15
*************************************************************************************************************************/
static bool write_lock = false;
KERNEL_TYPE int dma_uart_write_datas(unsigned char *pBuff, unsigned int iLen, unsigned int iTimedelay, int *Status)
{
if (write_lock) return -1; //不允许重入
write_lock = true;
for (int i = 0; i < iLen; i++)
{
tx_buff[tx_head++] = pBuff[i];
if (tx_head >= TX_BUFF_SIZE)
{
tx_head = 0;
}
}
write_lock = false;
if (!(TX_DMA_STREAM->CR&DMA_SxCR_EN))//未使能
{
TX_DMA_FLAG_CLEAR;//不加上可能会造成DMA一使能就进入中断
start_transport();
}
return iLen;
}
-
DMA发送完成中断
void TX_DMA_IRQHandler()
{
if (DMA_GetFlagStatus(TX_DMA_STREAM, TX_DMA_FLAG) != RESET) //判断通道4传输完成
{
DMA_Cmd(TX_DMA_STREAM, DISABLE);
TX_DMA_FLAG_CLEAR;
if (write_lock) return;
start_transport();
}
}
注意和总结
-
DMA的中断标志问题
如下为STM32F4数据手册介绍,启动DMA之前必须先清除事件标志位。我表示深受其害。辗转难眠之后还是翻开手册看到了这句话。
#define TX_DMA_FLAG_CLEAR (DMA2->HIFCR = 0xFF << 22) //清除DMA2_Stream7所有相关标志位
所以启动DMA传输之前,必须加上上面这句。方可太平无事,否则。。。中**一样的出现数据混乱和丢失问题。
-
中断及多线程问题
- 中断会在启动之后的任何时间来临,写数据入缓冲区时是不能被打断的,所以加了一个状态锁。
- 多线程顶层调用一般都要加锁,防止数据被打断。底层也得也简单做了防重入处理。
字符设备程序框架展示
采用字符设备操作架构,有感兴趣的朋友可打赏点C币,留言后给重要部分源码。
-
字符设备模块自定义
-
字符设备接口标准化
-
字符设备注册
-
调用演示