STM32F4 串口DMA缓冲实现之同步接收 异步发送

目录

绪论

串口DMA接收

串口发送

启动一次DMA发送

串口发送的接口函数

 DMA发送完成中断

注意和总结

 DMA的中断标志问题

中断及多线程问题 

字符设备程序框架展示

字符设备模块自定义

字符设备接口标准化

字符设备注册

调用演示


绪论

本文针对STM32F4基于FreeRTOS操作系统的串口收发实现。

对于操作系统来说,通过while死等一个状态或者频繁的中断,都是一种奢侈,说白了都是不明智的选择。会大大降低操作系统的实时性和运行效率。

所以我不希望我的串口收发函数存在这些情况。采用DMA传输是一个最为合理的方案。

 

串口DMA接收

DMA可以自动传输串口数据到内存中,当接收满整个存储空间上限的时候,DMA可以自动从头开始循环传输数据。采用循环链表的实现方式如下。

  1. DMA的传输长度为链表的总长度LEN
  2. DMA设置为循环模式
  3. 链表的头HEAD = LEN – DMA_NDTR (等待传输数据的数量)
  4. 链表的尾 TAIL 自己控制。根据读取函数自加
  5. 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;
}

串口发送

  1. 发送串口数据时,将数据写入缓冲区(循环链表)
  2. 判断有无数据待传输。无则设置传输内存的地址和待传输的长度,启动DMA传输
  3. 有则退出,无需启动DMA(会在DMA传输完成中断再启动)。
  4. 当进入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传输之前,必须加上上面这句。方可太平无事,否则。。。中彩票一样的出现数据混乱和丢失问题。

  • 中断及多线程问题 

  1. 中断会在启动之后的任何时间来临,写数据入缓冲区时是不能被打断的,所以加了一个状态锁。
  2. 多线程顶层调用一般都要加锁,防止数据被打断。底层也得也简单做了防重入处理。

字符设备程序框架展示

 采用字符设备操作架构,有感兴趣的朋友可打赏点C币,留言后给重要部分源码。

  • 字符设备模块自定义

  • 字符设备接口标准化

 

  • 字符设备注册

  • 调用演示

发布了13 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ai5945fei/article/details/103550155