尊重原创,转载请署名来源谢谢!
引言
最近项目中需要用到串口对传感器数据采集,需求上要求在2ms完成32个字节的传输,前后测试发现算上传感器的响应时间,RM57要使用高达420000波特率进行传输才能勉强满足上述要求,而如果使用中断接收串口数据,就会及其频繁的触发串口中断,这将影响到其他正常的运算。为了解决这个问题,这里提出了DMA的方式进行串口数据接收的方案。
相比较中断触发方式进行串口处理,DMA并不支持通过HalCoGen配置生成相关代码,所以如果想要用起来DMA,就要仔细阅读芯片手册,这个过程很煎熬,并不是说个人英文水平如何,而是TI关于DMA的描述非常碎片化,而且没有一个全局的描述去帮助使用者去理解如何正确使用串口DMA功能。既然如此,这里做一个详细的记录就显得十分有价值了。
目录
代码实现
#include <HL_sci.h>
#include <HL_sys_dma.h>
#include <string.h>
uint8 recv_queue_buf[24];
void sciEnableRxDmaRequest()
{
sciREG1->SETINT &= ~(uint32)((uint32)1U << 9U); /*rx dma*/
sciREG1->SETINT |= (uint32)((uint32)1U << 17U); /*rx dma*/
sciREG1->SETINT |= (uint32)((uint32)1U << 18U); /*rx dma*/
}
void scidmaInit(){
sciInit();
dmaReset();
dmaEnable();
sciEnableRxDmaRequest();
g_dmaCTRL ch_ctrl_pkt ;
ch_ctrl_pkt.SADD = (uint32)&sciREG1->RD;
ch_ctrl_pkt.DADD = (uint32)&recv_queue_buf[0];
ch_ctrl_pkt.CHCTRL = 0; /*no need trig next channel*/
ch_ctrl_pkt.FRCNT = 24U; /*24 frames in block*/
ch_ctrl_pkt.ELCNT = 1U; /*1 elements in frame*/
ch_ctrl_pkt.ELDOFFSET = 0U;
ch_ctrl_pkt.ELSOFFSET = 0U;
ch_ctrl_pkt.FRDOFFSET = 0U;
ch_ctrl_pkt.FRSOFFSET = 0U;
ch_ctrl_pkt.PORTASGN = (uint32)PORTB_READ_PORTA_WRITE;
ch_ctrl_pkt.RDSIZE = (uint32)ACCESS_8_BIT;
ch_ctrl_pkt.WRSIZE = (uint32)ACCESS_8_BIT;
ch_ctrl_pkt.TTYPE = (uint32)FRAME_TRANSFER;
ch_ctrl_pkt.ADDMODERD = (uint32)ADDR_FIXED;
ch_ctrl_pkt.ADDMODEWR = (uint32)ADDR_INC1;
ch_ctrl_pkt.AUTOINIT = (uint32)AUTOINIT_OFF;
dmaSetCtrlPacket(DMA_CH3, ch_ctrl_pkt);
dmaReqAssign(DMA_CH3, DMA_REQ28); /*sci1/lin1 dma request 28*/
dmaSetChEnable(DMA_CH3, DMA_HW);
dmaSetPriority(DMA_CH3, HIGHPRIORITY);
}
void resetReceiveBuffer() {
dmaReset();
dmaEnable();
dmaReqAssign(DMA_CH3, DMA_REQ28); /*sci1/lin1 dma request 28*/
dmaSetPriority(DMA_CH3, HIGHPRIORITY);
dmaSetChEnable(DMA_CH3, DMA_HW);
memset(&recv_queue_buf[0], 0, sizeof(recv_queue_buf));
}
void handleReceivedSciBytes() {
if (dmaGetInterruptStatus(DMA_CH3, BTC)){ /*all byte receive finished*/
//read and handle recv_queue_buf[]
}
else{
LOCAL_LOG_WARNING("recieve bytes not finished");
}
}
/*this function called every 2ms*/
void procHandler(){
handleReceivedSciBytes();
resetReceiveBuffer();
}
理解DMA传输以及串口DMA传输如何实现
示例代码中串口DMA传输的完整路径是这样的:
- 初始化并使能串口1,复位并使能DMA模块
- 从DMA的32个传输通道中选用CH3作为串口接收使用;
- 配置CH3读取源地址为串口1接收数据寄存器地址
- 配置CH3写入目标地址为接收缓冲区地址
- 配置CH3每次DMA传输1个字节,即Element尺寸为8bits、Frame尺寸为1Element、每次传输1Frame
- 配置CH3传输完成所需字节数为24字节,即Block尺寸为24个Frame
- 配置CH3的DMA请求信号为串口1(请求ID28)
- 配置CH3读端口为PORTB、写端口为PORTA
- 打开CH3硬件使能开关
- 打开串口1DMA接收中断
- 串口完成1个字节接收后,将触发DMA请求信号,DMACH3接收到该请求,执行一次Frame传输即从串口接收寄存器读取1字节数据并写入到应用程序的接收缓冲区中,然后将写指针地址加1;
- 完成24次DMA传输后,DMACH3完成1次Block传输,DMACH3的Block传输完成标志位置1,同时DMACH3自动关闭使能。
- 应用程序通过再次使能DMACHA3硬件使能开关,开启一次新的DMA传输。
理解RM57的DMA传输有以下几个关键概念:端口、通道、触发信号和数据组织形式。
端口PORT:RM57有2个端口,分别为PORTA和PORTB,用于访问不同地址。需要明确的是二者分工完全不同,不可以混用。PORTA用于读写Flash、内部和外部RAM;PORTB用于包括串口在内的各种外设地址空间。关于端口的映射关系可以参考手册SPNU562(page 678)。基于这种限制,我们在配置串口接收DMA的时候就要选用PORTB作为串口接收数据寄存器的访问端口,PORTA作为应用程序串口数据缓冲区的访问端口。
通道CHANNEL:DMA传输操作是通过通道实现的,RM57支持32个通道,每个通道定义了其DMA传输从哪里读取数据、读取的数据向哪里写入、读取后的指针如何变化、写入后的指针如何变化、每次触发请求的数据传输量、如何触发一次传输以及读取和写入的端口选择等信息。
触发请求REQUEST:DMA支持3中触发方式,包括程序软触发、硬件触发和链式触发。其中软触发的方式为程序控制,通过写寄存器的方式手动触发1次dma传输,这种方式比较适合从一片内存拷贝数据到另一片内存;硬件触发方式为支持DMA的硬件外设,比如串口,在接收到数据后发起触发信号的触发方式,这种触发方式需要查询各个外设对应的触发信号ID,具体参考手册SPNS215C(page 122)。以串口1为例,其触发ID为28;最后的链式触发方式为某一通道触发后,会触发其关联的其他通道,具体可以通过通道配置寄存器设置。
数据组织形式:DMA传输的数据从逻辑上分为3层,从低到高分别为元素Element、帧Frame、块Block。其中元素的大小可以配置成8、16、32、64位,每个帧包含若干个元素、每个块又由若干的帧组成。在前面的“通道CHANNEL”中描述的每次触发请求数据传输量,实际上就是通过配置每一次请求发出后,要传输一个帧还是一个块。以串口DMA传输为例,每次成功接收一个字节,将发起1次DMA请求,以一次完整通讯所需接收的数据量为8字节为例,合适的DMA数据组织形式应该为,ELEMENT大小:8bits;Frame尺寸:1 Elements;Block尺寸:8Frames;Channel每次请求传输Frame。具体可参考手册SPNU562(page 680)。