0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

利用极海APM32 MCU的UART和DMA配合乒乓操作实现批量数据接收

Geehy极海半导体 来源:21ic论坛极海半导体专区 2026-05-26 09:53 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

《极海芯得》系列内容为用户使用极海系列产品的经验总结,均转载自21ic论坛极海半导体专区,全文未作任何修改,未经原文作者授权禁止转载。

一、前言

1.1 应用场景

UART是一种应用广泛的通信接口,使用简单,绝大部分MCU都有这种接口

MCU端如果想接收串口的数据,简单的做法是在“接收寄存器非空”中断中一个字节一个字节的接收,这种方式代码处理起来简单些,一般情况下够用,但是如果接收的数据量非常大就不太合适,此外,这种方式需要CPU频繁处理中断,占用大量CPU时间不说,MCU性能低点还容易丢数据。

这是就需要用DMA来进行批量数据的接收,APM32的大部分UART都配有DMA,利用UART的空闲中断和DMA传输完成中断实现一次中断接收多个数据。

1.2 APM32的相关资源

想用DMA接收前提是改UART支持DMA功能,对于APM32E103来说除了UART5,其他串口都是有DMA功能的。

bb368e58-55ac-11f1-90a1-92fbcf53809c.png

这次我们用DMA进行多个数据接收,串口中断源只需要用到"空闲中断"即可。DMA传输完成中断只有在传输完特定数量数据后才触发,接收到的数据不足则不触发;而空闲中断则在超过1个字节时间没有收到数据时触发,空闲中断还是很有必要,因为对方发过来的数据往往是随机的。

bb93e8dc-55ac-11f1-90a1-92fbcf53809c.png

其中,“总线空闲”(IDLE)正是我们所要使用的关键中断源。手册里对空闲帧的处理机制说明如下:

bbf9521c-55ac-11f1-90a1-92fbcf53809c.png

对应的状态标志位定义如下,当检测到空闲总线时硬件会置位该标志:

bc567938-55ac-11f1-90a1-92fbcf53809c.png

这次我们以USART1为例,USART1的RX对应是DMA1的通道5。

bcb5bcf4-55ac-11f1-90a1-92fbcf53809c.png

二、乒乓操作算法

在用DMA接收数据如果只有一个数组进行缓存,那存在一个问题,在处理这些刚数据时,后面发过来的数据会覆盖这个缓存,导致数据错乱;如果直接在中断服务中先处理数据再开启下一次DMA传输,则很可能因为DMA开晚了导致丢失部分数据。

想解决以上问题,最好就是使用双缓存组成乒乓操作,DMA传输使用一个缓存,数据的解析解析处理使用另一个缓存,每次DMA传输完成修改地址切换成另一个缓存,这样一个缓存用于写入,另一个缓存用于读取,解决了接收速度和解析速度不匹配的问题,这种操作也叫乒乓操作。

bd0d7a7a-55ac-11f1-90a1-92fbcf53809c.png

弄懂乒乓操作后,下面就来编写代码,先建立一个乒乓数据类型,用来存储该算法需要的所有相关信息。

#defineMaxBufferSize 100 //单次最大接收长度

typedef struct

{

unsigned char buffer[2][MaxBufferSize+4];

unsigned int length[2];

unsigned char w_index;

unsigned char r_index;

}PingpongDef;

为了防止某些变量默认值不是0而产生一些BUG,编写一个初始化函数进行关键变量清零:

//乒乓缓存初始化

void pingpong_init(void)

{

comBuf.w_index = 0;

comBuf.r_index = 0;

comBuf.length[0] = 0;

comBuf.length[1] = 0;

}

编写缓存的写入函数,实现DMA传输完成中断或串口空闲中断的乒乓操作,写操作主要是要记录此次接收数据长度并切换DMA传输地址。

//乒乓缓存写

//unsigned int len: 本次写入的数据长度

//返回:下次写入的缓存地址

unsigned char *pingpong_write(unsigned int len)

{

comBuf.length[comBuf.w_index] = len;

comBuf.r_index = comBuf.w_index;

comBuf.w_index ^= 0x01;

return comBuf.buffer[comBuf.w_index];

}

编写缓存的读取函数,实现在while(1)中的数据解析前的乒乓操作,读操作时先判断这次的数据长度,如果确实有数据则输出这次数据的长度和指针,给后面代码使用。

//乒乓缓存读

//unsigned int *len:读出的数据长度

//返回:读出的数据地址

unsigned char *pingpong_read(unsigned int *len)

{

unsigned char *p;

*len = comBuf.length[comBuf.r_index];

if (*len == 0)

{

p = NULL;

}

else

{

p = comBuf.buffer[comBuf.r_index];

comBuf.length[comBuf.r_index] = 0;

}

return p;

}

这三个函数属于纯算法,可以用于后面代码调用。

void pingpong_init(void);

unsigned char *pingpong_write(unsigned int len);

unsigned char *pingpong_read(unsigned int *len);

三、UART和DMA的代码

3.1、UART和DMA的初始化

首先是USART1的配置,这部分和正常的USART配置相同。

接着是DMA的配置,也就是 DMA1_Channel5 的配置,外设地址设为USART的基地址+0x04。

bd6882a8-55ac-11f1-90a1-92fbcf53809c.png

外设地址不增,储存地址自增,数据大小都设置为字节,循环模式设置为禁止,内存地址设为之前的乒乓缓存的第1个数组,内存大小也就是DMA单次最大传输长度,这里设为100,传输方向设为从外设到内存。

最后使能USART的DMA接收功能,使能DMA通道,使能DMA的传输完成中断,使能串口的空闲中断。

//UART + DMA初始化

void uart_dma_init(void)

{

GPIO_Config_T GPIO_ConfigStruct;

USART_Config_T USART_ConfigStruct;

RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA | RCM_APB2_PERIPH_USART1);

//USART config

GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;

GPIO_ConfigStruct.pin = GPIO_PIN_9;

GPIO_ConfigStruct.speed = GPIO_SPEED_10MHz;

GPIO_Config(GPIOA, &GPIO_ConfigStruct);

GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;

GPIO_ConfigStruct.pin = GPIO_PIN_10;

GPIO_ConfigStruct.speed = GPIO_SPEED_10MHz;

GPIO_Config(GPIOA, &GPIO_ConfigStruct);

USART_ConfigStruct.baudRate = UartBaudrate;

USART_ConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;

USART_ConfigStruct.mode = USART_MODE_TX_RX;

USART_ConfigStruct.parity = USART_PARITY_NONE;

USART_ConfigStruct.stopBits = USART_STOP_BIT_1;

USART_ConfigStruct.wordLength = USART_WORD_LEN_8B;

USART_Config(USART1, &USART_ConfigStruct);

USART_Enable(USART1);

//DMA config

RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);

DMA_Reset(DMA1_Channel4);

DMA_Reset(DMA1_Channel5);

DMA_Config_T DMA_ConfigStruct;

DMA_ConfigStruct.peripheralBaseAddr = (uint32_t)(USART1_BASE + 0x04);

DMA_ConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;

DMA_ConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE;

DMA_ConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;

DMA_ConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;

DMA_ConfigStruct.loopMode = DMA_MODE_NORMAL;

DMA_ConfigStruct.M2M = DMA_M2MEN_DISABLE;

DMA_ConfigStruct.priority = DMA_PRIORITY_HIGH;

//USART1_RX DMA1_CH5

DMA_ConfigStruct.memoryBaseAddr = (uint32_t)comBuf.buffer[0];;

DMA_ConfigStruct.bufferSize = MaxBufferSize;

DMA_ConfigStruct.dir = DMA_DIR_PERIPHERAL_SRC;

DMA_Config(DMA1_Channel5, &DMA_ConfigStruct);

USART_EnableDMA(USART1, USART_DMA_RX);

DMA_Enable(DMA1_Channel5); //使能DMA通道

DMA_EnableInterrupt(DMA1_Channel5, DMA_INT_TC);

USART_EnableInterrupt(USART1, USART_INT_IDLE);

NVIC_EnableIRQRequest(USART1_IRQn, 0, 1);

NVIC_EnableIRQRequest(DMA1_Channel5_IRQn, 0, 0);

}

3.2 中断服务函数的处理

在两个中断服务函数中还要实现数据的接收操作,首先是串口空闲中断,进入串口空闲中断后,先清除空闲中断标志,然后读取DMA的传输长度,接着获取下一个待切换的缓存地址,重新配置长度并开启DMA通道,中断中的代码尽量少,避免执行时间过长而错过数据。

//串口接收空闲中断

void USART1_IRQHandler(void)

{

if (USART_ReadStatusFlag(USART1, USART_FLAG_IDLE) != RESET)

{

USART_ClearIntFlag(USART1, USART_INT_IDLE);

USART_RxData(USART1);

DMA1_Channel5->CHCFG_B.CHEN = DISABLE;

unsigned short len = MaxBufferSize - DMA1_Channel5->CHNDATA; //获取本次DMA传输长度

unsigned char *p = pingpong_write(len);

DMA1_Channel5->CHMADDR = (unsigned int)p;

DMA1_Channel5->CHNDATA = MaxBufferSize;

DMA1_Channel5->CHCFG_B.CHEN = ENABLE;

}

}

DMA传输完成中断的操作和串口空闲中断差不多,不同的只是前面是清除DMA的中断标志。

//DMA接收传输完成中断

void DMA1_Channel5_IRQHandler(void)

{

if (DMA_ReadIntFlag(DMA1_INT_FLAG_TC5) != RESET)

{

DMA_ClearIntFlag(DMA1_INT_FLAG_TC5);

DMA1_Channel5->CHCFG_B.CHEN = DISABLE;

unsigned short len = MaxBufferSize - DMA1_Channel5->CHNDATA; //获取本次DMA传输长度

unsigned char *p = pingpong_write(len);

DMA1_Channel5->CHMADDR = (unsigned int)p;

DMA1_Channel5->CHNDATA = MaxBufferSize;

DMA1_Channel5->CHCFG_B.CHEN = ENABLE;

}

}

四、批量数据接收测试

上面已经实现乒乓写入操作,剩下的就在while(1)中实现乒乓的读取操作,例如对数据进行解析。这里只演示数据的接收数量,把本次接收的数据量和总计接收的数据量通过printf打印出来。

//接收数据的处理..

void buffer_process(void)

{

static unsigned int total_len = 0;

unsigned int cur_len;

unsigned char *buf;

buf = pingpong_read(&cur_len);

if (buf)

{

total_len += cur_len;

printf("%d/%d ", cur_len, total_len); //打印输出本次读取长度和总长度

}

}

最后用串口助手发送一些数据进行测试,每一次都能收数据量都正确。

最重要的测试是发送一个文件,模拟批量数据的发送,以便能测试APM32的串口接收能力。发送一个64KB的文件,最终接收到的数据总数是65536,刚好不多不少。

bddeb248-55ac-11f1-90a1-92fbcf53809c.png

注:文章作者在原帖中提供了代码文件,有需要请至原文21ic论坛

原文地址:

https://bbs.21ic.com/icview-3499758-1-3.html?_dsign=b03b3d40

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    147

    文章

    19241

    浏览量

    405205
  • 寄存器
    +关注

    关注

    31

    文章

    5625

    浏览量

    130660
  • 中断
    +关注

    关注

    5

    文章

    922

    浏览量

    43929
  • uart
    +关注

    关注

    22

    文章

    1325

    浏览量

    107153

原文标题:极海芯得 EP.82 | 玩转APM32的DMA-用UART和DMA配合乒乓操作实现批量数据接收

文章出处:【微信号:geehysemi,微信公众号:Geehy极海半导体】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    APM32系列MCU中如何把代码重定位到SDRAM运行

    在有些情况下,我们想要把代码放到SDRAM运行。下面介绍在APM32MCU中,如何把代码重定位到SDRAM运行。对于不同APM32系列的MCU,方法都是一样的。
    的头像 发表于 11-04 09:14 5504次阅读
    在<b class='flag-5'>极</b><b class='flag-5'>海</b><b class='flag-5'>APM32</b>系列<b class='flag-5'>MCU</b>中如何把代码重定位到SDRAM运行

    APM32F030R8 MINI开发板试用体验】+官方dma接收usart程序软件

    接【APM32F030R8 MINI开发板免费试用】+官方adc程序由于APM32F03
    发表于 12-24 12:34

    APM32代替STM32遇到的坑

    APM32芯片是如何替换STM32芯片的?有哪些操作流程?
    发表于 09-22 07:56

    记录对TI M4C129 MCUDMA操作

    Cortex M4 DMA 操作Cortex M4 DMA 操作本文记录了对 TI M4C129 MCU
    发表于 02-22 06:10

    推出APM32A系列车规级MCU芯片

    、ADCx1、DACx1• USART增强支持主同步 SPI 和调制解调控制• 封装LQFP64第一代APM32A系列车规级MCU的推出,标志着在汽车行业已
    发表于 02-21 14:21

    TI M4(Cortex M4) MCU DMA 操作

    Cortex M4 DMA 操作Cortex M4 DMA 操作本文记录了对 TI M4C129 MCU
    发表于 12-28 19:18 11次下载
    TI M4(Cortex M4) <b class='flag-5'>MCU</b> <b class='flag-5'>DMA</b> <b class='flag-5'>操作</b>

    基于APM32 MCU的电动车BMS及电机控制应用方案

    APM32系列工业级通用MCU,低功耗、高性能、高集成、易于移植、支持96位唯一设备ID(UID),ESD高达8KV,符合工业级可靠性标准,具有较强的抗干扰性和防静电能力,全系列产
    发表于 02-08 17:01 17次下载
    基于<b class='flag-5'>APM32</b> <b class='flag-5'>MCU</b>的电动车BMS及电机控制应用方案

    嵌入式开发工具服务商IAR Systems工具链全面支持半导体APM32系列MCU

    嵌入式开发工具服务商IAR Systems工具链全面支持半导体APM32系列MCU. IAR Embedded Workbench for Arm 9.30已全面支持
    发表于 07-13 17:08 2476次阅读
    嵌入式开发工具服务商IAR Systems工具链全面支持<b class='flag-5'>极</b><b class='flag-5'>海</b>半导体<b class='flag-5'>APM32</b>系列<b class='flag-5'>MCU</b>

    Flasher在线烧录器全面支持APM32系列MCU

    半导体常务副总经理王远学表示:“非常有幸能与SEGGER达成合作,目前APM32系列MCU
    的头像 发表于 09-08 11:10 3496次阅读

    半导体32位APM32工业级MCU在工控领域的出色表现

    工业级MCU应用场景范围十分广泛,并对使用寿命、温度、湿度、电磁辐射等有着严格的品质要求。半导体长期深耕中高端工控市场,本期就以绝对值编码器、高性能伺服驱动器及变频器方案为例,详细介绍
    发表于 11-03 17:14 4228次阅读

    APM32 PROG安装包

    APM32 PROG安装包
    发表于 11-09 21:03 7次下载
    <b class='flag-5'>APM32</b> PROG安装包

    喜报频传!APM32工业级/车规级MCU产品接连荣获三项大奖

    半导体喜报频传接连荣获三项大奖。半导体“工业级高安全MCU APM32F415”、 “工
    发表于 11-21 15:17 1169次阅读

    PEmicro全面支持APM32MCU

    高性能、高集成度、低功耗APM32工业级/车规级MCU,基于Arm Cortex-M0+/M3/M4内核,拥有强大运算性能和增强型存储空间,具有丰富的协处理功能和广泛的外设资源。
    发表于 01-13 11:37 934次阅读

    APM32 MCU助力推动新型工业化发展

    国产APM32 MCU助力推动新型工业化发展
    的头像 发表于 09-28 17:38 1789次阅读
    <b class='flag-5'>APM32</b> <b class='flag-5'>MCU</b>助力推动新型工业化发展

    APM32F072xBT7硬件兼容ST电动车应用方案mcu

    APM32F072xBT7硬件兼容ST电动车应用方案MCU 随着电动车市场的不断发展,对于电动车控制器的要求也越来越高。而作为电动车控制器的核心部件,
    的头像 发表于 03-04 21:30 1772次阅读