网站建设虚线的代码网络逻辑设计报告
- 作者: 五速梦信息网
- 时间: 2026年04月20日 07:44
当前位置: 首页 > news >正文
网站建设虚线的代码,网络逻辑设计报告,网站编辑前端可以做吗,网站开发费用是否资本化使用DMA传输实现单片机高效串口转发——以STM32系列为例
DateAuthorVersionNote2023.08.06Dog TaoV1.01. 完成了文档的撰写。 文章目录 使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函…使用DMA传输实现单片机高效串口转发——以STM32系列为例
DateAuthorVersionNote2023.08.06Dog TaoV1.01. 完成了文档的撰写。 文章目录 使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函数DMA中断服务函数Modbus协议代码 应用场景
在许多现实应用场景中例如工业自动化控制、嵌入式通信设备等领域单片机需要实时地从一个串口读取数据并转发到另一个串口。如果使用常规的轮询或中断方法来完成这样的任务会消耗大量的CPU资源效率较低。此时如果采用DMA直接存储器访问进行串口数据转发则可以具备很多优势例如降低数据转发延时、减轻CPU的运行负载、提高系统的实时性等。通过串口转发也可以实现多个不同通讯形式例如无线传输与有线传输、不同通讯协议例如自定协议与Modbus协议、不同通讯参数例如两个设备分别具备不同波特率的设备通讯中转。
直接存储器访问DMADirect Memory Access是一种允许外设或内存直接与其他外设或内存交换数据而不需要通过CPU进行中介处理的技术。DMA可以有效提高整体系统效率因为它允许数据传输的同时CPU仍可以执行其他任务。STM32的DMA系统是一项强大的功能允许高效的数据传输同时减轻了CPU的负担。其灵活的配置选项和与多种外设的兼容性使其适用于许多应用从简单的数据复制到复杂的外设管理。正确使用DMA可以显著提高STM32微控制器的性能和功能。 From STM32F103 datasheet: The flexible 7-channel general-purpose DMA is able to manage memory-to-memory, peripheral-to-memory and memory-to-peripheral transfers. The DMA controller supports circular buffer management avoiding the generation of interrupts when the controller reaches the end of the buffer. Each channel is connected to dedicated hardware DMA requests, with support for software trigger on each channel. Configuration is made by software and transfer sizes between source and destination are independent.The DMA can be used with the main peripherals: SPI, I2C, USART, general-purpose and advanced-control timers TIMx and ADC. From STM32F407 datasheet: The devices feature two general-purpose dual-port DMAs (DMA1 and DMA2) with 8 streams each. They are able to manage memory-to-memory, peripheral-to-memory and memory-to-peripheral transfers. They feature dedicated FIFOs for APB/AHB peripherals, support burst transfer and are designed to provide the maximum peripheral bandwidth (AHB/APB). The two DMA controllers support circular buffer management, so that no specific code is needed when the controller reaches the end of the buffer. The two DMA controllers also have a double buffering feature, which automates the use and switching of two memory buffers without requiring any special code. Each stream is connected to dedicated hardware DMA requests, with support for software trigger on each stream. Configuration is made by software and transfer sizes between source and destination are independent. The DMA can be used with the main peripherals: SPI and I2S, I2C, USART, General-purpose, basic and advanced-control timers TIMx, DAC, SDIO, Camera interface (DCMI) and ADC. 实现流程
使用单片机实现串口转发可以分为两种主要的模式直接转发模式与选择转发模式。直接转发模式是指单片机从一个串口中接收到的数据不经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, USART_ConfigPort等来自笔者自定的HAL库。
示例代码中本机为Modbus-RTU从机设备其USART1为Modbus-RTU/无线433MHz通讯口USART3为RS485通讯口485总线上连接多台Modbus-RTU从机设备。单片机从USART1中接收到Modbus-RTU请求报文之后会首先判断从机地址是否为本机如果从机地址为本机地址则进行正常的报文回复处理。如果从机地址不是本机地址则通过USART3/485端口进行数据转发。接收到来自USART3/485端口上对应从机的回复后再通过USART1/无线433MHz通讯端口将报文进一步封装后发送到主机。
通过此方法可以实现一对一的无线通讯与一对多的Modbus/RS485的混合组网。其通讯系统示意图如下所示 串口与中断配置
void NVIC_Config()
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);USART_ConfigNVIC(1, 0, 0);USART_ConfigNVIC(2, 0, 0);USART_ConfigNVIC(3, 0, 0);NVIC_InitTypeDef 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);
}void USART_Config()
{USART_ConfigPort(1, 115200, WordLength_8b, StopBits_1, Parity_No);USART_ConfigPort(2, 115200, WordLength_8b, StopBits_1, Parity_No);USART_ConfigPort(3, 115200, WordLength_8b, StopBits_1, Parity_No);// 使能串口发送完成中断// USART_ITConfig(USART1, USART_IT_TC, ENABLE);// USART_ITConfig(USART2, USART_IT_TC, ENABLE);// USART_ITConfig(USART3, USART_IT_TC, ENABLE);USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);USART_Config_DMA();// 初始化串口接收缓冲区USART_RevInitAll();
}DMA外设配置
void USART_Config_DMA()
{//配置USART1_TX-Stream: DMA-2 Stream-7 Channel-4DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); //开启DMA时钟 DMA_DeInit(DMA2_Stream7);while(DMA_GetCmdStatus(DMA2_Stream7) ! DISABLE){} //等待stream可配置即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel DMA_Channel_4; //从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr (u32)USART1-DR; //外设地址DMA_InitStructure.DMA_Memory0BaseAddr (u32)SendBuff; //存储器0地址双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR DMA_DIR_MemoryToPeripheral; //存储器到外设模式DMA_InitStructure.DMA_BufferSize SENDBUFF_SIZE; //数据传输量以外设数据项为单位 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; //外设地址保持不变DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; //存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; //外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; //存储器数据位宽:8位DMA_InitStructure.DMA_Mode DMA_Mode_Normal; //普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority DMA_Priority_Medium; //中等优先级DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; //禁止FIFO模式 DMA_InitStructure.DMA_FIFOThreshold DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst DMA_MemoryBurst_Single; //单次传输DMA_InitStructure.DMA_PeripheralBurst DMA_PeripheralBurst_Single; //单次传输DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);// DMA_ITConfig(DMA2_Stream7, DMA_IT_TE, ENABLE);DMA_Init(DMA2_Stream7, DMA_InitStructure);//配置USART3_TX-Stream: DMA-1 Stream-3 Channel-4// DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); //开启DMA时钟 DMA_DeInit(DMA1_Stream3);while(DMA_GetCmdStatus(DMA1_Stream3) ! DISABLE){} //等待stream可配置即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel DMA_Channel_4; //从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr (u32)USART3-DR; //外设地址DMA_InitStructure.DMA_Memory0BaseAddr (u32)SendBuff; //存储器0地址双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR DMA_DIR_MemoryToPeripheral; //存储器到外设模式DMA_InitStructure.DMA_BufferSize SENDBUFF_SIZE; //数据传输量以外设数据项为单位 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; //外设地址保持不变DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; //存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; //外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; //存储器数据位宽:8位DMA_InitStructure.DMA_Mode DMA_Mode_Normal; //普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority DMA_Priority_Medium; //中等优先级DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; //禁止FIFO模式 DMA_InitStructure.DMA_FIFOThreshold DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst DMA_MemoryBurst_Single; //单次传输DMA_InitStructure.DMA_PeripheralBurst DMA_PeripheralBurst_Single; //单次传输DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);// DMA_ITConfig(DMA1_Stream3, DMA_IT_TE, ENABLE);DMA_Init(DMA1_Stream3, DMA_InitStructure);
}DMA发送数据函数
由于USART3是RS485协议传输需要选择收发状态。本文源码中通过RS485_CTRL_ADDR的值实现收发转换。在发送数据前先将RS485_CTRL_ADDR置1。在DMA中断服务函数中发送完成中断将RS485_CTRL_ADDR置0恢复RS485的数据接收状态。
void USART1_DMA_SendData(uint8_t *tx_buffer,uint16_t length)
{// DMA_InitTypeDef DMA_InitStructure;DMA_Cmd(DMA2_Stream7, DISABLE); //关闭DMA通道DMA_SetCurrDataCounter(DMA2_Stream7, (uint16_t)length); //设置传输字节数DMA2_Stream7-CR | (1 10); //发送DMA流的地址不自增DMA2_Stream7-M0AR (uint32_t)tx_buffer; //设置接收和发送的内存地址DMA_Cmd(DMA2_Stream7, ENABLE); //打开DMA通道USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送// while( DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) RESET); //等待传输完成 // DMA_Cmd(DMA2_Stream7, DISABLE); //关闭DMA通道 // DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7); //清除DMA传输完成标志
}void USART3_DMA_SendData(uint8_t *tx_buffer,uint16_t length)
{// DMA_InitTypeDef DMA_InitStructure;DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA通道DMA_SetCurrDataCounter(DMA1_Stream3, (uint16_t)length); //设置传输字节数DMA1_Stream3-CR | (1 10); //发送DMA流的地址不自增DMA1_Stream3-M0AR (uint32_t)tx_buffer; //设置接收和发送的内存地址DMA_Cmd(DMA1_Stream3, ENABLE); //打开DMA通道USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送// while( DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) RESET); //等待传输完成 // DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA通道 // DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF7); //清除DMA传输完成标志
}void USART1_WSN32_SendData(uint8_t *tx_buffer, uint16_t length)
{static uint8_t data_temp[300];memcpy(data_temp, MB_CommParam.MB_PreTrans_Data, MB_CommParam.MB_PreTrans_Num);memcpy(data_temp MB_CommParam.MB_PreTrans_Num, tx_buffer, length);USART1_DMA_SendData(data_temp, length MB_CommParam.MB_PreTrans_Num);// USART_SendData(USART1, MB_CommParam.MB_PreTrans_Data, MB_CommParam.MB_PreTrans_Num);// USART_SendData(USART1, tx_buffer, length);
}void USART3_RS485_SendData(uint8_t *tx_buffer, uint16_t length)
{if(tx_buffer[0] MB_CommParam.MB_SlaveAddr){// 本机地址不发送return;}*RS485_CTRL_ADDR 1;// delay_ms(1);USART3_DMA_SendData(tx_buffer, length);// 恢复RS485控制信号为接收状态的操作放到DMA发送完成中断中// vtaskDelay(100);// RS485_CTRL_ADDR 0;
}串口中断服务函数
示例代码中本机为Modbus-RTU从机设备其USART1为Modbus-RTU/无线433MHz通讯串口USART3为RS485通讯串口485总线上连接多台Modbus-RTU从机设备。因此MB_CommParam.MB_PortNum 的值为1。
USART1: 在串口接收中断USART_IT_RXNE的服务函数中调用Modbus-RTU协议的数据接收函数pxMBFrameCBByteReceived。 USART3: 在串口接收中断USART_IT_RXNE的服务函数中往FIFO队列缓冲中添加接收到的数据。在串口空闲中断USART_IT_IDLE的服务函数中判断数据接收完成并实现数据转发的操作。
void USART1_IRQHandler(void)
{/** 如果使能串口接收中断那么ORE为1时也会产生中断。* 在应用中对ORE标志进行处理当判断发生ORE中断的时候* 我们再读一次USART_DR的值* 这样如果没有新的Overrun 溢出事件发生的时候ORE会被清除,* 然后程序就不会因为ORE未被清除而一直不断的进入串口中断/if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) ! RESET){USART_ReceiveByte(USART1);}if (MB_CommParam.MB_PortNum 1){if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET){pxMBFrameCBByteReceived();USART_ClearITPendingBit(USART1, USART_IT_RXNE);}else if (USART_GetITStatus(USART1, USART_IT_TC) ! RESET){pxMBFrameCBTransmitterEmpty();USART_ClearITPendingBit(USART1, USART_IT_TC);}else if (USART_GetITStatus(USART1, USART_IT_IDLE) ! RESET){// 串口接收完数据后空闲必须清除空闲标志位。// 通过读串口DR寄存器里的值来清除IDLE标志位否则将一直触发空闲中断uint16_t data_temp USART1-DR; // 先读取接收缓存中数据清除空闲标志位data_temp USART1-SR;}else{}}else{/ 省略无关代码 /}
}void USART3_IRQHandler(void)
{if (USART_GetFlagStatus(USART3, USART_FLAG_ORE) ! RESET){USART_ReceiveByte(USART3);}if (MB_CommParam.MB_PortNum 3){/ 省略无关代码 */}else{if (USART_GetITStatus(USART3, USART_IT_RXNE) ! RESET){USART_ClearITPendingBit(USART3, USART_IT_RXNE);USART_WriteFIFO(2, USART_ReceiveByte(USART3)); // 将接收到的数据添加到FIFO缓冲区}else if (USART_GetITStatus(USART3, USART_IT_IDLE) ! RESET) // 串口空闲数据接收完成时转发数据到USART1{uint16_t data_temp USART3-DR; // 先读取接收缓存中数据清除空闲标志位data_temp USART3-SR;if (IsEnablePortForwarding ! 0){// USART3 数据接收完成后转发数据到串口1USART3_RevBuffer_Handler(USART1_WSN32_SendData);// USART3_RevBuffer_Handler(USART2_RS232_SendData);}}else if (USART_GetITStatus(USART3, USART_IT_TC) ! RESET){USART_ClearITPendingBit(USART3, USART_IT_TC);// do something}else{}}
}DMA中断服务函数
在DMA中断服务函数中发送完成中断将RS485_CTRL_ADDR置0恢复RS485的数据接收状态。
void DMA2_Stream7_IRQHandler(void) // USART-1-TX DMA
{// 判断是否为DMA发送完成中断if (DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) SET){DMA_Cmd(DMA2_Stream7, DISABLE); // 关闭DMA通道DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7);}
}void DMA1_Stream3_IRQHandler(void) // USART3-TX DMA
{// 判断是否为DMA发送完成中断if (DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) SET){DMA_Cmd(DMA1_Stream3, DISABLE); // 关闭DMA通道DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3);// delay_ms(1);delay_us(500);*RS485_CTRL_ADDR 0; // 恢复RS485的数据接收状态}
}Modbus协议代码
Modbus-RTU协议通过移植freemodbus库实现笔者在此库中增加了报文接收的函数指针
/// brief When modbus-RTU ADU received, this function will be called.
extern RTU_ADU_ReceivedHandler_Type RTU_ADU_ReceivedHandler;因此可以设计一个回调函数USART3_RS485_SendData已在上文提供实现源码注册给RTU_ADU_ReceivedHandler指针实现非本机地址的modbus请求指令通过USART3转发。
User_Init函数首先通过读取两个拨码开关的值来判断当前设备的功能设定如果处于无线通讯状态主机与本机一对一则使能串口转发功能。通过将不是本机地址的Modbus报文通过RS485总线发送出去再将接收到的RS485回复数据通过无线通讯转发到主机则可以实现多机通讯与无线/有线混合组网。
void User_Init()
{// 读取拨码开关的拨码值DevAddr_Val Debug_GetDipSwitchValue(GPIO_Array_DevAddr, 4, 0);SigChan_Val Debug_GetDipSwitchValue(GPIO_Array_SigChan, 4, 0);if ((DevAddr_Val ! 0) (SigChan_Val ! 0)) // The current work mode is WSN32{IsEnablePortForwarding 1;}else{IsEnablePortForwarding 0;}// 初始化Modbus四种寄存器User_MB_InitRegs();if(IsEnablePortForwarding ! 0){RTU_ADU_ReceivedHandler USART3_RS485_SendData; // 注册回调函数处理接收到Modbus报文事件}
}
- 上一篇: 网站建设虚线的代码免费h5模板网站模板
- 下一篇: 网站建设需求 百度文库编程自学
相关文章
-
网站建设虚线的代码免费h5模板网站模板
网站建设虚线的代码免费h5模板网站模板
- 技术栈
- 2026年04月20日
-
网站建设修改教程视频小型教育网站的开发与建设论文
网站建设修改教程视频小型教育网站的开发与建设论文
- 技术栈
- 2026年04月20日
-
网站建设修改教程视频教程wordpress frame主题
网站建设修改教程视频教程wordpress frame主题
- 技术栈
- 2026年04月20日
-
网站建设需求 百度文库编程自学
网站建设需求 百度文库编程自学
- 技术栈
- 2026年04月20日
-
网站建设需求发布网站按内容分可以分为
网站建设需求发布网站按内容分可以分为
- 技术栈
- 2026年04月20日
-
网站建设需求方案上海网站营销怎么样
网站建设需求方案上海网站营销怎么样
- 技术栈
- 2026年04月20日
