N32G45之串口+DMA数据收发

1.串口简介

  通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。使用多缓冲器配置的DMA方式,可以实现高速数据通信。
  串口是计算机上一种非常通用设备通信的协议。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。本次所所使用开发板为mini USB串口,有3个分别为USART1、USART2和USART3。我们将以串口1为例完成开发板与上位机之间通讯。

2.串口特性

  • 任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)

  RX:接收数据串行输。通过过采样技术来区别数据和噪音,从而恢复数据。
  TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,
  TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。总线在发送或接收前应处于空闲状态。
  一个起始位。
  一个数据字(8或9位),最低有效位在前。
  0.5, 1.5, 2个的停止位,由此表明数据帧的结束。
  使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
  一个状态寄存器(USART_STS)。
  一个数据寄存器(USART_DAT)。
  一个波特率寄存器(USART_BRCF), 12位的整数和4位小数。

3.串口配置

    置位 USART_CTRL1.UEN 来使能 USART;
    配置波特率、数据长度、校验位、停止位长度、以及根据需要配置相关 DMA;
    使能发送功能 (USART_CTRL1.TXEN);
    通过 CPU 或 DMA 将要发送的数据依次写入数据寄存器 USART_DAT, 当数据写入数据寄存器时将清零 USART_STS.TXDE;
    当所有数据已写入到数据寄存器 USART_DAT 后,等待发送完成标志位 USART_STS.TXC 置 1,数据发送完成;

    3.1 串口控制寄存器CTRL1

      串口的基本功能(使能串口、中断功能、发送、接收)均在USART1_CRTL1寄存器中完成配置。

    3.2 串口状态寄存器STS

      状态寄存器主要用于检测各个状态标志是否触发(发送标志、接收标志、空闲帧标志等等)。

    3.3 串口数据寄存器DAT

      串口数据寄存器用包含了发送和接收数据。串口数据收发均使用DAT寄存器装载。

    3.4 串口波特率寄存器

      串口波特率寄存器包含了4位小数位和12位整数位。

     波特率计算方式如下:

    3.5 串口模式配置

      我们这里以串口1为例,因为串口1为开发板调试端口,方便后面效果展示。配置模式为:1个起始位+8个数据位+1个停止位,无校验位。开始长发送功能和空闲帧接收功能,通过DMA配合串口数据收发,实现不定长数据处理。

    void USART_Init(u32 buad)
    {
      RCC->APB2PCLKEN|=1<<14;
      RCC->APB2PRST|=1<<14;
      RCC->APB2PRST&=~(1<<14);
      /*配置GPIO*/
      RCC->APB2PCLKEN|=1<<2;//PA
      GPIOA->PH_CFG&=0xFFFFF00F;
      GPIOA->PH_CFG|=0x000008B0;
      /*串口配置*/
      USART1->BRCF=72000000/buad;
      USART1->CTRL1|=1<<2;
      USART1->CTRL1|=1<<3;
      
      USART1->CTRL3|=1<<7;//DMA发送
      USART1->CTRL3|=1<<6;//DMA接收
      USART1->CTRL1|=1<<4;//IDLE(空闲帧中断)
      N32_NVIC_SetPriority(USART1_IRQn,1,1);
      USART1->CTRL1|=1<<13;//使能串口
      DMA_CH5_Init((u32 )&USART1->DAT,(u32 )usart1_rx_buff);//DMA配合串口1接收数据 
     DMA_CH4_Init((u32 )&USART1->DAT);
    }
    

    3.6 修改printf底层重定向

      修改printf重定向,方便后续功能调试。
      printf底层实现接口函数为fputc,我们只需要将该函数重定向到串口调试终端即可。

    int fputc(int c,FILE *stream) 
    {
      USART1->DAT=c;
      while(!(USART1->STS&1<<7));
      return c;
    }
    

    4.DMA功能配置

      直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

      DMA 控制器总共可以访问 8 个 AHB 从机: Flash、 SRAM、 ADC、 SDIO、 QSPI、 ETH、 ABP1 和 APB2。
      DMA 控制器由 CPU 控制以执行从源到目的的快速数据移动。配置完成后,无需 CPU 干预即可传输数据。因此,可以释放 CPU 用于其他计算/控制任务或节省整体系统功耗。
      芯片有两个 DMA( DMA1、 DMA2)控制器,每个 DMA 控制器有 8 个逻辑通道。每个逻辑通道用于服务来自单个或多个外设的内存访问请求。内部仲裁器控制不同 DMA 通道的优先级。

    •  DMA特性
    • 16 个可独立配置的 DMA 通道: DMA1 和 DMA2 各有 8 个通道
    • 支持内存到内存、内存到外设和外设到内存三种传输类型
    • 每个 DMA 通道支持硬件请求和软件触发来启动传输,并由软件配置
    • 每个 DMA 通道都有专用的软件优先级( DMA_CHCFGx.PRIOLVL[1:0]位,对应 4 个优先级),可以单 独配置。具有相同软件优先级的通道将进一步比较硬件索引(通道号)以确定最终优先级(索引号越低的通道优先级越高)
    • 可配置的源和目标大小。地址设置应与数据大小相对应
    • 每个通道可配置循环传输模式
    • 每个通道有 3 个独立的事件标志和中断(传输完成、半传输、传输错误)和 1 个全局中断标志(由 3 个事件的逻辑或设置)。
    • 共访问 8 个 AHB 从机: Flash、 SRAM、 ADC、 SDIO、 QSPI、 ETH、 ABP1 和 APB2
    • 可配置数据传输数( 0~65535)
    • DMA功能框图

      DMA 控制器和 Cortex™-M4F 内核共享相同的系统数据总线。当 CPU 和 DMA 同时访问同一个目标( RAM或外设)时, DMA 请求会暂停 CPU 访问系统总线几个周期,由总线仲裁器进行循环调度。这允许 CPU 获得至少一半的系统总线(内存或外围设备)带宽。
      DMA 请求可以由硬件外设或软件触发, DMA 控制器根据通道的优先级处理请求。根据配置的传输地址和位宽从源地址读取数据,然后将读取的数据存储在目的地址空间中。一次操作后,控制器计算剩余传输数,并更新下一次传输的源地址和目的地址。

    •  每个 DMA 数据传输包括三个操作:

      数据访问:根据传输方向确定源地址( DMA_PADDRx 或 DMA_MADDRx),从源地址读取数据。
      数据存储:根据传输方向确定目的地址( DMA_PADDRx 或 DMA_MADDRx),将读取的数据存储到目的地址空间。
      计算未完成操作的数量,对 DMA_TXNUMx 寄存器进行减量操作,更新下一个操作的源地址和目的地址。

    4.1DMA请求映像

      DMA1 请求映像如下图所示。通过配置对应外设的寄存器,每个外设的 DMA 请求均可以独立的开启或关闭,并根据通道优先级,同一时间只有一个请求有效。

     根据DMA请求映像可知,USART1_TX在Channel4上,USART1_RX在Channel5上。

    4.2 DMA配置流程

    • 配置中断屏蔽位, 1:启用中断, 0:禁用中断。
    • 配置通道外设地址和内存地址以及传输方向。
    • 配置通道优先级, 0:最低, 3:最高。
    • 配置外设和内存地址增量。
    • 配置通道传输块大小。
    • 如有必要,配置循环模式。
    • 如果是存储器到存储器,配置 MEM2MEM 模式。
    • 在通道 1~ 8 上重复第 1~ 8 步。
    • 最后使能相应通道。
       如果使用软件提供中断服务,则软件必须查询中断状态寄存器以检查发生了哪个中断(软件需要向中断标志清除位写 1 来清除相应的中断)。在使能通道之前,应清除该通道对应的所有中断。
       如果中断是传输完成中断,软件可以配置下一次传输,或者向用户报告该通道传输完成。
    •  DMA支持三种传输方式:
    • 存储器到存储器
    • 存储器到外设
    • 外设到存储器
        流控制由每个 DMA 通道配置寄存器中的两个寄存器位控制。流控制用于控制 DMA 通道的源/目标和方向。

    4.3 DMA相关寄存器介绍

      DMA通道配置寄存器用于配置DMA传输方向、数据宽度、优先级等功能。

      DMA传输数量寄存器用于设置DMA传输的数量,范围在0~65535之间。

    外设基地址寄存器DMA_PADDR

    4.5 DMA通道4配置

      DMA1_CH4配置,设置模式为存储器到外设,配合串口1数据发送。

    /******DMA配合串口1发送数据************
    **形参:u32 cpar -- 外设地址
    **
    **例:DMA_CH4_Init(&USART1->DR,buff)
    **外设地址:USART1->DR的地址
    **          buff的地址
    **作者:IT_阿水
    **************************************/
    void DMA_CH4_Init(u32 cpar)
    {
      RCC->AHBPCLKEN|=1<<0;//dma1时钟使能
      DMA1_CH4->CHCFG&=~(1<<14);//非存储器到存储器模式
      DMA1_CH4->CHCFG|=0x3<<12;//设置CH4优先级为最高
      DMA1_CH4->CHCFG&=~(0x3<<10);//存储器数据宽度8位
      DMA1_CH4->CHCFG&=~(0x3<<8);//外设数据宽度8位  
      DMA1_CH4->CHCFG|=1<<7;//存储器地址增量
      DMA1_CH4->CHCFG&=~(1<<6);//外设地址不增量
      DMA1_CH4->CHCFG&=~(1<<5);//不执行循环操作
      DMA1_CH4->CHCFG|=1<<4;//从存储器读
    
      DMA1->CHMAPEN|=1<<0;//使能MAP通道
      DMA1_CH4->CHSEL|=16<<0;//开启USART1_TX
    
      DMA1_CH4->CHCFG&=~(1<<0);//关闭通道传输
      DMA1_CH4->PADDR=cpar;//外设地址
    }
    /**********开启DMA1_CH4数据传输*************
    ***
    ***形参:u16 data_len -- DMA要传输数目
    ***********************************************/
    void DMA_CH4_Start(u8 *buff,u16 data_len)
    {
      DMA1_CH4->CHCFG&=~(1<<0);//关闭通道传输
      DMA1_CH4->MADDR=(u32)buff;//存储器地址
      DMA1_CH4->TXNUM=data_len;//设置传输数量
      DMA1_CH4->CHCFG|=1<<0;//开启通道传输
    }
    

    4.6 DMA通道5配置

      DMA1_CH5配置,设置模式为外设到存储器,配合串口1数据接收。

    /******DMA配合串口1接收数据************
    **形参:u32 cpar -- 外设地址
    **      u32 cmar -- 存储器地址
    **
    **例:DMA_CH5_Init(&USART1->DR,buff)
    **外设地址:USART1->DR的地址
    **          buff的地址
    **作者:IT_阿水
    **************************************/
    void DMA_CH5_Init(u32 cpar,u32 cmar)
    {
    	RCC->AHBPCLKEN|=1<<0;//dma1时钟使能
    	DMA1_CH5->CHCFG&=~(1<<14);//非存储器到存储器模式
    	DMA1_CH5->CHCFG|=0x3<<12;//设置CH4优先级为最高
    	DMA1_CH5->CHCFG&=~(0x3<<10);//存储器数据宽度8位
    	DMA1_CH5->CHCFG&=~(0x3<<8);//外设数据宽度8位  
    	DMA1_CH5->CHCFG|=1<<7;//存储器地址增量
    	DMA1_CH5->CHCFG&=~(1<<6);//外设地址不增量
    	DMA1_CH5->CHCFG&=~(1<<5);//执行循环操作
    	DMA1_CH5->CHCFG&=~(1<<4);//从外设读
    	
    	DMA1->CHMAPEN|=1<<0;//使能MAP通道
    	DMA1_CH5->CHSEL=23;//开启USART1_RX
    	
    	DMA1_CH5->CHCFG&=~(1<<0);//关闭通道传输
    	DMA1_CH5->PADDR=cpar;//外设地址
    	DMA1_CH5->MADDR=cmar;//存储器地址
    	DMA1_CH5->TXNUM=1024;//设置传输数量
    	DMA1_CH5->CHCFG|=1<<0;//开启通道传输
     
    }
    /**********开启DMA1_CH5数据传输*************
    ***
    ***形参:u16 data_len -- DMA要传输数目
    ***********************************************/
    void DMA_CH5_Start(u16 data_len)
    {
      DMA1_CH5->CHCFG&=~(1<<0);//关闭通道传输
      DMA1_CH5->TXNUM=data_len;//设置传输数量
      DMA1_CH5->CHCFG|=1<<0;//开启通道传输
    }
    

    5.编写串口1中断服务函数

      编写串口以中断服务函数,通过触发空闲帧中断,配置DMA实现串口不定长数据接收处理。
      当一空闲帧被检测到时, USART_STS.IDLEF 置 1。此时如果 USART_CTRL1.IDLEIEN 已置 1,将产生一个中断。 USART_STS.IDLEF 可通过以下软件操作清零:先读 USART_STS 寄存器,再读 USART_DAT 寄存器。

    /*************************串口接收数据函数********************/
    void Usart1_Receive_Data(void)
    {
      DMA1_CH5->CHCFG&=~(1<<0);//关闭通道传输
      usart1_cnt=1024-DMA1_CH5->TXNUM;//获取接收到是字符长度
      if(usart1_cnt>1024)usart1_cnt=0;
      DMA1->INTSTS|=1<<17;//清除标志位
      DMA1_CH5->TXNUM=1024;//从新赋值
      DMA1_CH5->CHCFG|=1<<0;//开启闭通道传输
      usart1_flag=1;
    }
    
    /*串口中断服务函数*/
    void USART1_IRQHandler(void)
    {
      u8 c;
      //清除标志:先读USART_STS,再读USART_DAT;
      c=USART1->STS&1<<4;
      if(c)//空闲帧
      {
        c=USART1->DAT;
        Usart1_Receive_Data();//接收数据处理函数
      }
      USART1->STS=0;//清除标志位
    }
    

    6.实现串口终端控制LED亮灭

      通过串口调试终端配合,利用串口数据接收功能,实现串口通讯方式对开发板LED亮灭控制。

    #include "n32g45x.h"
    #include <stdio.h>
    #include <string.h>
    #include "led.h"
    #include "key.h"
    #include "usart.h"
    #include "delay.h"
    int main()
    {
      u8 time=0;
      u8 key_val;
      LED_Init();
      KEY_Init();
      USART_Init(115200);
      printf("串口初始化完成\r\n");
      while(1)
      {
        key_val=Key_Scan();  
        if(key_val)
        {
          printf("串口+DMA数据发送测试示例!\r\n");
        }
        if(usart1_flag)
       {
               usart1_rx_buff[usart1_cnt]='\0';
         printf("%s,%d\r\n",usart1_rx_buff,usart1_cnt);
               if(strcmp((char *)usart1_rx_buff,"LED1_ON")==0)LED_D1=1;
              else if(strcmp((char *)usart1_rx_buff,"LED1_OFF")==0)LED_D1=0;
              else if(strcmp((char *)usart1_rx_buff,"LED2_ON")==0)LED_D2=1;
              else if(strcmp((char *)usart1_rx_buff,"LED2_OFF")==0)LED_D2=0;
              else if(strcmp((char *)usart1_rx_buff,"LED3_ON")==0)LED_D3=1;
              else if(strcmp((char *)usart1_rx_buff,"LED3_OFF")==0)LED_D3=0;
              usart1_flag=0;
      }  
        time++;
        Delay_Ms(10);
        if(time>=50)
        {
          time=0;
          LED_D1=!LED_D1;
        }
      }    
    }