DMA增加:
usart1 gpio 默认即可
usart1中断必须打开
在 STM32 中。USART 发送接收有三种基本方式,轮询、中断和 DMA。
1.轮询方式为堵塞模式,使用超时管理机制。它每次接收一个字节,在规定时间内接收固定长度的数据。在对于某些数据不固定长度接收的数据,轮询的方式有时候不够灵活。
2.可以使用中断的方式,如每一个字节都中断一次,当时比较消耗系统资源。特别是HAL库中,从中断到回调函数运行了不少的程序,频繁的中断很可能造成数据溢出。为了避免这个问题,我们使用指定接收一定长度的数据,再调用回调函数,这会让我们可以接收大数据,但是这种情况则造成了,要求每次的包是固定长度。
3.为了接收不定长度数据,网上最常用的办法是使用空闲中断,即在串口空闲的时候,触发一次中断,通知内核,本次运输完成了。数据传输过程为了尽量不占用CPU的处理数据时间,所以就使用DMA接收串口的数据。DMA(Direct memory access),即直接存储器访问。用于在外设与存储器之间以及存储器与存储器之间提供一种高速数据传输的方式。它在开始发送和接收完成数据时会给CPU相应的信号或者中断,在数据传输过程中无需CPU参与,通过硬件方式为RAM与I/O设备提供一条直接传送数据的通道。
main.h
main.c
usart.h
usart.c
stm32f1xx_it.c
1.1 DMA发送函数
*HAL_UART_Transmit_DMA(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size);
函数主要功能是以DAM模式发送pData指针指向的数据中固定长度的数据,并同时设置和使能DMA中断,具体怎么设置和使能中断的,打开此函数源码会发现下面这个函数。
HAL_DMA_Start_IT(huart->hdmatx, (uint32_t )tmp, (uint32_t)&huart->Instance->DR, Size);
此函数是启动DMA传输并启用中断。从函数源码可以看出此函数首先判断DMA传输状态是否是Ready:
如果是Ready则使能了DMA三个中断:DMA 半传输,DMA传输完成和DMA传输出错。如果发送数据正常,进入2次进入DMA中断(DMA 半传输和DMA传输完成);错误的话进入DMA传输出错中断;从这里也能看出HAL库比标准库严谨但效率低,DMA 半传输中断如果觉得效率低可以在程序中屏蔽掉,这样数据正常发送完成就只会进入一次DMA完成中断,三种DMA中断其实是同一个函数HAL_DMA_IRQHandler(DMA_HandleTypeDef hdma)
此函数功能是处理DMA中断请求,主要工作是清除中断标志位,改写DMA的状态,只有把状态改成HAL_DMA_STATE_READY,下一次才能正常使用DMA功能,否则会进入 HAL_BUSY状态
如果不是Ready状态,则进入HAL_BUSY状态,这也是为什么连续使用HAL_UART_Transmit_DMA()函数发送数据,第二次会发不出来数据,而且第二次函数会进入HAL_BUSY状态,所以要想使用HAL_UART_Transmit_DMA()函数连续发送数据,相邻两次之间要有延时间隔或者检测DMA数据是否完成。
1.2 DMA接收函数
**HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef huart, uint8_t pData, uint16_t Size)
函数说明:此函数的功能在DMA模式下接收大量数据,同时设置DMA线和哪个串口外设连接,以及将DMA线接收到的数据搬 *pData对应地内存中,和上面DMA发送函数一样,此函数同时具有设置和使能DMA中断的功能。可以看出此函数不仅是一个接收函数同时也是一个初始化函数,在main()之前调用,初始化串口和DMA连接和DMA接收BUF,以及设置和使能中断。如果DMA模式设置成循环模式时,只需设置这一次,如果DMA模式设置成正常模式时,每次读取完数据后需要再从新设置一次(就是再调用一次此函数),分析函数源码会发现此函数内部同样会调用以下两个函数,使用方法和分析和发送类似,不再赘述。
主要说说HAL_UART_Receive_DMA(),怎么配合IDLE串口空闲中断使用,main()函数之前一般调用此函数,一个主要目的是指明DMA传输串口数据存到指定的地方。一般情况我们会开辟一个全局变量的缓存extern uint8_t receive_buff[BUFFER_SIZE]。
比如函数初始化为HAL_UART_Receive_DMA(&huart2, (uint8_t*)receive_buff, BUFFER_SIZE); 就是设置串口2接收到数据通过DMA线直接到receive_buff中了,配合串口空闲中断,当进入串口空闲中断,说明一帧数据已接收完成。我们读取receive_buff相应长度的数据就是此次接收一帧的数据,这里还需要再介绍一个函数;__HAL_DMA_GET_COUNTER(HANDLE)
此函数的功能:获取当前DMA通道传输中 receive_buff[BUFFER_SIZE]缓存还剩余多少个数据单元。这样就能算出这一帧数据到底接收了多少单元的数据(数据长度=缓存总长度-缓存剩余的长度),
判断数据接收完成
那么问题来了,怎么知道数据是否接收完成呢?
其实,有很多方法:
对于定长的数据,只需要判断一下数据的接收个数,就知道是否接收完成,这个很简单,暂不讨论。
对于不定长的数据,其实也有好几种方法,麻烦不介绍,下面这种方法是最简单的,充分利用了stm32的串口资源,效率也是非常之高。
DMA+串口空闲中断这两个资源配合,简直就是天衣无缝啊,无论接收什么不定长的数据,管你数据有多少,来一个我就收一个
先看看stm32串口的状态寄存器:
当我们检测到触发了串口总线空闲中断的时候,我们就知道这一波数据传输完成了,然后我们就能得到这些数据,去进行处理即可。这种方法是最简单的,根本不需要我们做多的处理,只需要配置好,串口就等着数据的到来,dma也是处于工作状态的,来一个数据就自动搬运一个数据。
1.3空闲函数调用
为了减少误进入串口空闲中断,串口RX的IO管脚一定设置成Pull-up<上拉模式>,串口空闲中断只是接收的数据时触发,发送时不触发。
首先在main()之前初始化的时候调用__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
函数的功能是打开了串口的接收中断。注意这个时候我还没有打开空闲中断。而是在接收到了一个byte以后打开空闲中断。
当发送一帧数据接收完成后,会进入串口中断函数,如下函数
HAL库提供的这个串口中断函数,并没有针对空闲中断的处理,所以得我们自己加相应的代码。