使用DMA传输实现单片机高效串口转发——以STM32系列为例

使用DMA传输实现单片机高效串口转发——以STM32系列为例

DateAuthorVersionNote
2023.08.06Dog TaoV1.01. 完成了文档的撰写。
2023.08.23Dog TaoV1.11. 增加了STM32F103-USART2的DMA传输配置示例。 2. 增加了STM32F103与F407单片机的DMA控制器介绍并更改了第一章节的结构。

背景介绍

应用场景

在许多现实应用场景中,例如工业自动化控制、嵌入式通信设备等领域,单片机需要实时地从一个串口读取数据,并转发到另一个串口。如果使用常规的轮询或中断方法来完成这样的任务,会消耗大量的CPU资源,效率较低。此时如果采用DMA(直接存储器访问)进行串口数据转发则可以具备很多优势,例如降低数据转发延时、减轻CPU的运行负载、提高系统的实时性等。通过串口转发也可以实现多个不同通讯形式(例如无线传输与有线传输)、不同通讯协议(例如自定协议与Modbus协议)、不同通讯参数(例如两个设备分别具备不同波特率)的设备通讯中转。

直接存储器访问(DMA,Direct 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.

STM32的DMA控制器

  • STM32F103的DMA控制器

对于大容量的STM32F103芯片有2个DMA控制器,DMA1有7个通道,DMA2有5个通道。每个通道都可以配置一些外设的地址。从外设TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3]产生的7个DMA请求,通过逻辑或输入到DMA1控制器。从外设TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO产生的5个请求,经逻辑或输入到DMA2控制器。

在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

  • STM32F407的DMA控制器

每个DMA控制器有8个数据流,每个数据流都能够提供源和目标之间的单向传输链路。每个DMA控制器可以同时配置多个数据流,但在某一时刻只允许有一个数据流使用DMA控制器。当多个数据流同时请求时,由仲裁器决定哪一个数据流优先使用DMA控制器。仲裁器用于在多个数据流同时请求时,解决请求冲突的问题。在硬件上,数据流的编号越低,请求优先级越高,仲裁器优先响应编号低的数据流。

仲裁器为实现更灵活的配置,数据流还可以设置软件优先级,软件优先级分为以下4个级别:非常高优先级、高优先级、中优先级、低优先级。如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。

每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。

在这里插入图片描述
在这里插入图片描述

实现流程

使用单片机实现串口转发可以分为两种主要的模式:直接转发模式与选择转发模式。直接转发模式是指单片机从一个串口中接收到的数据不经CPU的判断与处理,直接通过DMA传输从另个一串口发送出去。间接转发模式是指单片机从一个串口中接收到的数据需经过CPU的判断与处理,选择性的将部分数据或者修改后的数据通过DMA传输从另个一串口发送出去。

直接转发模式的核心实现过程为:对于接收数据的DMA通道,将串口的数据寄存器地址设置为源地址,并设置一个内存地址为目标地址。对于发送数据的DMA通道,将之前设置的内存地址设置为源地址,将另一个串口的数据寄存器地址设置为目标地址。

间接转发模式由于CPU的恰当介入而具备更好的灵活性与多场景的适应性,因此得到更为广泛的应用。以USART1与USART3为例,间接转发的主要实现流程为:

  1. 初始化串口:初始化USART1和USART3,配置波特率、数据位、停止位、奇偶校验等。

  2. 配置USART1用于中断接收和DMA转发:启用USART1的接收中断功能,并配置相关NVIC。选择适当的DMA通道,关联USART1的发送功能。设置DMA源地址(例如缓冲区)和目标地址(USART3的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。

  3. 配置USART3用于中断接收和DMA转发:与USART1类似,配置USART3以使用中断进行接收,并选择适当的DMA通道用于发送。设置DMA源地址(例如缓冲区)和目标地址(USART1的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。

  4. 启用USART和DMA:启用USART1、USART3以及相关的DMA通道。

  5. 中断服务程序处理:在USART1的中断服务程序中,读取接收到的数据,并触发与USART3关联的DMA传输。在USART3的中断服务程序中,读取接收到的数据,并触发与USART1关联的DMA传输。

  6. 错误处理和同步:监视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-4
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);    //开启DMA时钟 
    DMA_DeInit(DMA2_Stream7);
    while(DMA_GetCmdStatus(DMA2_Stream7) != DISABLE){}   //等待stream可配置,即DMAy_SxCR.EN变为0
   
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;          //从8个channel中选择一个
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;            //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;      //存储器0地址,双缓存模式还要使用M1AR
    DMA_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变为0
   
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;          //从8个channel中选择一个
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART3->DR;            //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;      //存储器0地址,双缓存模式还要使用M1AR
    DMA_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 数据接收完成后,转发数据到串口1
                USART3_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报文事件
	}
}

其他

STM32F103 - USART2 DMA传输配置示例

uint8_t VFD_RequestCmd[20] = {0};

void USART_Config()
{
    // 配置串口
   	USART_ConfigGPIO(USART_485_TXD_PORT, USART_485_TXD_PIN, USART_485_RXD_PORT, USART_485_RXD_PIN);
	USART_ConfigNVIC(USART_485_NUM, 7, 0);
	USART_ConfigPort(USART_485_NUM, 9600, WordLength_8b, StopBits_1, Parity_No);
   	USART_ITConfig(USART_485,USART_IT_IDLE,ENABLE);

    // 配置DMA - STM32: DMA-Channel7 (USART2_TX)
    DMA_InitTypeDef DMA_InitStruct;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输
	DMA_DeInit(DMA1_Channel7);
	
    DMA_InitStruct.DMA_PeripheralBaseAddr=(u32)&USART2->DR;
	DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; 	//配置外设数据单位 8位字节传输
	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable; 	//禁止外设地址递增

    DMA_InitStruct.DMA_MemoryBaseAddr=(u32)VFD_RequestCmd;
	DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; 	//配置内存数据单位 8位字节传输
	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;	    //允许内存地址是否递增

	DMA_InitStruct.DMA_BufferSize=sizeof(VFD_RequestCmd);

	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralDST;   // 配置数据传输方向 外设作为数据传输目的地
	DMA_InitStruct.DMA_M2M=DMA_M2M_Disable;	        // 配置是否使能从内存到内存传输
    DMA_InitStruct.DMA_Mode=DMA_Mode_Normal;        // 配置DMA模式 单次传输 or 循环传输
	DMA_InitStruct.DMA_Priority=DMA_Priority_High;	// 配置DMA优先级

	DMA_Init(DMA1_Channel7, &DMA_InitStruct);
	USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
    DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, DISABLE);//使能DMA传输完成通道

	// DMA_ClearITPendingBit(DMA1_IT_TC4);
	// DMA_Cmd(DMA1_Channel7, ENABLE);  //使能USART2 TX DMA1 所指示的通道
}

void USART2_SendWithDMA(uint8_t *Data, uint16_t Count)
{
	memcpy(VFD_RequestCmd, Data, Count);

	DMA_Cmd(DMA1_Channel7, DISABLE);   // 关闭USART2 TX DMA1 所指示的通道      
 	DMA_SetCurrDataCounter(DMA1_Channel7, Count);//DMA通道的DMA缓存的大小
 	DMA_Cmd(DMA1_Channel7, ENABLE);     // 使能USART2 TX DMA1 所指示的通道 
}
  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全能骑士涛锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值