stm32串口dma发送/接收程序

嵌入式设计应用

128人已加入

描述

  串口可以配置成用DMA的方式接收数据,不过DMA需要定长才能产生接收中断,如何接收可变长度的数据呢?

  方法有以下3种:

  1.将RX脚与一路时钟外部引脚相连,当串口一帧发完,即可利用此定时器产生超时中断。这个实时性较高,可以做到1个字节实时监测。

  2.不改变硬件,开启一个定时器监控DMA接收,如果超时则产生中断。这个实时性不高,因为超时时间必须要大于需要接收帧的时间,精度不好控制。

  3.STM32单片机有的串口可以监测总线是否处于空闲,如果空闲则产生中断。可以用它来监测DMA接收是否完毕。这种方式实时性很高。

  串口DMA发送:

  发送数据的流程:

  前台程序中有数据要发送,则需要做如下几件事

  1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。

  2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道)

  3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。

  4. 等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。 判断数据发送完成:

  启动DMA并发送完后,产生DMA发送完成中断,在中断函数中做如下几件事:

  1. 清DMA发送完成中断标志位 2. 关闭串口发送DMA通道

  3. 给前台程序设置一个软件标志位,说明数据已经发送完毕

  串口DMA接收:

  接收数据的流程:

  串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。

  判断数据数据接收完成:

  这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:

  1.关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。

  2. 清除DMA 所有标志位

  3. 从DMA寄存器中获取接收到的数据字节数

  4.重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。

  5. 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。

  说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。

  串口用DMA方式发送和接收,分以下几步:

  1)串口初始化

  2)DMA初始化

  3)发送数据

  4)接收数据

  我们按部就班:

  1) 串口初始化 — 使用串口一

  #define DMASIZE 1024

  // 配置串口一的发送和接收的GPIO口功能,以及中断

  static void _uart1_gpio_init(void)

  {

  NVIC_InitTypeDef NVIC_InitStructure;

  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |

  RCC_APB2Periph_USART1 |

  RCC_APB2Periph_AFIO, ENABLE) ;

  GPIOA-》CRH&=0XFFFFF00F;

  GPIOA-》CRH|=0X000008B0;//IO状态设置 10pin_上拉输入 9pin_推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  /* Configure USART1 Rx as input floating */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Configure USART1 Tx as alternate function push-pull */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Enable the USART1 Interrupt */

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

  USART_ClearFlag(USART1, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */

  USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空闲中断,目的是在产生空闲中断时,说明接收或者发送已经结束,此时可以读取DMA中的数据了。

  //USART_ITConfig(USART1, USART_IT_TC, ENABLE);

  //USART_ITConfig(USART1, USART_IT_FE, ENABLE);

  }

  // 设置对应串口的波特率

  static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)

  {

  USART_InitTypeDef USART_InitStructure;

  USART_InitStructure.USART_BaudRate =value;

  USART_InitStructure.USART_WordLength = USART_WordLength_8b;

  USART_InitStructure.USART_StopBits = USART_StopBits_1;

  USART_InitStructure.USART_Parity = USART_Parity_No;

  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

  USART_Init(USARTx, &USART_InitStructure);

  USART_Cmd(USARTx, ENABLE);

  2)初始化DMA

  u8 sendbuf[1024];

  u8 receivebuf[1024];

  static void _uart1_dma_configuration()

  {

  DMA_InitTypeDef DMA_InitStructure;

  /* DMA1 Channel6 (triggered by USART1 Rx event) Config */

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,

  ENABLE);

  /* DMA1 Channel5 (triggered by USART1 Rx event) Config */

  DMA_DeInit(DMA1_Channel5);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递

  DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算)

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)

  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储

  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存

  DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议

  DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效

  /* DMA1 Channel4 (triggered by USART1 Tx event) Config */

  DMA_DeInit(DMA1_Channel4);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // 外设地址,串口1, 即发件的快递

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件

  DMA_InitStructure.DMA_BufferSize = 0; //发送长度为0,即未有快递需要发送

  DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化

  USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断

  USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求

  }

  }

  数据发送

  流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以

  发送数据分为两部分,

  A、发送数据

  B、中断处理

  A、发送数据

  u16 Uart_Send_Data(void* buffer, u16 size)

  {

  if(!size) return 0;// 判断长度是否有效

  while (DMA_GetCurrDataCounter(DMA1_Channel4));// 检查DMA发送通道内是否还有数据

  if(buffer) memcpy(sendbuf, buffer,(size 》 1024?1024:size));

  //DMA发送数据-要先关 设置发送长度 开启DMA

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR = size;// 设置发送长度

  DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送

  return size;

  }

  B、中断处理

  1)中断处理相关准备工作

  typedef enum _UartEvent_

  {

  E_uart_0 = 0,// 没有事件

  E_uart_tc=0x40, //发送完成

  E_uart_idle=0x80, //接收完成

  }UartEvent;

  u16 receivelen = 0;// 声明接收数据长度

  UartEvent event;//申明一个事件参数

  //清除DMA 缓存,并终止DMA

  void Uart_Dma_Clr(void)

  {

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR=0;

  DMA_Cmd(DMA1_Channel5, DISABLE);

  DMA1_Channel5-》CNDTR=DMASIZE ;

  DMA_Cmd(DMA1_Channel5, ENABLE);

  }

  // 获取一个事件,事件分为发送完成事件和接收完成事件,可以根据事件进行进行处理

  UartEvent Uart_Get_Event(void)

  {

  UartEvent e;

  if(!DMA1_Channel5-》CNDTR) Uart_Dma_Clr();// 如果产生一个事件后,接收数据通道已经没有了缓存空间,进行清除DMA清空

  return event;

  }

  // 清除对应的事件

  void Uart_Clr_Event(UartEvent event_in)

  {

  event&=~event_in;

  }

  2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;

  tem=tem;

  Uart_Set_Event(E_uart_idle);

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE);

  }

  **if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记**

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  4、接收数据

 dma

  根据上图描述,流程如下:

  1、串口接收到数据

  2、DMA自动取走数据

  3、DMA把数据存到内存receive[1024]中

  

  4、串口接收完毕后会产生一个空闲中断

  根据上面流程,我们接收数据需要做到两步:

  1)串口产生一个空闲中断后,设置一个接收完成事件

  中断处理:

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  **if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;// 清除DR

  tem=tem; // 防止编译器警告

  Uart_Set_Event(E_uart_idle);// 设置接收完成(空闲)事件

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断

  }

  if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作

  u8 Uart_Receive_Data(u8*recbuf u16 *revLen)

  {

  u8 *str;

  if( event & E_uart_idle) // 是否产生空闲中断

  {

  str = Uart_Get_Data(revLen);

  memcpy(recbuf,receivebuf,*revLen);

  Uart_Clr_Event(E_uart_idle);

  Uart_Dma_Clr();

  return TRUE;

  }

  else

  {

  revLen = 0;

  return FALSE;

  }

  }

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
Another968 2019-04-21
0 回复 举报
学习 收起回复

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分