(七)ASCLIN_UART模块串口+DMA+环形缓存区

文章目录


环形缓存区

长什么样子

顾名思义,环形的数组罢了,大致如图所示:

理解起来就是:往数组里面放数据,数组索引,一直在加,加到最后,再回来,这个原理就是环形缓存区


为什么要用环形缓存区

基于(六)ASCLIN_UART模块串口DMA模式
上篇博文,已经完成了串口硬件触发DMA搬运,从数据寄存器读写到自己的buffer里面,现在只是把这个g_ascRxBuffer 和 g_ascTxBufferr 变得高级,或者说是更智能化,并且再结合一些小算法,可以实现任意字节的读写,(任意字节写,其实上一章节已经完成了),但是上一章节读是有点问题的,任意字节写,可以自主指定长度,但是指定长度读,就有点鸡肋了,因此采用环形缓存区。

使用环形缓存区存在的小问题及解决思路

问题1

因为这个往环形缓存区读写,是并没有判断里面数据是否被处理过了,必然会造成数据覆盖等问题。

1.比如写入,假设环形缓存区是16字节,我写17个字节的话,是不是第一个字节就会被覆盖。可以定义大一点环形缓存区,或者,在写入的时候,代码判断一下,是否有足够的空间写入(这里用总长度➖未被处理的数据长度➖写入长度是否大于0)

2.比如第一次写10个字节到缓存区,第二次写7个字节到缓存区,是不是缓存区的第1个字节会被覆盖,如果在第一次写入10个字节的时候,直接启动DMA搬运,然后再发送7个字节,就不会造成第一次的10个字节的第一个字节被覆盖问题,因为此时前面的10个字节已经发出去了,被覆盖的数据,已经处理过了。

3.比如,串口有数据发来,然后被DMA搬运到数据缓存区了,然后读取17个字节,同样也是第一个字节被重复读取了,一般来说,都是有事先商量好,串口发多大内存,一般不会超过,只需要数据缓存区定义大一点即可。或者 代码判断 如果要读取的长度 大于 环形缓存区长度直接return

4.比如 第一次串口发来10个字节,第二次发来7个字节,第二次发来的第17个字节 会覆盖第1个字节,如果再第一次串口发送来,及时把数据读取走,即使第二次发来7个字节的最后一个字节,会覆盖第1次 发来的10个字节的第一个字节,但已经及时处理了,就避免了覆盖问题。

问题2

为了处理问题1的2,3问题时,使用了一个小算法,是用一个记录处理数据长度的索引,这个算法,有一点点小问题,就是读取,刚好环形缓存区的长度字节,因为DMA搬运的"指针"偏移刚好是一个环形缓存区,导致DMA指针就是指向头部的,造成读取数据长度为0,造成读取不到数据,在代码那块会详细分析。


实现什么样的效果

对于读操作:把g_ascRxBuffer定义成环形缓存区类型的变量,因为DMA不断的将数据搬运到g_ascRxBuffer的buffer里面,不需要人为干涉,然后只需要读取环形缓存区g_ascRxBuffer的buffer里面数据到自己的数组里面即可。

对于写操作:把g_ascTxBuffer定义成环形缓存区类型的变量,然后将自己要发送的数据,拷贝到g_ascTxBuffer的buffer里面,然后要人为设置DMA一次搬运的数量,并且开启DMA搬运即可。


代码如何实现环形缓存区

如下封装了ringbuf.c ringbuf.h

ringbuf.h

c 复制代码
#ifndef __RINGBUF_H_
#define __RINGBUF_H_

#include "stdint.h"

#define CBUF_MAX_SIZE 16

typedef struct cbuf
{
    uint8_t buffer[CBUF_MAX_SIZE]; //数组一定要放在第一个元素位置,因为要字节对齐
    uint64_t put_index; //uint32_t以460800波特率不停的通信24小时就溢出,故换为uint64_t
    uint64_t get_index;
    uint32_t buf_actual_size; //缓冲区大小,最大不超过CBUF_MAX_SIZE
}ringbuf;

void ringbuf_init(ringbuf *cbuff);
uint32_t ringbuf_Len(ringbuf *cbuff);
uint32_t ringbuf_put(ringbuf *rb, uint8_t *str,uint16_t length);
uint32_t ringbuf_get(ringbuf *rb, uint8_t *str,uint16_t length);

#endif

ringbuf.c

c 复制代码
#include "ringbuf.h"
#include "stddef.h"
#include <string.h>


void ringbuf_init(ringbuf *cbuff)
{
    cbuff->get_index = 0;
    cbuff->put_index = 0;
    cbuff->buf_actual_size = CBUF_MAX_SIZE; //初始化时缓冲区设置为最大
    memset(cbuff->buffer,'\0',CBUF_MAX_SIZE);
}

uint32_t ringbuf_Len(ringbuf *cbuff)		//这个是数据长度
{
    return (cbuff->put_index - cbuff->get_index) % (cbuff->buf_actual_size+1);//0-buf_actual_size 
}

uint32_t ringbuf_put(ringbuf *rb, uint8_t *str,uint16_t length)
{

    if ((rb == NULL) || (str == NULL) || (length == 0)) {
        return 0;
    }

    uint32_t size = rb->buf_actual_size -ringbuf_Len(rb)-length;

    if(size < 0 ){//没有足够的空间了
        return 0;
    }

    uint32_t put_pos = rb->put_index % rb->buf_actual_size; //定位投放位置
    
    if((rb->buf_actual_size - put_pos) >= length){ //buffer中剩余空间 比 数据长度 大 (可以放下) 直接放入
        (void *)memcpy(&rb->buffer[put_pos], &str[0], length);  //从put_pos位置 往里面放
    }else{                                            //剩余空间 比 数据长度 小 (不可放下) 需要拆成俩段 放入
        uint16_t a_room  = rb->buf_actual_size - put_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;  //后半段的 长度大小
        (void *)memcpy(&rb->buffer[put_pos], &str[0], a_room);  //从put_pos位置 将a_room长度的str(str前半段)放入buffer里面
        (void *)memcpy(&rb->buffer[0], &str[a_room], b_room);  //从头部位置 将b_room长度的str(str后半段)放入buffer里面
    }

    rb->put_index += length;

    return length;
}


uint32_t ringbuf_get(ringbuf *rb,uint8_t *str,uint16_t length)
{
    if ((rb == NULL) || (length == 0)) {
        return 0;
    }
    uint32_t get_pos = ((rb->get_index) % (rb->buf_actual_size));
    if((rb->buf_actual_size - get_pos) >= length){       //buffer中剩余未拷贝的数据长度 大于 需要拷贝的长度(可以直接拷贝)
        (void *)memcpy(&str[0], &rb->buffer[get_pos],length);  //从get_pos位置 拷贝 length长度 到str里面
    }else{                                          //剩余未拷贝的数据长度 小于  需要拷贝的长度(不可直接拷贝) 需要分段拷贝
        uint16_t a_room  = rb->buf_actual_size - get_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;             //后半段的 长度大小
        (void *)memcpy(&str[0],&rb->buffer[get_pos], a_room);  //从put_pos位置 往里面放a_room长度的str(str前半段)
        (void *)memcpy(&str[a_room],&rb->buffer[0], b_room);  //从头部位置 往里面放b_room长度的str(str后半段)
    }

    rb->get_index += length;

    return length;
}

代码分析

头文件里面声明一个结构体:

c 复制代码
#define CBUF_MAX_SIZE 16

typedef struct cbuf
{
    uint8_t buffer[CBUF_MAX_SIZE];
    uint64_t put_index; //uint32_t以460800波特率不停的通信24小时就溢出,故换为uint64_t
    uint64_t get_index;
    uint32_t buf_actual_size; //缓冲区大小,最大不超过CBUF_MAX_SIZE

}ringbuf;

buffer数组一定要放在第一个元素位置,因为要字节对齐

buffer:数据缓存区,长度最大为16
put_index:往数组放入的长度(对于特定的读写操作说法不是很严谨,算法中可以体会)
get_index:从数组取出的长度(对于特定的读写操作说法不是很严谨,算法中可以体会)
buf_actual_size:缓冲区大小

C文件里面:

初始化ringbuf类型的变量;

c 复制代码
void ringbuf_init(ringbuf *cbuff)
{
    cbuff->get_index = 0;
    cbuff->put_index = 0;

    cbuff->buf_actual_size = CBUF_MAX_SIZE; //初始化时缓冲区设置为最大
    memset(cbuff->buffer,'\0',CBUF_MAX_SIZE);
}

这里是用来计算数据长度用的,不是计算数组索引,因此是0-buf_actual_size长度。

c 复制代码
uint32_t ringbuf_Len(ringbuf *cbuff)
{
    return (cbuff->put_index - cbuff->get_index) % (cbuff->buf_actual_size+1);//0-buf_actual_size
}

接下来就是往这个结构体环形缓存区,读/写操作,并且用get_index来记录读操作的操作数据长度,put_index来记录写操作的数据长度:

写操作:

c 复制代码
uint32_t ringbuf_put(ringbuf *rb, uint8_t *str,uint16_t length)
{

    if ((rb == NULL) || (str == NULL) || (length == 0)) {
        return 0;
    }

    uint32_t size = rb->buf_actual_size -ringbuf_Len(rb)-length;

    if(size < 0 ){//没有足够的空间了
        return 0;
    }

    uint32_t put_pos = rb->put_index % rb->buf_actual_size; //定位投放位置

    if((rb->buf_actual_size - put_pos) >= length){ //buffer中剩余空间 比 数据长度 大 (可以放下) 直接放入
        (void *)memcpy(&rb->buffer[put_pos], &str[0], length);  //从put_pos位置 往里面放
    }else{                                            //剩余空间 比 数据长度 小 (不可放下) 需要拆成俩段 放入
        uint16_t a_room  = rb->buf_actual_size - put_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;  //后半段的 长度大小

        (void *)memcpy(&rb->buffer[put_pos], &str[0], a_room);  //从put_pos位置 将a_room长度的str(str前半段)放入buffer里面
        (void *)memcpy(&rb->buffer[0], &str[a_room], b_room);  //从头部位置 将b_room长度的str(str后半段)放入buffer里面
    }

    rb->put_index += length;

    return length;
}

代码思路:先判断,写入的数据是否为空,长度是否为0,然后判断,是否有足够的空间长度装下要写入的数据,再定义一个变量put_pos定位,将要写入的位置,然后判断从当前位置到数组尾巴,空间是否足够写入,如果够,直接拷贝数据到环形缓存区的buffer里面,如果空间不够,则将自己的数据分成两部分(a_room是前半段长度 和 b_room后半段长度),逐步拷贝到buffer里面,这里注意,用的是buf_actual_size 减去 get_pos,虽然这里减去的是get_pos索引,但是就是实际剩余的长度,另外注意,这个只是搬运到buffer里面了,并没有启动DMA往发送数据寄存器里面写入,因此要人为干涉,设置DMA搬运数,以及启动DMA搬运。


读操作:

c 复制代码
uint32_t ringbuf_get(ringbuf *rb,uint8_t *str,uint16_t length)
{
    if ((rb == NULL) || (length == 0)) {
        return 0;
    }
    uint32_t get_pos = ((rb->get_index) % (rb->buf_actual_size));

    if((rb->buf_actual_size - get_pos) >= length){       //buffer中剩余未拷贝的数据长度 大于 需要拷贝的长度(可以直接拷贝)
        (void *)memcpy(&str[0], &rb->buffer[get_pos],length);  //从get_pos位置 拷贝 length长度 到str里面
    }else{                                          //剩余未拷贝的数据长度 小于  需要拷贝的长度(不可直接拷贝) 需要分段拷贝
        uint16_t a_room  = rb->buf_actual_size - get_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;             //后半段的 长度大小

        (void *)memcpy(&str[0],&rb->buffer[get_pos], a_room);  //从put_pos位置 往里面放a_room长度的str(str前半段)
        (void *)memcpy(&str[a_room],&rb->buffer[0], b_room);  //从头部位置 往里面放b_room长度的str(str后半段)
    }

    rb->get_index += length;

    return length;

}

代码思路:先判断,g_ascRxBuffer的buffer是否为空,以及读取长度是否为0,然后获取读取位置,判断从当前位置到尾巴的长度,是否大于,需要读取的数据长度,如果大于等于,直接拷贝,g_ascRxBuffer.buffer的内容,到自己的数组里面,如果小于,则分两段拷贝(a_room是前半段的长度 和 b_room是后半段的长度)到自己数组里面。这里能直接读取的原因是DMA自动将数据搬运到g_ascRxBuffer的buffer里面了。


引入环形缓存区并实现对缓存区的读写

基于上一章代码,加入ringbuf.c.h,仅作缓存区的修改

因为加入了ringbuf.c .h 里面的ringbuf_get 实现了串口的任意字节读,和上一章节一样(因为开启了硬件触发DMA自动将数据接收到buffer里面),
但是ringbuf_get 只能实现将数据放入到buffer里面,如需发送,和上一章节类似,只需要设置DMA搬运的数量,再启动DMA即可。

修改以及添加内容

这个缓存区变量一定要字节对齐(缓存区buffer一定要放到结构体的第一个位置)

c 复制代码
/* Declaration of the FIFOs parameters */
_Alignas(16) static  ringbuf g_ascTxBuffer;     //只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
_Alignas(16) static  ringbuf g_ascRxBuffer;

串口的绑定缓存:

c 复制代码
    /* FIFO configuration */  //没用到串口接收/发送的缓存可以不绑定,直接删除即可(如出现问题,可以直接删除)
    ascConfig.txBuffer = &g_ascTxBuffer.buffer[0];
    ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
    ascConfig.rxBuffer = &g_ascRxBuffer.buffer[0];
    ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;

DMA发送的源地址

c 复制代码
    /* Address of the UART TX FIFO */
    cfg.sourceAddress = (uint32)&g_ascTxBuffer.buffer[0];
    cfg.destinationAddress = (uint32) &g_ascHandle.asclin->TXDATA.U;

DMA接收的目的地地址:

c 复制代码
    /* Address of the UART RX FIFO */
    cfg.sourceAddress = (uint32) &g_ascHandle.asclin->RXDATA.U;
    cfg.destinationAddress = (uint32)&g_ascRxBuffer.buffer[0];

添加一个测试函数

c 复制代码
void ceshi( uint8_t *str,uint16_t length)
{
    ringbuf_get(&g_ascRxBuffer,str,length);
    //ringbuf_put(&g_ascTxBuffer,str,length);
}

最终mydma.c代码:

c 复制代码
#include "IfxAsclin_Asc.h"

#include "IfxDma_Dma.h"
#include "IfxDma.h"

#include "Bsp.h"
//#include "string.h"

#include "ringbuf.h"

/*********************************************************************************************************************/
/*------------------------------------------------------Macros-------------------------------------------------------*/
/*********************************************************************************************************************/
#define UART_BAUDRATE           460800                                  /* UART baud rate in bit/s                  */

#define UART_PIN_RX             IfxAsclin2_RXE_P33_8_IN                 /* UART receive port pin                    */
#define UART_PIN_TX             IfxAsclin2_TX_P33_9_OUT                 /* UART transmit port pin                   */

#define DMA_CHANNEL_RX             INTPRIO_ASCLIN2_RX
#define DMA_CHANNEL_TX             INTPRIO_ASCLIN2_TX

IfxDma_Dma_Channel g_rxchn;                                             /* DMA channel handle                       */
IfxDma_Dma_Channel g_txchn;

/* Definition of the interrupt priorities */
#define INTPRIO_ASCLIN2_TX      11                                       /* Triggered when AscLin transmits         */
#define INTPRIO_ASCLIN2_RX      12                                       /* Triggered when AscLin receives          */

#define INTPRIO_DMA_TX          13                          /* Triggered when a DMA transaction is finished         */
#define INTPRIO_DMA_RX          14                          /* Triggered when a DMA transaction is finished         */

#define UART_RX_BUFFER_SIZE     16                                      /* Definition of the receive buffer size    */
#define UART_TX_BUFFER_SIZE     16                                      /* Definition of the transmit buffer size   */


/* Declaration of the ASC handle */
static IfxAsclin_Asc g_ascHandle;

/* Declaration of the FIFOs parameters */
_Alignas(16) static  ringbuf g_ascTxBuffer;     //只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
_Alignas(16) static  ringbuf g_ascRxBuffer;

/* Definition of txData  */
_Alignas(16) uint8 g_txData[] = "Hello World!";//只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!

/* Size of the message */
Ifx_UReg_32Bit g_count = sizeof(g_txData)-1;
//static uint8_t rx_ok = 0;
/*
int rxcnt = 0;
int txcnt = 0;


IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
    rxcnt++;
    rx_ok =1;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}


IFX_INTERRUPT(prio_DMA_TX, 0, INTPRIO_DMA_TX);
void prio_DMA_TX(void)
{
    txcnt++;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_txchn.channelId);
}
*/


/* This function initializes the ASCLIN UART module */
void init_asclin_uart(void)
{
    /* Initialize an instance of IfxAsclin_Asc_Config with default values */
    IfxAsclin_Asc_Config ascConfig;
    IfxAsclin_Asc_initModuleConfig(&ascConfig, &MODULE_ASCLIN2);

    /* Set the desired baud rate */
    ascConfig.baudrate.baudrate = UART_BAUDRATE;

    /* ISR priorities and interrupt target */
    ascConfig.interrupt.txPriority = INTPRIO_ASCLIN2_TX;
    ascConfig.interrupt.rxPriority = INTPRIO_ASCLIN2_RX;
    ascConfig.interrupt.typeOfService = IfxSrc_Tos_cpu0 ;

    /* FIFO configuration */  //没用到串口接收/发送的缓存可以不绑定,直接删除即可(如出现问题,可以直接删除)
    ascConfig.txBuffer = &g_ascTxBuffer.buffer[0];
    ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
    ascConfig.rxBuffer = &g_ascRxBuffer.buffer[0];
    ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;

    /* Pin configuration */
    const IfxAsclin_Asc_Pins pins =
    {
        NULL_PTR,       IfxPort_InputMode_pullUp,     /* CTS pin not used */
        &UART_PIN_RX,   IfxPort_InputMode_pullUp,     /* RX pin           */
        NULL_PTR,       IfxPort_OutputMode_pushPull,  /* RTS pin not used */
        &UART_PIN_TX,   IfxPort_OutputMode_pushPull,  /* TX pin           */
        IfxPort_PadDriver_cmosAutomotiveSpeed1
    };
    ascConfig.pins = &pins;

    IfxAsclin_Asc_initModule(&g_ascHandle, &ascConfig); /* Initialize module with above parameters */

    /* Modification of the TOS for the Rx related interruption */
    /* Change from CPU0 (previously defined above) to DMA */
    volatile Ifx_SRC_SRCR *src;
    src = IfxAsclin_getSrcPointerTx(ascConfig.asclin);

    /* Assign DMA as Service Provider when INTPRIO_ASCLIN2_TX is triggered */
    IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_TX);
    IfxAsclin_enableTxFifoFillLevelFlag(ascConfig.asclin, TRUE);
    IfxSrc_enable(src);

    /* Modification of the TOS for the Rx related interruption */
    /* Change from CPU0 (previously defined above) to DMA */
    src = IfxAsclin_getSrcPointerRx(ascConfig.asclin);
    /* Assign DMA as Service Provider when INTPRIO_ASCLIN2_RX is triggered */
    IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_RX);
    IfxAsclin_enableRxFifoFillLevelFlag(ascConfig.asclin, TRUE);
    IfxSrc_enable(src);
}

/* This function is called from main in order to initialize the DMA module */


void init_dma(void)
{
    /* Initialize an instance of IfxDma_Dma_Config with default values */
    IfxDma_Dma_Config dmaConfig;
    IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);

    /* Initialize module */
    IfxDma_Dma dma;
    IfxDma_Dma_initModule(&dma, &dmaConfig);

    /* Initial configuration for all channels */
    IfxDma_Dma_ChannelConfig cfg;
    IfxDma_Dma_initChannelConfig(&cfg, &dma);

    /* Following configuration is used by the DMA channel */
    cfg.moveSize = IfxDma_ChannelMoveSize_8bit;
    cfg.blockMode = IfxDma_ChannelMove_1;

    /*********************************************** TX部分**************************************************************/

    cfg.transferCount = 0;//一次触发搬运多少个字节数据(后面写入的时候会修改)
    /* DMA completes a full transaction on requests */
    cfg.requestMode = IfxDma_ChannelRequestMode_oneTransferPerRequest;  //一个请求触发一个单一DMA传输 即请求启动单个事务

    /* DMA as Interrupt Service Provider */
    cfg.hardwareRequestEnabled = TRUE;                         //  使能UART发送中断触发Dma(硬件触发)
    /* DMA channel stays enabled after one request */
    cfg.operationMode = IfxDma_ChannelOperationMode_single;   //DMA单次搬运

    /*************** Source and destination addresses ***************/
    cfg.sourceCircularBufferEnabled = TRUE;                           //开启源地址自增
    cfg.sourceAddressIncrementStep = IfxDma_ChannelIncrementStep_1;  //源地址自增加+1
    cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_16; //源地址16字节循环

    cfg.destinationCircularBufferEnabled = TRUE;                     //开启目的地址自增
    cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_none; //目的地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE

    /*************** Channel specific configurations ***************/
    /* Select the Channel 11, related to the interruption on AscLin TX */
    cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_TX;
    /* Address of the UART TX FIFO */
    cfg.sourceAddress = (uint32)&g_ascTxBuffer.buffer[0];
    cfg.destinationAddress = (uint32) &g_ascHandle.asclin->TXDATA.U;

    /*  DMA中断部分(如果采用环形缓存区,就没必要开启了哈)           */
    //cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    //cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    //cfg.channelInterruptPriority = INTPRIO_DMA_TX;
    /* Interrupt service provider */
    ///cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;

    IfxDma_Dma_initChannel(&g_txchn, &cfg);


    /********************************************************* RX部分***********************************************************/
    cfg.transferCount = 1;   //一次触发搬运多少个字节数据
    /* DMA completes a full transaction on requests */
    cfg.requestMode = IfxDma_ChannelRequestMode_completeTransactionPerRequest;  //一个请求触发一个完整事务 即请求启动完整的事务

    /* DMA as Interrupt Service Provider */
    cfg.hardwareRequestEnabled = TRUE;                         // UART接收中断触发Dma

    /* DMA channel stays enabled after one request */
    cfg.operationMode = IfxDma_ChannelOperationMode_continuous; // DMA连续搬运模式

    /*************** Source and destination addresses ***************/
    cfg.sourceCircularBufferEnabled = TRUE;                 //源地址自增
    cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;//源地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE

    cfg.destinationCircularBufferEnabled = TRUE;
    cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_16;

    /*************** Channel specific configurations ***************/
    /* Select the Channel 12, related to the interruption on AscLin RX */
    cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_RX;
    /* Address of the UART RX FIFO */
    cfg.sourceAddress = (uint32) &g_ascHandle.asclin->RXDATA.U;
    cfg.destinationAddress = (uint32)&g_ascRxBuffer.buffer[0];

    /* DMA中断部分(如果采用环形缓存区,就没必要开启了哈)  */
    //cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    //cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    //cfg.channelInterruptPriority = INTPRIO_DMA_RX;
    /* Interrupt service provider */
    //cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;

    IfxDma_Dma_initChannel(&g_rxchn, &cfg);

    ringbuf_init(&g_ascRxBuffer);
    ringbuf_init(&g_ascTxBuffer);
}

void send_data(char* g_txData,Ifx_UReg_32Bit len)   //发送数据--指定长度大小
{
    Ifx_DMA_CH *channel = g_txchn.channel;

    IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    channel->SADR.U   = (uint32)g_txData;
    channel->CHCFGR.B.TREL     = len;
    IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    IfxDma_Dma_startChannelTransaction(&g_txchn);
   //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
}

void send_1s_pack(void) //对上面 send_data封装而已
{
    send_data((char *)g_txData,g_count);
    waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}

void recive_data(void)    //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)
{
    Ifx_DMA_CH *channel = g_txchn.channel;
    IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    channel->SADR.U   = (uint32)&g_ascRxBuffer.buffer[0];
    channel->CHCFGR.B.TREL     = 16;
    IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    IfxDma_Dma_startChannelTransaction(&g_txchn);
    //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
    waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}

void ceshi( uint8_t *str,uint16_t length)
{
    ringbuf_get(&g_ascRxBuffer,str,length);
    //ringbuf_put(&g_ascTxBuffer,str,length);
}

最终mydma.h代码:

c 复制代码
#ifndef __MYDMA_H_
#define __MYDMA_H_
#include "stdint.h"

void init_asclin_uart(void);                    /* Initialization function                                          */
void init_dma(void);

void send_data(char* g_txData,Ifx_UReg_32Bit len);   //发送数据--指定长度大小
void send_1s_pack(void);
void recive_data(void) ;   //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)


void ceshi( uint8_t *str,uint16_t length);

#endif

最终的main.c

c 复制代码
#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"


#include "mydma.h"
#include "Blinky_LED.h"
#include "Interrupt.h"
#include "pwm.h"


IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;

uint8_t str[18] = {'\0'};

void core0_main(void)
{
    IfxCpu_enableInterrupts();
    
    /* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
     * Enable the watchdogs and service them periodically if it is required
     */
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());
    
    /* Wait for CPU sync event */
    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
    
    initLED();
    initGtmTom();
    init_asclin_uart();
    init_dma();
    bsp_pwm_init();
    while(1)
    {
        ceshi(str,10);
    }
}

测试读写环形缓存区

Debug调试:

读固定的10个字节

串口发送1234567890!@#$%^ 这16个字节,串口DMA直接将数据搬运到g_ascRxBuffer的buffer里面。

从g_ascRxBuffer的buffer里面接收一次10个字节到str里面:

从g_ascRxBuffer的buffer里面,接收第二次10个字节到str里面:继续往后接收10个字节,刚好是!@#$%^(6个字节)+1234(四个字节)


从g_ascRxBuffer的buffer里面,接收第三次10个字节到str里面:继续往后接收10个字节,刚好是56789!@#$

注意:

串口接收,不需要人为干涉,因为串口接收数据寄存器一旦有数据,就会硬件触发DMA搬运数据到g_ascRxBuffer的buffer里面,并且DMA开启的还是自增加循环模式,这里注意要及时读取数据,否则会出现数据还没来及读走,串口又接收到数据,把未处理的数据覆盖掉了。

写固定的10个字节

代码修改一点:

c 复制代码
void ceshi( uint8_t *str,uint16_t length)
{
    //ringbuf_get(&g_ascRxBuffer,str,length);
    ringbuf_put(&g_ascTxBuffer,str,length);
}

主函数修改:

c 复制代码
char *ptr = "1234567890";

    while(1)
    {
        ceshi(ptr,10);
    }

第一次发送ptr的10个字节

可以看到ptr放入到了g_ascTxBuffer的buffer里面:

第二次发送ptr的10个字节

又将ptr的10个字节放入到g_ascTxBuffer的buffer里面,因buffer是16字节的,开启了DMA自增循环,因此将123456放入尾巴,再后面的内容7890从开头重新覆盖。

第三次发送ptr的10个字节

继7890后面又写入了1234567890

注意:

这些数据都是写入到g_ascTxBuffer的buffer里面,串口并不能收到数据的,因为不是直接写入串口发送数据寄存器的,还需要DMA来将buffer里面数据搬运到串口发送数据寄存器。需要设置DMA一次搬运数量,以及开启DMA搬运启动。


至此:实现了读取指定长度数据(但是不能控制只读取没处理的数据,说人话,就是接收多少,就读取多少,不是一股脑的读),实现了写入指定长度数据到buffer里面,并没有实现正真意义上的发送 ,需要设置一次DMA搬运数量,并且开启DMA搬运,现象我就不演示了。


实现环形利用缓存区,并且实现串口不定长接收/指定长发送

基于上面完成的定长读,以及未完成的指定长写,进行继续实现,这里就借助环形缓存区结构体里面的get/put索引完成。

在mydma.c 添加实现读/写函数:

c 复制代码
uint16_t get_uart_rxbuf(uint8_t *buf)
{
    uint32_t rx_dma_add = IfxDma_getChannelDestinationAddress(g_rxchn.dma,DMA_CHANNEL_RX);
    uint32_t rx_dma_index =  rx_dma_add - (uint32_t)&g_ascRxBuffer.buffer[0];
    uint32_t rx_buf_index = g_ascRxBuffer.put_index % CBUF_MAX_SIZE;
    uint16_t rx_cnt = 0;

    if(rx_dma_index < rx_buf_index){   //DMA指针在前,说明接收到数据已经超过尾巴,回到头部
        g_ascRxBuffer.put_index += ((CBUF_MAX_SIZE - rx_buf_index)+ rx_dma_index); //CBUF_MAX_SIZE - rx_buf_index 是 到尾部 长度 + rx_dma_index 是 距离头部的长度
    }else{                      //DMA指针在后面  说明 接收到数据 还未到一圈
        g_ascRxBuffer.put_index += (rx_dma_index - rx_buf_index);
    }

    if((rx_ok == 1)&&(rx_dma_index == rx_buf_index)){
        rx_ok =0;
        g_ascRxBuffer.put_index +=  CBUF_MAX_SIZE;
    }

    rx_cnt = (uint16_t)ringbuf_Len(&g_ascRxBuffer);

    if(rx_cnt>0){  //要处理长度大于0 才进行读取
        ringbuf_get(&g_ascRxBuffer,buf,rx_cnt);
    }

    return rx_cnt;
}


void uart_send_pack(uint8_t *buf,int16_t len)
{
    uint32_t tx_dma_add   =  IfxDma_getChannelSourceAddress(g_txchn.dma,DMA_CHANNEL_TX);
    uint32_t tx_dma_index =  tx_dma_add - (uint32_t)&g_ascTxBuffer.buffer[0];
    uint32_t tx_buf_index =  g_ascTxBuffer.get_index % CBUF_MAX_SIZE;
	uint16_t tx_cnt = 0;
    Ifx_DMA_CH *channel = g_txchn.channel;

    ringbuf_put(&g_ascTxBuffer,buf,len);
    if(tx_buf_index == tx_dma_index){  //DMA 和 处理索引 一致说明上次DMA搬运完成了,也就是发送完毕
        tx_cnt =  (uint16_t)ringbuf_Len(&g_ascTxBuffer);
        if(tx_cnt>0){
            IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            channel->CHCFGR.B.TREL     = tx_cnt;//IfxDma_Dma_setChannelTransferCount(&g_txchn, tx_cnt);
            IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            IfxDma_Dma_startChannelTransaction(&g_txchn);
           //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
            g_ascTxBuffer.get_index += tx_cnt;
        }
    }else{
        g_ascTxBuffer.get_index  =  g_ascTxBuffer.get_index-  tx_cnt +   tx_dma_index;  //保持get_index 与DMA同步,其实就是上次DMA没有搬运完成,避免数据错乱,就是把总数据长度+dma数据长度而已
    }
}

另外需要开启接收完成中断,编写对应的服务函数:

c 复制代码
    /* DMA中断部分(因为要实现恰好接收环形缓存区长度的数据,借助中断完成的)  */
    cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    cfg.channelInterruptPriority = INTPRIO_DMA_RX;
    /* Interrupt service provider */
    cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;

DMA接收完成中断服务函数:

c 复制代码
static uint8_t rx_ok = 0;  //接收完成标志位

IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
    rx_ok =1;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}

代码分析:

不定长接收部分:

c 复制代码
uint16_t get_uart_rxbuf(uint8_t *buf)
{
    uint32_t rx_dma_add = IfxDma_getChannelDestinationAddress(g_rxchn.dma,DMA_CHANNEL_RX);
    uint32_t rx_dma_index =  rx_dma_add - (uint32_t)&g_ascRxBuffer.buffer[0];
    uint32_t rx_buf_index = g_ascRxBuffer.put_index % CBUF_MAX_SIZE;
    uint16_t rx_cnt = 0;

    if(rx_dma_index < rx_buf_index){   //DMA指针在前,说明接收到数据已经超过尾巴,回到头部
        g_ascRxBuffer.put_index += ((CBUF_MAX_SIZE - rx_buf_index)+ rx_dma_index); //CBUF_MAX_SIZE - rx_buf_index 是 到尾部 长度 + rx_dma_index 是 距离头部的长度
    }else{                      //DMA指针在后面  说明 接收到数据 还未到一圈
        g_ascRxBuffer.put_index += (rx_dma_index - rx_buf_index);
    }

    if((rx_ok == 1)&&(rx_dma_index == rx_buf_index)){
        rx_ok =0;
        g_ascRxBuffer.put_index +=  CBUF_MAX_SIZE;
    }

    rx_cnt = (uint16_t)ringbuf_Len(&g_ascRxBuffer);

    if(rx_cnt>0){  //要处理长度大于0 才进行读取
        ringbuf_get(&g_ascRxBuffer,buf,rx_cnt);
    }

    return rx_cnt;
}

首先获取接收dma指针位置,注意使用的时获取dma接收通道目的地地址哈:

c 复制代码
uint32_t rx_dma_add = IfxDma_getChannelDestinationAddress(g_rxchn.dma,DMA_CHANNEL_RX);

计算dma指针相对于环形缓存区头部位置(dma接收的长度):

c 复制代码
uint32_t rx_dma_index =  rx_dma_add - (uint32_t)&g_ascRxBuffer.buffer[0];

获取已读取数据位置(已读取数据长度):

c 复制代码
uint32_t rx_buf_index = g_ascRxBuffer.put_index % CBUF_MAX_SIZE;

计算真正要处理数据长度:

c 复制代码
uint16_t rx_cnt = 0;

判断
如果dma指针位置在前面,已读取数据位置在后面,说明串口又接收了从已读取位置,到dma位置那么长的数据,形成了回环,记录到g_ascRxBuffer.put_index里面,g_ascRxBuffer.put_index += 环形长度➖已读位置 + dam数据长度,这个g_ascRxBuffer.put_index代表DMA又搬运多少数据到数组里面了。

如果已读数据位置在前面,dma指针位置在后面 或者 两者相等,说明串口接收到dma位置到已读位置长度的数据,未回环,记录到g_ascRxBuffer.put_index里面,g_ascRxBuffer.put_index += dma数据长度➖已读长度。

但这里两者相等有两种情况,第一种串口确实没有数据,也就是DMA没往环形缓存区里面搬运数据,自然g_ascRxBuffer.put_index += 0是合理的 。但是有一种情况就是上面所说的串口恰好接收了环形缓存区长度的数据,DMA搬运了和环形缓存区一样长度的DMA指针恰好环绕一圈回来了,此时g_ascRxBuffer.put_index += 0,就是说DMA明明搬运了一圈,但是更新长度是0,所以需要开启DMA接收完成中断,判断接收完成标志位的同时判断dma数据位置和已读数据位置是否相同,一旦接收完成且 还相等,那就是DMA恰好搬运环形缓存区大小数据,DMA指针绕了一圈又回来了!!!!清除此次接收完成标志位,并且更新g_ascRxBuffer.put_index += CBUF_MAX_SIZE,这个更新长度恰好是环形缓存区的长度。

另外g_ascRxBuffer.get_index 代表 已从buffer里面 读出来多少长度的数据。

c 复制代码
    if(rx_dma_index < rx_buf_index){   //DMA指针在前,说明接收到数据已经超过尾巴,回到头部
        g_ascRxBuffer.put_index += ((CBUF_MAX_SIZE - rx_buf_index)+ rx_dma_index); //CBUF_MAX_SIZE - rx_buf_index 是 到尾部 长度 + rx_dma_index 是 距离头部的长度
    }else{                      //DMA指针在后面  说明 接收到数据 还未到一圈
        g_ascRxBuffer.put_index += (rx_dma_index - rx_buf_index);
    }

然后获取 真正需要读取长度,拿更新的DMA搬运的长度➖之前已读取过的长度(调用ringbuf_Len函数即可)

c 复制代码
    rx_cnt = (uint16_t)ringbuf_Len(&g_ascRxBuffer);

最后判断真正需要读取长度是否大于0,如果大于,直接调用ringbuf_get 开始读取。并返回本次读取长度

c 复制代码
    if(rx_cnt>0){  //要处理长度大于0 才进行读取
        ringbuf_get(&g_ascRxBuffer,buf,rx_cnt);
    }
    return rx_cnt;

定长发送部分:

c 复制代码
void uart_send_pack(uint8_t *buf,int16_t len)
{
    uint32_t tx_dma_add   =  IfxDma_getChannelSourceAddress(g_txchn.dma,DMA_CHANNEL_TX);
    uint32_t tx_dma_index =  tx_dma_add - (uint32_t)&g_ascTxBuffer.buffer[0];
    uint32_t tx_buf_index =  g_ascTxBuffer.get_index % CBUF_MAX_SIZE;
    uint16_t tx_cnt = 0;
    Ifx_DMA_CH *channel = g_txchn.channel;

    ringbuf_put(&g_ascTxBuffer,buf,len);
    if(tx_buf_index == tx_dma_index){  //DMA 和 处理索引 一致说明上次DMA搬运完成了,也就是发送完毕
        tx_cnt =  (uint16_t)ringbuf_Len(&g_ascTxBuffer);
        if(tx_cnt>0){
            IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            channel->CHCFGR.B.TREL     = tx_cnt; // IfxDma_Dma_setChannelTransferCount(&g_txchn, tx_cnt);
            IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            IfxDma_Dma_startChannelTransaction(&g_txchn);
           //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
            g_ascTxBuffer.get_index += tx_cnt;
        }
    }else{
        g_ascTxBuffer.get_index  =  g_ascTxBuffer.get_index-  tx_cnt +   tx_dma_index;  //保持get_index 与DMA同步,其实就是上次DMA没有搬运完成,避免数据错乱,就是把总数据长度+dma数据长度而已
    }
}

首先获取发送dma指针位置,注意使用的时获取发送dma通道源地址哈:

c 复制代码
uint32_t tx_dma_add   =  IfxDma_getChannelSourceAddress(g_txchn.dma,DMA_CHANNEL_TX);

计算dma指针相对于环形缓存区头部位置(dma发送的长度):

c 复制代码
uint32_t tx_dma_index =  tx_dma_add - (uint32_t)&g_ascTxBuffer.buffer[0];

获取已发送数据位置(已发送数据长度):

c 复制代码
uint32_t tx_buf_index =  g_ascTxBuffer.get_index % CBUF_MAX_SIZE;

计算真正要处理数据长度:

c 复制代码
uint16_t tx_cnt = 0;

DMA的发送通道,因为后面要设置DMA发送通道一次搬运多少数量:

c 复制代码
Ifx_DMA_CH *channel = g_txchn.channel;

直接写入指定长度的数据

c 复制代码
ringbuf_put(&g_ascTxBuffer,buf,len);

判断
如果DMA发送位置等于已发送数据位置,说明上次DMA搬运完成,也就是发送完毕,然后计算本次真正要发送的数据长度,保存到tx_cnt里面,一旦真正要发送的长度大于0,先关闭DMA通道,设置需要一次DMA搬运tx_cnt的数据,在启动DMA通道,最后开启DMA搬运,再更新DMA已搬运数量,到g_ascTxBuffer.get_index 里面。否则的话保持get_index 与DMA同步,其实就是DMA上次未搬运完成,避免数据错乱,可以理解为发送总长度减区已发送位置,刚好是环形数组的头,再加上 DMA相对于环形数组头偏移位置。此时DMA指针和get_index就保持一致了。

这里g_ascTxBuffer.get_index 代表 DMA已经搬运多少数据,也就是发送多少数据,g_ascTxBuffer.put_index 代表总共需要搬运多少数据,总共需要搬运的数据减去DMA已经搬运的数据,就是真正要发送的数据。

c 复制代码
    if(tx_buf_index == tx_dma_index){  //DMA 和 处理索引 一致说明上次DMA搬运完成了,也就是发送完毕
        tx_cnt =  (uint16_t)ringbuf_Len(&g_ascTxBuffer);
        if(tx_cnt>0){
            IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            channel->CHCFGR.B.TREL     = tx_cnt; // IfxDma_Dma_setChannelTransferCount(&g_txchn, tx_cnt);
            IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            IfxDma_Dma_startChannelTransaction(&g_txchn);
           //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
            g_ascTxBuffer.get_index += tx_cnt;
        }
    }else{
        g_ascTxBuffer.get_index  =  g_ascTxBuffer.get_index-  tx_buf_index +   tx_dma_index;   //保持get_index 与DMA同步,其实就是上次DMA没有搬运完成,避免数据错乱,就是把总数据长度+dma数据长度而已
    }

开启DMA接收完成中断部分:

c 复制代码
    /* DMA中断部分(如果采用环形缓存区,就没必要开启了哈)  */
    cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    cfg.channelInterruptPriority = INTPRIO_DMA_RX;
    /* Interrupt service provider */
    cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;


static uint8_t rx_ok = 0;
IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
    rx_ok =1;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}

最终关于串口的整合代码:

包含了之前章节的代码功能,都在其中。

ringbuf.c .h

.H

c 复制代码
#ifndef __RINGBUF_H_
#define __RINGBUF_H_

#include "stdint.h"

#define CBUF_MAX_SIZE 16

typedef struct cbuf
{
    uint8_t buffer[CBUF_MAX_SIZE];
    uint64_t put_index; //uint32_t以460800波特率不停的通信24小时就溢出,故换为uint64_t
    uint64_t get_index;
    uint32_t buf_actual_size; //缓冲区大小,最大不超过CBUF_MAX_SIZE


}ringbuf;


void ringbuf_init(ringbuf *cbuff);
uint32_t ringbuf_Len(ringbuf *cbuff);
uint32_t ringbuf_put(ringbuf *rb, uint8_t *str,uint16_t length);
uint32_t ringbuf_get(ringbuf *rb, uint8_t *str,uint16_t length);


#endif

.C

c 复制代码
#include "ringbuf.h"
#include "stddef.h"
#include <string.h>


void ringbuf_init(ringbuf *cbuff)
{
    cbuff->get_index = 0;
    cbuff->put_index = 0;

    cbuff->buf_actual_size = CBUF_MAX_SIZE; //初始化时缓冲区设置为最大
    memset(cbuff->buffer,'\0',CBUF_MAX_SIZE);
}


uint32_t ringbuf_Len(ringbuf *cbuff)
{
    return (cbuff->put_index - cbuff->get_index) % (cbuff->buf_actual_size+1);//0-buf_actual_size
}



uint32_t ringbuf_put(ringbuf *rb, uint8_t *str,uint16_t length)
{

    if (  (str == NULL) || (length == 0)) {
        return 0;
    }

    uint32_t size = rb->buf_actual_size -ringbuf_Len(rb)-length;

    if(size < 0 ){//没有足够的空间了
        return 0;
    }

    uint32_t put_pos = rb->put_index % rb->buf_actual_size; //定位投放位置

    if((rb->buf_actual_size - put_pos) >= length){ //buffer中剩余空间 比 数据长度 大 (可以放下) 直接放入
        (void *)memcpy(&rb->buffer[put_pos], &str[0], length);  //从put_pos位置 往里面放
    }else{                                            //剩余空间 比 数据长度 小 (不可放下) 需要拆成俩段 放入
        uint16_t a_room  = rb->buf_actual_size - put_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;  //后半段的 长度大小

        (void *)memcpy(&rb->buffer[put_pos], &str[0], a_room);  //从put_pos位置 将a_room长度的str(str前半段)放入buffer里面
        (void *)memcpy(&rb->buffer[0], &str[a_room], b_room);  //从头部位置 将b_room长度的str(str后半段)放入buffer里面
    }

    rb->put_index += length;

    return length;
}


uint32_t ringbuf_get(ringbuf *rb,uint8_t *str,uint16_t length)
{
    if ((rb == NULL) || (length == 0) || (length > rb->buf_actual_size) ) {
        return 0;
    }
    uint32_t get_pos = ((rb->get_index) % (rb->buf_actual_size));


    if((rb->buf_actual_size - get_pos) >= length){       //buffer中剩余未拷贝的数据长度 大于 需要拷贝的长度(可以直接拷贝)
        (void *)memcpy(&str[0], &rb->buffer[get_pos],length);  //从get_pos位置 拷贝 length长度 到str里面
    }else{                                          //剩余未拷贝的数据长度 小于  需要拷贝的长度(不可直接拷贝) 需要分段拷贝
        uint16_t a_room  = rb->buf_actual_size - get_pos;  //前半段的 长度大小
        uint16_t b_room  = length - a_room;             //后半段的 长度大小

        (void *)memcpy(&str[0],&rb->buffer[get_pos], a_room);  //从put_pos位置 往里面放a_room长度的str(str前半段)
        (void *)memcpy(&str[a_room],&rb->buffer[0], b_room);  //从头部位置 往里面放b_room长度的str(str后半段)
    }

    rb->get_index += length;

    return length;

}

mydma.c .h

.H

c 复制代码
#ifndef __MYDMA_H_
#define __MYDMA_H_
#include "stdint.h"

void init_asclin_uart(void);                    /* Initialization function                                          */
void init_dma(void);

void send_data(char* g_txData,Ifx_UReg_32Bit len);   //发送数据--指定长度大小
void send_1s_pack(void);
void recive_data(void) ;   //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)


void ceshi( uint8_t *str,uint16_t length);

uint16_t get_uart_rxbuf(uint8_t *buf);
void uart_send_pack(uint8_t *buf,int16_t len);

#endif

.C

c 复制代码
#include "IfxAsclin_Asc.h"

#include "IfxDma_Dma.h"
#include "IfxDma.h"

#include "Bsp.h"
//#include "string.h"

#include "ringbuf.h"

/*********************************************************************************************************************/
/*------------------------------------------------------Macros-------------------------------------------------------*/
/*********************************************************************************************************************/
#define UART_BAUDRATE           460800                                  /* UART baud rate in bit/s                  */

#define UART_PIN_RX             IfxAsclin2_RXE_P33_8_IN                 /* UART receive port pin                    */
#define UART_PIN_TX             IfxAsclin2_TX_P33_9_OUT                 /* UART transmit port pin                   */

#define DMA_CHANNEL_RX             INTPRIO_ASCLIN2_RX
#define DMA_CHANNEL_TX             INTPRIO_ASCLIN2_TX

IfxDma_Dma_Channel g_rxchn;                                             /* DMA channel handle                       */
IfxDma_Dma_Channel g_txchn;

/* Definition of the interrupt priorities */
#define INTPRIO_ASCLIN2_TX      11                                       /* Triggered when AscLin transmits         */
#define INTPRIO_ASCLIN2_RX      12                                       /* Triggered when AscLin receives          */

#define INTPRIO_DMA_TX          13                          /* Triggered when a DMA transaction is finished         */
#define INTPRIO_DMA_RX          14                          /* Triggered when a DMA transaction is finished         */

#define UART_RX_BUFFER_SIZE     16                                      /* Definition of the receive buffer size    */
#define UART_TX_BUFFER_SIZE     16                                      /* Definition of the transmit buffer size   */


/* Declaration of the ASC handle */
static IfxAsclin_Asc g_ascHandle;

/* Declaration of the FIFOs parameters */
_Alignas(16) static  ringbuf g_ascTxBuffer;     //只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!
_Alignas(16) static  ringbuf g_ascRxBuffer;

/* Definition of txData  */
_Alignas(16) uint8 g_txData[] = "Hello World!";//只要开启了自增+循环,必须保证与循环范围保持一致!!!!!!

/* Size of the message */
Ifx_UReg_32Bit g_count = sizeof(g_txData)-1;


//int txcnt = 0;
static uint8_t rx_ok = 0;
IFX_INTERRUPT(prio_DMA_RX, 0, INTPRIO_DMA_RX);
void prio_DMA_RX(void)
{
    rx_ok =1;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_rxchn.channelId);
}

/*
IFX_INTERRUPT(prio_DMA_TX, 0, INTPRIO_DMA_TX);
void prio_DMA_TX(void)
{
    txcnt++;
    IfxDma_clearChannelInterrupt(&MODULE_DMA, g_txchn.channelId);
}
*/


/* This function initializes the ASCLIN UART module */
void init_asclin_uart(void)
{
    /* Initialize an instance of IfxAsclin_Asc_Config with default values */
    IfxAsclin_Asc_Config ascConfig;
    IfxAsclin_Asc_initModuleConfig(&ascConfig, &MODULE_ASCLIN2);

    /* Set the desired baud rate */
    ascConfig.baudrate.baudrate = UART_BAUDRATE;

    /* ISR priorities and interrupt target */
    ascConfig.interrupt.txPriority = INTPRIO_ASCLIN2_TX;
    ascConfig.interrupt.rxPriority = INTPRIO_ASCLIN2_RX;
    ascConfig.interrupt.typeOfService = IfxSrc_Tos_cpu0 ;

    /* FIFO configuration */  //没用到串口接收/发送的缓存可以不绑定,直接删除即可(如出现问题,可以直接删除)
    ascConfig.txBuffer = &g_ascTxBuffer.buffer[0];
    ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
    ascConfig.rxBuffer = &g_ascRxBuffer.buffer[0];
    ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;

    /* Pin configuration */
    const IfxAsclin_Asc_Pins pins =
    {
        NULL_PTR,       IfxPort_InputMode_pullUp,     /* CTS pin not used */
        &UART_PIN_RX,   IfxPort_InputMode_pullUp,     /* RX pin           */
        NULL_PTR,       IfxPort_OutputMode_pushPull,  /* RTS pin not used */
        &UART_PIN_TX,   IfxPort_OutputMode_pushPull,  /* TX pin           */
        IfxPort_PadDriver_cmosAutomotiveSpeed1
    };
    ascConfig.pins = &pins;

    IfxAsclin_Asc_initModule(&g_ascHandle, &ascConfig); /* Initialize module with above parameters */

    /* Modification of the TOS for the Rx related interruption */
    /* Change from CPU0 (previously defined above) to DMA */
    volatile Ifx_SRC_SRCR *src;
    src = IfxAsclin_getSrcPointerTx(ascConfig.asclin);

    /* Assign DMA as Service Provider when INTPRIO_ASCLIN2_TX is triggered */
    IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_TX);
    IfxAsclin_enableTxFifoFillLevelFlag(ascConfig.asclin, TRUE);
    IfxSrc_enable(src);

    /* Modification of the TOS for the Rx related interruption */
    /* Change from CPU0 (previously defined above) to DMA */
    src = IfxAsclin_getSrcPointerRx(ascConfig.asclin);
    /* Assign DMA as Service Provider when INTPRIO_ASCLIN2_RX is triggered */
    IfxSrc_init(src, IfxSrc_Tos_dma, INTPRIO_ASCLIN2_RX);
    IfxAsclin_enableRxFifoFillLevelFlag(ascConfig.asclin, TRUE);
    IfxSrc_enable(src);
}

/* This function is called from main in order to initialize the DMA module */


void init_dma(void)
{
    /* Initialize an instance of IfxDma_Dma_Config with default values */
    IfxDma_Dma_Config dmaConfig;
    IfxDma_Dma_initModuleConfig(&dmaConfig, &MODULE_DMA);

    /* Initialize module */
    IfxDma_Dma dma;
    IfxDma_Dma_initModule(&dma, &dmaConfig);

    /* Initial configuration for all channels */
    IfxDma_Dma_ChannelConfig cfg;
    IfxDma_Dma_initChannelConfig(&cfg, &dma);

    /* Following configuration is used by the DMA channel */
    cfg.moveSize = IfxDma_ChannelMoveSize_8bit;
    cfg.blockMode = IfxDma_ChannelMove_1;

    /*********************************************** TX部分**************************************************************/

    cfg.transferCount = 0;//一次触发搬运多少个字节数据(后面写入的时候会修改)
    /* DMA completes a full transaction on requests */
    cfg.requestMode = IfxDma_ChannelRequestMode_oneTransferPerRequest;  //一个请求触发一个单一DMA传输 即请求启动单个事务

    /* DMA as Interrupt Service Provider */
    cfg.hardwareRequestEnabled = TRUE;                         //  使能UART发送中断触发Dma(硬件触发)
    /* DMA channel stays enabled after one request */
    cfg.operationMode = IfxDma_ChannelOperationMode_single;   //DMA单次搬运

    /*************** Source and destination addresses ***************/
    cfg.sourceCircularBufferEnabled = TRUE;                           //开启源地址自增
    cfg.sourceAddressIncrementStep = IfxDma_ChannelIncrementStep_1;  //源地址自增加+1
    cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_16; //源地址16字节循环

    cfg.destinationCircularBufferEnabled = TRUE;                     //开启目的地址自增
    cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_none; //目的地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE

    /*************** Channel specific configurations ***************/
    /* Select the Channel 11, related to the interruption on AscLin TX */
    cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_TX;
    /* Address of the UART TX FIFO */
    cfg.sourceAddress = (uint32)&g_ascTxBuffer.buffer[0];
    cfg.destinationAddress = (uint32) &g_ascHandle.asclin->TXDATA.U;

    /*  DMA中断部分(如果采用环形缓存区,就没必要开启了哈)           */
    //cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    //cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    //cfg.channelInterruptPriority = INTPRIO_DMA_TX;
    /* Interrupt service provider */
    ///cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;

    IfxDma_Dma_initChannel(&g_txchn, &cfg);


    /********************************************************* RX部分***********************************************************/
    cfg.transferCount = 1;   //一次触发搬运多少个字节数据
    /* DMA completes a full transaction on requests */
    cfg.requestMode = IfxDma_ChannelRequestMode_completeTransactionPerRequest;  //一个请求触发一个完整事务 即请求启动完整的事务

    /* DMA as Interrupt Service Provider */
    cfg.hardwareRequestEnabled = TRUE;                         // UART接收中断触发Dma

    /* DMA channel stays enabled after one request */
    cfg.operationMode = IfxDma_ChannelOperationMode_continuous; // DMA连续搬运模式

    /*************** Source and destination addresses ***************/
    cfg.sourceCircularBufferEnabled = TRUE;                 //源地址自增
    cfg.sourceAddressCircularRange = IfxDma_ChannelIncrementCircular_none;//源地址循环范围为0(就是原地不动,即使上面开启了也是在原地踏步)等同于上面FALSE

    cfg.destinationCircularBufferEnabled = TRUE;
    cfg.destinationAddressCircularRange = IfxDma_ChannelIncrementCircular_16;

    /*************** Channel specific configurations ***************/
    /* Select the Channel 12, related to the interruption on AscLin RX */
    cfg.channelId = (IfxDma_ChannelId) DMA_CHANNEL_RX;
    /* Address of the UART RX FIFO */
    cfg.sourceAddress = (uint32) &g_ascHandle.asclin->RXDATA.U;
    cfg.destinationAddress = (uint32)&g_ascRxBuffer.buffer[0];

    /* DMA中断部分(如果采用环形缓存区,就没必要开启了哈)  */
    cfg.channelInterruptEnabled = TRUE;
    /* DMA triggers an interrupt once the full transaction is done */
    cfg.channelInterruptControl = IfxDma_ChannelInterruptControl_thresholdLimitMatch;
    /* Priority of the channel interrupt trigger */
    cfg.channelInterruptPriority = INTPRIO_DMA_RX;
    /* Interrupt service provider */
    cfg.channelInterruptTypeOfService = IfxSrc_Tos_cpu0;

    IfxDma_Dma_initChannel(&g_rxchn, &cfg);

    ringbuf_init(&g_ascRxBuffer);
    ringbuf_init(&g_ascTxBuffer);
}

void send_data(char* g_txData,Ifx_UReg_32Bit len)   //发送数据--指定长度大小
{
    Ifx_DMA_CH *channel = g_txchn.channel;

    IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    channel->SADR.U   = (uint32)g_txData;
    channel->CHCFGR.B.TREL     = len;
    IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    IfxDma_Dma_startChannelTransaction(&g_txchn);
   //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
}

void send_1s_pack(void) //对上面 send_data封装而已
{
    send_data((char *)g_txData,g_count);
    waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}

void recive_data(void)    //发送接收到的数据缓存区的数据--需要指定数据长度(一般和整个接收缓存数组长度一致)
{
    Ifx_DMA_CH *channel = g_txchn.channel;
    IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    channel->SADR.U   = (uint32)&g_ascRxBuffer.buffer[0];
    channel->CHCFGR.B.TREL     = 16;
    IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
    IfxDma_Dma_startChannelTransaction(&g_txchn);
    //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1; //发送完成设置,用于通知其它程序的,可以不设置
    waitTime(IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, 1000));
}

void ceshi( uint8_t *str,uint16_t length)
{
    //ringbuf_get(&g_ascRxBuffer,str,length);
    ringbuf_put(&g_ascTxBuffer,str,length);
}


uint16_t get_uart_rxbuf(uint8_t *buf)
{
    uint32_t rx_dma_add = IfxDma_getChannelDestinationAddress(g_rxchn.dma,DMA_CHANNEL_RX);
    uint32_t rx_dma_index =  rx_dma_add - (uint32_t)&g_ascRxBuffer.buffer[0];
    uint32_t rx_buf_index = g_ascRxBuffer.put_index % CBUF_MAX_SIZE;
    uint16_t rx_cnt = 0;

    if(rx_dma_index < rx_buf_index){   //DMA指针在前,说明接收到数据已经超过尾巴,回到头部
        g_ascRxBuffer.put_index += ((CBUF_MAX_SIZE - rx_buf_index)+ rx_dma_index); //CBUF_MAX_SIZE - rx_buf_index 是 到尾部 长度 + rx_dma_index 是 距离头部的长度
    }else{                      //DMA指针在后面  说明 接收到数据 还未到一圈
        g_ascRxBuffer.put_index += (rx_dma_index - rx_buf_index);
    }

    if((rx_ok == 1)&&(rx_dma_index == rx_buf_index)){
        rx_ok =0;
        g_ascRxBuffer.put_index +=  CBUF_MAX_SIZE;
    }

    rx_cnt = (uint16_t)ringbuf_Len(&g_ascRxBuffer);

    if(rx_cnt>0){  //要处理长度大于0 才进行读取
        ringbuf_get(&g_ascRxBuffer,buf,rx_cnt);
    }

    return rx_cnt;
}


void uart_send_pack(uint8_t *buf,int16_t len)
{
    uint32_t tx_dma_add   =  IfxDma_getChannelSourceAddress(g_txchn.dma,DMA_CHANNEL_TX);
    uint32_t tx_dma_index =  tx_dma_add - (uint32_t)&g_ascTxBuffer.buffer[0];
    uint32_t tx_buf_index =  g_ascTxBuffer.get_index % CBUF_MAX_SIZE;
    uint16_t tx_cnt = 0;
    Ifx_DMA_CH *channel = g_txchn.channel;

    ringbuf_put(&g_ascTxBuffer,buf,len);
    if(tx_buf_index == tx_dma_index){  //DMA 和 处理索引 一致说明上次DMA搬运完成了,也就是发送完毕
        tx_cnt =  (uint16_t)ringbuf_Len(&g_ascTxBuffer);
        if(tx_cnt>0){
            IfxDma_disableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            channel->CHCFGR.B.TREL     = tx_cnt; // IfxDma_Dma_setChannelTransferCount(&g_txchn, tx_cnt);
            IfxDma_enableChannelTransaction(&MODULE_DMA, g_txchn.channelId);
            IfxDma_Dma_startChannelTransaction(&g_txchn);
           //g_ascHandle.asclin->FLAGSSET.B.TFLS = 1;  //发送完成设置,用于通知其它程序的,可以不设置
            g_ascTxBuffer.get_index += tx_cnt;
        }
    }else{
        g_ascTxBuffer.get_index  =  g_ascTxBuffer.get_index-  tx_buf_index +   tx_dma_index;   //保持get_index 与DMA同步,其实就是上次DMA没有搬运完成,避免数据错乱,就是把总数据长度+dma数据长度而已
    }
}

main.c

c 复制代码
#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"


#include "mydma.h"
#include "Blinky_LED.h"
#include "Interrupt.h"
#include "pwm.h"


IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;


uint8_t str[18] = {'\0'};
uint16_t cnt = 0;

char *ptr = "1234567890";

void core0_main(void)
{
    IfxCpu_enableInterrupts();
    
    /* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
     * Enable the watchdogs and service them periodically if it is required
     */
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());
    
    /* Wait for CPU sync event */
    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
    
    initLED();
    initGtmTom();
    init_asclin_uart();
    init_dma();
    bsp_pwm_init();
    while(1)
    {
        cnt = get_uart_rxbuf((uint8_t*)str);
        //uart_send_pack((uint8_t *)ptr,9);
        //uart_send_pack((uint8_t *)ptr,10);
    }
}

注意事项:

1.定义环形缓存区结构体时buffer数组必须要放在第一位,原因:结构体地址分配,默认字节对齐规则,第一个元素能保持和定义结构体变量时,指定的结构体变量地址对齐字节一致。

2.定义环形缓存区结构体变量,必须要字节对齐,比如需要16字节,那就使用_Alignas(16),需要1024字节对齐就使用_Alignas(1024).

3.需要开启DMA接收完成中断。

4.初始化串口配置的时候不需要绑定FIFO缓存区,因为不用官方给的读写函数。

5.初始化串口配置是,默认参数会乱码,需要设置过采因子为8,采样位置为3。(之前用的是过采因子16,采样位置是8,实际情况可以根据需求更改)。

6.使用环形缓存区时,需要初始化一下。

7.算法还是可以更近一步优化的,目前水平先到这里,这个实现方法,还是有精妙之处的,多多体会。

读写实验:

读测试:

主函数调用:

c 复制代码
cnt = get_uart_rxbuf((uint8_t*)str);

debug单步调试,串口发送1234567890,10个字节数据,DMA立马搬运到环形缓存区buffer里面,

然后再往下一步,get_uart_rxbuf执行,然后读取10个字节到自己str数组里面了。

串口再继续发送!@#$%888666,DMA将数据搬运到buffer里面,可想而知肯定有数据覆盖,但是上一次数据已经被走了,覆盖就覆盖呗。

再往下,执行了get_uart_rxbuf,然后读取11个字节到自己str数组里面了


再测试一下发送和环形缓存区一样大小的数据,这里我设置的是16字节,我发送1234567890123456可以看到可以完完全全可以接收的,至此任意字节接收完美收官

写测试:

主函数调用:

c 复制代码
char *ptr = "1234567890";
    while(1)
    {
        //cnt = get_uart_rxbuf((uint8_t*)str);
        uart_send_pack((uint8_t *)ptr,9);
        uart_send_pack((uint8_t *)ptr,10);
    }

*当uart_send_pack((uint8_t )ptr,9);执行后:

*当uart_send_pack((uint8_t )ptr,10);执行后:

虽然g_ascTxBuffer的buffer里面有数据覆盖,但是往g_ascTxBuffer的buffer写一次,紧接着就发送一次,完美解决了数据覆盖问题。

最后测试一下发送和缓存区一样长度的数据,修改一下调用函数,这里先发16字节,再发14字节,然后再发16字节,一次测试两种情况,就是上来就发16字节,再中间段直接发送16字节。

c 复制代码
char *ptr = "1234567890123456";

        uart_send_pack((uint8_t *)ptr,16);
        uart_send_pack((uint8_t *)ptr,14);

*第一次执行uart_send_pack((uint8_t )ptr,16);

*第一次执行uart_send_pack((uint8_t )ptr,14);

*第二次执行uart_send_pack((uint8_t )ptr,16);

至此写的所有功能测试完毕。

另外完全是支持读写同时进行的,这里就不做过多演示了。

相关推荐
hallo-ooo28 分钟前
【STM32】定时器的外部时钟模式
stm32·单片机
hallo-ooo44 分钟前
【STM32】定时器输入捕获
stm32·单片机
小智学长 | 嵌入式2 小时前
单片机-STM32部分:1、STM32介绍
stm32·单片机·嵌入式硬件
zhbi982 小时前
STM32移植U8G2
stm32·单片机·嵌入式硬件·u8g2
Hans_Rudle3 小时前
MSP430G2553驱动0.96英寸OLED(硬件iic)
单片机·嵌入式硬件·msp430
爱奥尼欧3 小时前
【STM32】ADC的认识和使用——以STM32F407为例
stm32·单片机·嵌入式硬件
海绵宝宝的月光宝盒7 小时前
[STM32] 4-2 USART与串口通信(2)
c语言·开发语言·笔记·stm32·单片机
Mr_Chenph9 小时前
Stm32 烧录 Micropython
stm32·单片机·嵌入式硬件·micropython
快乐飒男9 小时前
stm32基础001(串口)
stm32·单片机
DIY机器人工房9 小时前
[2-2]新建工程 江协科技学习笔记(15个知识点)
笔记·科技·stm32·单片机·学习