硬汉嵌入式论坛

 找回密码
 立即注册
查看: 1104|回复: 4
收起左侧

[客户分享] DMA与环形缓冲区实现(KFIFO)

  [复制链接]

1

主题

2

回帖

10

积分

新手上路

积分
10
发表于 2024-2-2 20:23:30 | 显示全部楼层 |阅读模式
本帖最后由 xzyswr 于 2024-2-2 20:34 编辑

一个比较通用的环形缓冲区实现,代码是去年就写好的,拖拖沓沓今天才发出来,很少在论坛发东西,排版很乱,见谅
虽然也是造轮子,但过程学到了很多,这才是快乐的地方

主要是接我的上一条帖子STM32不定长串口DMA接收+简单AT命令实现,评论区有朋友讨论了FIFO相关问题。顺便完善一下
首先简单介绍一下KFIFO的特点

static inline uint32_t RB_OFFSET_OUT(const KFIFO_Handle_t *rb)
{
    return rb->out & rb->mask; // 使用位运算获取读取位置在缓冲区中的偏移量
}



特点1:使用位运算(&)替换模运算(mod),在效率上更快,较小代价:缓冲区的大小需要是2的幂次方
特点2:读写指针位置使用uint32_t来记录,正常不会出现in大于out的情况,,有一点需要考虑,比如使用达到uint32_t最大长度时考虑重置读写指针()
使用三目运算来判断因为缓冲区循环in大小超过out的情况(这是以前为FIFO考虑的情况,在KFIFO中不需要):
#define RB_USED ((rb->in<rb->out)?RB_SIZE-(rb->out-rb->in)rb->in-rb->out))



使用方法介绍:
1.乒乓缓存,双缓存
DMA配合乒乓缓存主要是为了实现DMA接收时旧数据被新数据覆盖
这里就不造轮子了。我也是参考了这位网友的(我按照这位网友的方法验证过是可以的,)
STM32 HAL 库实现乒乓缓存加空闲中断的串口 DMA 收发机制,轻松跑上 2M 波特率
2.使用线性读写
函数原型:

/**
* @brief 向KFIFO的线性缓冲区写入数据。
*
* 这个函数向KFIFO的线性缓冲区写入数据。
*
* @param rb 指向KFIFO句柄的指针。
* @return 指向可以写入数据的线性缓冲区的指针。
*/
void *KFIFO_Linear_Write(KFIFO_Handle_t *rb);

/**
* @brief 完成对KFIFO线性缓冲区的写操作。
*
* 这个函数完成对KFIFO线性缓冲区的写操作。
*
* @param rb 指向KFIFO句柄的指针。
* @param size 写入线性缓冲区的字节数。
*/
void KFIFO_Linear_Write_Finish(KFIFO_Handle_t *rb, uint32_t size);
其原理也很简单,调用函数*KFIFO_Linear_Write后返回的指向一片空闲内存空间的指针,可以把DMA的接收地址设置为函数返回的地址,在读写完成使用KFIFO_Linear_Write_Finish更新地址


这种方法说实话学习为主,实际使用复杂需要考虑的情况较多
方法示例:

// dma空闲中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART2)
    {
        if (Size == RB_CMD_SIZE)
            KFIFO_Linear_Write_Finish(&RxBuffer_Handle, Size); // 线性缓冲区写入完成,更新地址

        uint8_t *pRB_RX_Buffer = KFIFO_Linear_Write(&RxBuffer_Handle);     // 获取下一次写入的地址
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2, pRB_RX_Buffer, RB_CMD_SIZE); // 重新启动DMA接收
    }
}

然后是读写缓冲区的函数,把接收的内容发出去,在循环中调用这两个函数就行了

void UserApp_Recv(void)
{
    //获取长度
    uint32_t size = KFIFO_GetLen(&RxBuffer_Handle);
    if (size == 0)
        return;
    //线性读取
    uint8_t *pRB_RX_Buffer = KFIFO_Linear_Read(&RxBuffer_Handle);
    //拷贝到发送缓冲区
    KFIFO_Write_Buffer(&TxBuffer_Handle, pRB_RX_Buffer, size);
    KFIFO_Linear_Read_Finish(&RxBuffer_Handle, size);
}

void UserApp_Send(void)
{
    //获取长度
    uint32_t size = KFIFO_GetLen(&TxBuffer_Handle);
    if (size == 0)
        return;
    //DMA发送
    HAL_UART_Transmit_DMA(&huart2, KFIFO_Linear_Read(&TxBuffer_Handle), size);
    //更新地址
    KFIFO_Linear_Read_Finish(&TxBuffer_Handle, size);
}

如果有什么问题希望大家反馈一下,感谢






kfifo.c

4.1 KB, 下载次数: 27

kfifo.h

4.29 KB, 下载次数: 21

评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106753
QQ
发表于 2024-2-3 08:32:55 | 显示全部楼层
谢谢楼主分享。
回复

使用道具 举报

2

主题

269

回帖

275

积分

高级会员

积分
275
发表于 2024-2-21 11:34:59 | 显示全部楼层
写入长度有点问题吧,我改了改
static inline uint32_t RB_USED(const KFIFO_Handle_t *rb)
{
    return (rb->in - rb->out) & rb->mask; // in是写入位置,out是读取位置,二者之差就是已使用的大小
}
回复

使用道具 举报

2

主题

269

回帖

275

积分

高级会员

积分
275
发表于 2024-3-6 13:48:35 | 显示全部楼层
线性读写指针也有问题  整理完的丢仓库了https://github.com/cctv180/kfifo
void *KFIFO_Linear_Read(KFIFO_Handle_t *rb)
{
    if (RB_OFFSET_OUT(rb) != 0)
    {
        return (uint8_t *)rb->pool + RB_OFFSET_OUT(rb);
    }

    return rb->pool;
}

回复

使用道具 举报

1

主题

2

回帖

10

积分

新手上路

积分
10
 楼主| 发表于 2024-4-21 03:53:05 | 显示全部楼层
cctv180 发表于 2024-2-21 11:34
写入长度有点问题吧,我改了改
static inline uint32_t RB_USED(const KFIFO_Handle_t *rb)
{

使用& rb->mask会导致in为满时,out为0时返回为0
例如一个缓冲区大小为8,
in=8
out=0
运算:
(8-0) & 7
(1 000-0) AND 0111 = 0

kfifo 设计使用uint32_t存储读写指针
正常情况下in和out一直++不会出现需要指针回绕的情况,哪怕加到比buffer范围还大
比如缓冲区大小为8,计算used就不说了,说下偏移量
假如循环一段时间后in = 100
运算:
0110 0100 AND 0111 = 0100 (4)
100/8 = 96 %4 ,余4所以位置正确,偏移量会指向数组[4]

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-4 09:58 , Processed in 0.202230 second(s), 30 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表