目录
前言
初识串口-轮询模式
串口中断模式收发
串口DMA模式
蓝牙模块与数据包解析
前言
前面我们简单介绍过串口的原理和初步的使用方式,例如怎么配置和简单的收发,同时我们对串口有了一个初步的了解,这里我们来深入的来使用一下串口
初识串口-轮询模式
这里我们不介绍如何使用STM32CubeMX配置串口,我们默认使用USART1同时波特率为115200,首先让我们来使用串口发送一些数据
这里我们定义了一个数组,数组的内容为Hello Wrold,在主循环中我们调用了一个函数
HAL_UART_Transmit(huart,pData,Size,Timeout)
huart :第一个参数为要操作串口的指针,这里要填写 &huart1 如果设置2那就换成 &huart2
pData :第二个参数填的是需要发送信息的指针 (uint8_t *)message
Size :第三个参数填的是数据字符串的长度 我们可以使用关键字 strlen(message) 识别长度
Timeout :第四个为发送数据的时间,超过这个时间就停止发送 单位 毫秒
完整的代码例子 HAL_UART_Transmit(&huart1 , (uint8_t *)message , strlen(message) , 50 )
发送的解决了之后,我们来使用轮询模式接收一些数据
让我们讲发送的代码注释掉,同时再定义一个接收用的数组,这里我们直接使用uint8_t来定义,他和char是相同的作用,串口接收的参收与串口发送的参数一模一样
第一个参数也使用哪个串口来接收数据,也是需要填地址,第二个参数是用来接收数据的指针,也就是receiveData的首地址,第三个还是一样,最后一个参数我们设置为 HAL_MAX_DELAY 为最大时间,就一直等待,没有超时时间,如果使用uint8_t定义数组,第二个参数就不需要强制类型转换了
HAL_UART_Receive(&huart1 , receiveData , strlen(receiveData) , HAL_MAX_DELAY )
编写一个使用串口控制红绿蓝三个灯的颜色
上电默认电平为高,如果串口第二位接收到0了则将电平拉低,同时串口根据第一位接收的数据来控制红绿蓝灯的亮灭
轮询模式不够灵活,同时会阻塞程序或者程序会阻塞串口收发,而且只能确定串口数据长度,我们接下来会学习中断模式串口收发数据
串口中断模式收发
中断的发送函数与普通轮询模式的发送函数差别不大只在后面加上了IT,同时没有超时时间
HAL_UART_Transmit_IT(&huart1 , receiveData , strlen(receiveData) )
接收的函数也大致相同,
HAL_UART_Receive_IT(&huart1 , receiveData , 2)
不过需要注意由于不会堵塞数据,也就是还没等到接收到数据,就会接着向下执行下去,这就会造成执行到下次循环时,可能上次的数据还没有接收完,就又开始执行串口中断接收了,所以我们需要放在循环前,只执行一次,注意接收一次之后需要重新开启接收中断
而当我们需要再次接收时,我们可以在串口中断回调函数中再次开启接收中断就可以了
我们将while中的逻辑代码复制粘贴到串口接收中断回调函数中,这样可以帮助节约cpu资源,使用中断非常简单,在cubemx中勾选开启中断,同时在函数后面加上_IT后缀,以及将处理逻辑移入到串口中断函数中即可,我们成功的解决了串口堵塞程序的问题,那如何接收不确定长度的数据呢,能否进一步减少CPU的占用呢
串口DMA模式
虽然中断方式相较于轮询模式已经节约了很多cpu资源,但对于cpu本身来说,却是屡屡被打断,疲于在中断搬运数据与处理正常任务代码间辗转反侧,那我们能否进一步提升效率,给cpu找一个帮助搬运数据的小助手,这时候DMA的用处就显现出来了,DMA全称叫直接内存访问
打开cubemx,找到USART1设置界面,找到DMA Settings,点击下面的Add就可以添加DMA通道
在这里设置的DMA发送通道,DMA通道7为专门为USART2发送设置的,数据搬运方向由内存向外设搬运,也就是从内存变量向发送数据寄存器进行搬运,优先级默认为低,可以自己选择,其他参数CubeMX已经帮我们配置好了,默认即可,之后我们再添加一个USART2_RX的通道就行了
DMA模式的发送函数也就是把中断的_IT切换成了DMA,参数依旧是用哪个串口发送,要发送的变量以及发送的字节数,接收函数也是一样
需要注意的是,即使使用了DMA,其实还是有中断参与其中的,例如中断接收回调函数RxCpltCallback还是由中断触发,当然这次就不是串口的中断了,而是DMA传输中断,说完了DMA的用法,我们来看看如何接收不定长数据
其实接收不定长数据非常简单,主要靠的是串口空闲中断,此中断的触发条件与接收的字节数无关,只有当RX引脚上无后续数据进入,也就是串口接受从忙率转变为空闲时才会触发,这里我们就可以认为,当串口空闲中断时,一帧数据包就发送完成了
这里我们需要将串口接收函数替换为HAL封装的一个拓展函数
HAL_UARTEx_ReceiveToIdle_DMA(&huart2 , receiveData , 50)
我们可以注意,这个函数也有阻塞DMA和中断三个版本,我们这里选择DMA模式,由于是接收不定长数据,这里我们可以将receiveData这个数组改大一点,改成50,而这函数的最后一个参数,是填写一次能接收的最大数据长度,一般就是填写接收数组的长度,来避免接收数据太长而导致数组越界
此时我们要用的中断函数也变了
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart , uint16_t size)
这个回调函数和中断回调函数的区别就是他的参数中多了一个 size 长度,因为之前中断回调函数是接收定长数据,我们已知了数据长度,我们需要size入参来得知本次到底接收了几字节的数据
我们在下面这个回调函数中先判断一下是不是USART2接收了,我们将接收到的函数重新发送回串口助手中,要注意这里发送函数的第三个参数要改成回调函数参数中的size,来发送与接收相同的字节数,最后别忘记再次开启接收中断
可以发现,我们再次发送R1,发现数据返回来了,但是灯没有点亮,也就能证明使用ReceiveToldle函数后,不再调用RxCpltCallback回调函数,而是使用了RxEventCallback回调函数,我们再尝试将数据增长,发现数据正常返回,这就说明我们完美的实现了不定长数据接收
我们需要注意在这个函数中需要重新开启接收中断,但是我们使用普通模式或者中断模式的Toldle函数也是可以的,唯独DMA模式有个恼人之处,DMA的传输过半中断,也会触发这个回调函数,就是说如果接收的数据量达到我们设置的最大值的一半的时候,也会触发一次回调函数,使用一个很大的接收数组虽然能解决问题,但也是治标不治本,此方案的正确解决办法则是关闭DMA传输过半中断
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx , DMA_IT_HT)
第一个参数我们填写DMA通道的指针地址,第二个参数我们填写需要关闭的中断
注意别忘记在初始化中加上这一句,否则第一次接收依旧会出现问题