在前述学习实践基础(HNU-嵌入式系统-实验三(上)_基础内容)上,利用“STM32 开发板”,设计并实现一个具备 AD、DMA、串口通信等功能的应用案例。
工程文件已上传至github,自取
1.功能介绍:
(1)STM32板子上有两种工作模式:
1) 流水灯模式,Led灯以流水形式进行亮灭
2) 数码管显示模式,可以在数码管上显示数字内容
(2)ADC导航键(按键)功能:
1) Key1键按下进行串口通信向电脑发送tips(功能介绍说明)
2) Key2键按下进行串口通信向电脑发送当前的工作状态及信息
3) Key3键(与导航键是同一个GPIO)按下切换工作模式
4) 处于数码管显示模式时,导航键向上拨动数码管显示内容加一
(3)电脑向STM32发送指令
1) 在流水灯模式下,电脑可以进行串口通信发送指令来改变流水灯的速度和方向
而DMA功能用在了串口通信的实现上
2.具体实现:
(流水灯的功能在第一部分已经实现)
(1)流水灯模式
流水灯有两个特性:方向和速度
方向有两个:Led0Led7和Led7Led0
速度这里设置了两种:快(间隔20ms)和慢(间隔1s)
因此需要定义两个全局变量dir和speed分别表示方向和速度
那么实现就好办了,只需要将流水灯的实现代码放到Func_1s()和Func_20ms()函数里,再加上if的逻辑判断就行,代码大概如下(分别对应Func_20ms和Func_1s):
(2)数码管显示
首先在STM32cubeMX中配置决定数码管显示哪一位的三个:SEL0~2
定义一个全局变量dispalyAllDigit存放数码管显示的内容,以及数字0~9对应的数码管显示数组,再用一个函数封装一下等等,代码如下:
要使人不能分辨动态数码管显示,在1ms函数里对3-8译码器的SEL0~2的值“循环”:
其中,全局变量LED_SEL_Flag是标志着目前处于什么工作模式,只有在数码管显示模式下才会执行这段代码。
(3)串行通信&DMA
(DMA 是直接内存访问(Direct Memory Access),用于在外设(如 UART)和内存之间直接传输数据,完全绕过 CPU)
串口通信是常见的功能,常用的方法有三种:阻塞式、中断 和 DMA。
1)第一种阻塞式串口通信下,在发送或接收数据时,CPU 会等待操作完成后才继续执行下一步代码。所以不适合
2)而中断和DMA的串口通信代码逻辑上其实差不多,只是调用的函数名不同,使用硬件中断机制,发送或接收数据时,CPU 不需要等待,任务完成后硬件会触发中断,进入对应的中断服务函数进行处理。
中断式通信会让 CPU 处理每个字节的发送或接收,适合中小数据量或需要逐字节处理的场景。DMA通信让硬件负责整个数据块的传输,CPU 只处理完成中断,适合大数据量、高性能的场景。
所以对于我这“小”功能用不用DMA就可以了,但为了用到DMA就用的DMA串口通信进制实现的
首先在STM32cubeMX配置串口通信USART1和DMA:
选择异步Asynchronous:
添加两个DMA通道RX和TX:
打开中断:
直接生成代码后可以发现在stm32f1xx_it.c中多出来三个函数:
函数里面都有一个对于的中断处理函数,右键有个选项可以找到其定义。也可以直接去对应的文件里找到所有得hal函数
这里我用的是接收不定长的数据函数:
相应的回调函数是:
1)现在实现让电脑进行串口通信发送指令改变流水灯模式下的方向和速度,那么我需要HAL_UARTEx_ReceiveToIdle_DMA 接收数据,在回调函数中实现对流水的逻辑操作,为了让板子时刻接收电脑发送的数据,在回调函数最后要重新用 HAL_UARTEx_ReceiveToIdle_DMA 接收数据,代码如下:
为什么最后一行要用:
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);
用HAL_UARTEx_ReceiveToIdle_DMA()时,在接收的最大值的一半时也会调用该回调函数,所以用它可以禁用半传输中断。
注意:当时用这行禁用半传输中断代码时hdma_usart1_rx会报错。
解决办法:在usart.h里加上代码“extern DMA_HandleTypeDef hdma_usart1_rx;”
(4)ADC导航键
首先,在STM32cubeMX中配置ADC,在原理图中找到导航键键是用的ADC引脚接口是IN10,并打开中断使能。
生成代码后,在相应ADC模块文件里可以找到相关HAL函数:
我用到的HAL函数是:
1)开启 ADC 转换并启用中断模式
HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
2)在轮询模式或中断模式下,获取当前转换结果(12 位或更低分辨率)
HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);
3)ADC中断回调函数
HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
不过在这之前,我们还需知道ADC采样到的不同导航键的电压值。我们可以自己采样不同导航键对于的12位电压值并用串口通信在串口助手上发给电脑,不过得到的是ascll码形式,要进行相应转换,看看它们哪些位数是确定不变且互不相同的!以下是我采集到的不同导航键对应的12位值:
所以只有高4位是可以区分导航键的!
为了将导航键对应功能函数单独定义实现,进行函数封装。
用了flag进行标记,防止长按一直执行对应功能。
getNavigationKey函数确定是哪一个导航键,在ADC回调函数中调用Func_NavigationKey函数
然后可以分别在对应的功能函数写代码,非常清晰,这里我只用到导航键向上拨动和Key3键:
(5)按键Key1和Key2
首先,在STM32cubeMX中配置外部中断:
由于Key1和Key2的回调函数都是同一个,所以要用if分别处理对应功能
HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
Key1键按下进行串口通信向电脑发送tips(功能介绍说明)
Key2键按下进行串口通信向电脑发送当前的工作状态及信息
(tips内容太长,截不下了)
3.功能展示:
(1)首先是流水灯模式下(状态是Led0Led7,速度慢)
(2)按下key1发送tips:
(3)按下key1查看当前状态信息:
(4)电脑发送指令(比如0f):
(5)按下Key3切换工作模式
(6)数码管模式下,向上拨动导航键,使数码管内容加一
(7)再按下key2发送当前状态信息:
(8)发送指令格式不对时或在数码管模式下发送时:
这里推荐一个b站up主(keysking)讲解的STM32的教程:
【STM32入门教程】应该是全B站最好的STM32教程了
主要是这个up主有动画展示,讲的是真的好,听一遍就懂了!