96SEO 2026-02-20 08:03 0
使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函…使用DMA传输实现单片机高效串口转发——以STM32系列为例

DateAuthorVersionNote2023.08.06Dog
使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函数DMA中断服务函数Modbus协议代码
在许多现实应用场景中例如工业自动化控制、嵌入式通信设备等领域单片机需要实时地从一个串口读取数据并转发到另一个串口。
如果使用常规的轮询或中断方法来完成这样的任务会消耗大量的CPU资源效率较低。
此时如果采用DMA直接存储器访问进行串口数据转发则可以具备很多优势例如降低数据转发延时、减轻CPU的运行负载、提高系统的实时性等。
通过串口转发也可以实现多个不同通讯形式例如无线传输与有线传输、不同通讯协议例如自定协议与Modbus协议、不同通讯参数例如两个设备分别具备不同波特率的设备通讯中转。
Access是一种允许外设或内存直接与其他外设或内存交换数据而不需要通过CPU进行中介处理的技术。
DMA可以有效提高整体系统效率因为它允许数据传输的同时CPU仍可以执行其他任务。
STM32的DMA系统是一项强大的功能允许高效的数据传输同时减轻了CPU的负担。
其灵活的配置选项和与多种外设的兼容性使其适用于许多应用从简单的数据复制到复杂的外设管理。
正确使用DMA可以显著提高STM32微控制器的性能和功能。
使用单片机实现串口转发可以分为两种主要的模式直接转发模式与选择转发模式。
直接转发模式是指单片机从一个串口中接收到的数据不经CPU的判断与处理直接通过DMA传输从另个一串口发送出去。
间接转发模式是指单片机从一个串口中接收到的数据需经过CPU的判断与处理选择性的将部分数据或者修改后的数据通过DMA传输从另个一串口发送出去。
直接转发模式的核心实现过程为对于接收数据的DMA通道将串口的数据寄存器地址设置为源地址并设置一个内存地址为目标地址。
对于发送数据的DMA通道将之前设置的内存地址设置为源地址将另一个串口的数据寄存器地址设置为目标地址。
间接转发模式由于CPU的恰当介入而具备更好的灵活性与多场景的适应性因此得到更为广泛的应用。
以USART1与USART3为例间接转发的主要实现流程为
初始化串口初始化USART1和USART3配置波特率、数据位、停止位、奇偶校验等。
配置USART1用于中断接收和DMA转发启用USART1的接收中断功能并配置相关NVIC。
选择适当的DMA通道关联USART1的发送功能。
设置DMA源地址例如缓冲区和目标地址USART3的数据发送寄存器。
配置DMA的大小、方向、优先级、模式等。
配置USART3用于中断接收和DMA转发与USART1类似配置USART3以使用中断进行接收并选择适当的DMA通道用于发送。
设置DMA源地址例如缓冲区和目标地址USART1的数据发送寄存器。
配置DMA的大小、方向、优先级、模式等。
启用USART和DMA启用USART1、USART3以及相关的DMA通道。
中断服务程序处理在USART1的中断服务程序中读取接收到的数据并触发与USART3关联的DMA传输。
在USART3的中断服务程序中读取接收到的数据并触发与USART1关联的DMA传输。
错误处理和同步监视DMA和USART的错误标志并采取适当措施响应任何潜在问题。
根据需要实现缓冲区管理和同步机制以确保数据的完整性和时序。
以STM32F407的USART1与USART3双向互发为例展示核心功能实现的源码。
其中部分自定外设配置函数例如USART_ConfigNVIC,
示例代码中本机为Modbus-RTU从机设备其USART1为Modbus-RTU/无线433MHz通讯口USART3为RS485通讯口485总线上连接多台Modbus-RTU从机设备。
单片机从USART1中接收到Modbus-RTU请求报文之后会首先判断从机地址是否为本机如果从机地址为本机地址则进行正常的报文回复处理。
如果从机地址不是本机地址则通过USART3/485端口进行数据转发。
接收到来自USART3/485端口上对应从机的回复后再通过USART1/无线433MHz通讯端口将报文进一步封装后发送到主机。
通过此方法可以实现一对一的无线通讯与一对多的Modbus/RS485的混合组网。
其通讯系统示意图如下所示
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);USART_ConfigNVIC(1,
NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel
DMA2_Stream7_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
1;NVIC_InitStructure.NVIC_IRQChannelSubPriority
0;NVIC_InitStructure.NVIC_IRQChannelCmd
ENABLE;NVIC_Init(NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel
DMA1_Stream3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
1;NVIC_InitStructure.NVIC_IRQChannelSubPriority
0;NVIC_InitStructure.NVIC_IRQChannelCmd
ENABLE;NVIC_Init(NVIC_InitStructure);
DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,
DMA_DeInit(DMA2_Stream7);while(DMA_GetCmdStatus(DMA2_Stream7)
//等待stream可配置即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel
//从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr
//外设地址DMA_InitStructure.DMA_Memory0BaseAddr
//存储器0地址双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR
//存储器到外设模式DMA_InitStructure.DMA_BufferSize
DMA_InitStructure.DMA_PeripheralInc
//外设地址保持不变DMA_InitStructure.DMA_MemoryInc
//存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize
//外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize
//存储器数据位宽:8位DMA_InitStructure.DMA_Mode
//普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority
//中等优先级DMA_InitStructure.DMA_FIFOMode
DMA_InitStructure.DMA_FIFOThreshold
DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst
//单次传输DMA_InitStructure.DMA_PeripheralBurst
//单次传输DMA_ITConfig(DMA2_Stream7,
DMA_InitStructure);//配置USART3_TX-Stream:
DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,
DMA_DeInit(DMA1_Stream3);while(DMA_GetCmdStatus(DMA1_Stream3)
//等待stream可配置即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel
//从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr
//外设地址DMA_InitStructure.DMA_Memory0BaseAddr
//存储器0地址双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR
//存储器到外设模式DMA_InitStructure.DMA_BufferSize
DMA_InitStructure.DMA_PeripheralInc
//外设地址保持不变DMA_InitStructure.DMA_MemoryInc
//存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize
//外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize
//存储器数据位宽:8位DMA_InitStructure.DMA_Mode
//普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority
//中等优先级DMA_InitStructure.DMA_FIFOMode
DMA_InitStructure.DMA_FIFOThreshold
DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst
//单次传输DMA_InitStructure.DMA_PeripheralBurst
//单次传输DMA_ITConfig(DMA1_Stream3,
由于USART3是RS485协议传输需要选择收发状态。
本文源码中通过RS485_CTRL_ADDR的值实现收发转换。
在发送数据前先将RS485_CTRL_ADDR置1。
在DMA中断服务函数中发送完成中断将RS485_CTRL_ADDR置0恢复RS485的数据接收状态。
DMA_InitStructure;DMA_Cmd(DMA2_Stream7,
//关闭DMA通道DMA_SetCurrDataCounter(DMA2_Stream7,
//发送DMA流的地址不自增DMA2_Stream7-M0AR
//设置接收和发送的内存地址DMA_Cmd(DMA2_Stream7,
//打开DMA通道USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
DMA_GetFlagStatus(DMA2_Stream7,
DMA_InitStructure;DMA_Cmd(DMA1_Stream3,
//关闭DMA通道DMA_SetCurrDataCounter(DMA1_Stream3,
//发送DMA流的地址不自增DMA1_Stream3-M0AR
//设置接收和发送的内存地址DMA_Cmd(DMA1_Stream3,
//打开DMA通道USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);
DMA_GetFlagStatus(DMA1_Stream3,
data_temp[300];memcpy(data_temp,
MB_CommParam.MB_PreTrans_Num);memcpy(data_temp
length);USART1_DMA_SendData(data_temp,
MB_CommParam.MB_PreTrans_Num);//
MB_CommParam.MB_PreTrans_Num);//
本机地址不发送return;}*RS485_CTRL_ADDR
delay_ms(1);USART3_DMA_SendData(tx_buffer,
恢复RS485控制信号为接收状态的操作放到DMA发送完成中断中//
示例代码中本机为Modbus-RTU从机设备其USART1为Modbus-RTU/无线433MHz通讯串口USART3为RS485通讯串口485总线上连接多台Modbus-RTU从机设备。
因此MB_CommParam.MB_PortNum
在串口接收中断USART_IT_RXNE的服务函数中调用Modbus-RTU协议的数据接收函数pxMBFrameCBByteReceived。
在串口接收中断USART_IT_RXNE的服务函数中往FIFO队列缓冲中添加接收到的数据。
在串口空闲中断USART_IT_IDLE的服务函数中判断数据接收完成并实现数据转发的操作。
然后程序就不会因为ORE未被清除而一直不断的进入串口中断*/if
RESET){USART_ReceiveByte(USART1);}if
RESET){pxMBFrameCBByteReceived();USART_ClearITPendingBit(USART1,
RESET){pxMBFrameCBTransmitterEmpty();USART_ClearITPendingBit(USART1,
通过读串口DR寄存器里的值来清除IDLE标志位否则将一直触发空闲中断uint16_t
RESET){USART_ReceiveByte(USART3);}if
RESET){USART_ClearITPendingBit(USART3,
USART_IT_RXNE);USART_WriteFIFO(2,
串口空闲数据接收完成时转发数据到USART1{uint16_t
数据接收完成后转发数据到串口1USART3_RevBuffer_Handler(USART1_WSN32_SendData);//
USART3_RevBuffer_Handler(USART2_RS232_SendData);}}else
RESET){USART_ClearITPendingBit(USART3,
在DMA中断服务函数中发送完成中断将RS485_CTRL_ADDR置0恢复RS485的数据接收状态。
(DMA_GetFlagStatus(DMA2_Stream7,
关闭DMA通道DMA_ClearFlag(DMA2_Stream7,
(DMA_GetFlagStatus(DMA1_Stream3,
关闭DMA通道DMA_ClearFlag(DMA1_Stream3,
delay_ms(1);delay_us(500);*RS485_CTRL_ADDR
Modbus-RTU协议通过移植freemodbus库实现笔者在此库中增加了报文接收的函数指针
RTU_ADU_ReceivedHandler;因此可以设计一个回调函数USART3_RS485_SendData已在上文提供实现源码注册给RTU_ADU_ReceivedHandler指针实现非本机地址的modbus请求指令通过USART3转发。
User_Init函数首先通过读取两个拨码开关的值来判断当前设备的功能设定如果处于无线通讯状态主机与本机一对一则使能串口转发功能。
通过将不是本机地址的Modbus报文通过RS485总线发送出去再将接收到的RS485回复数据通过无线通讯转发到主机则可以实现多机通讯与无线/有线混合组网。
Debug_GetDipSwitchValue(GPIO_Array_DevAddr,
Debug_GetDipSwitchValue(GPIO_Array_SigChan,
初始化Modbus四种寄存器User_MB_InitRegs();if(IsEnablePortForwarding
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback