前言
这篇文章个人对WUT-电子科技协会的hal库学习视频的学习笔记,里面理论部分来自于文档“我的19岁-电子设计大赛”。
最开始通过一些示例来熟练上手HAL库
两个实例
CubeMX点亮LED
先以管理员身份运行CubeMX
点击New Project
点击GPIO_Output
然后点击Project Manager
配置好后,点击Code Generator
配置好后,点击Adcanced Settings
然后点击GENERATE CODE
然后关闭CubeMX
按照此路径打开文件(按照自己保存的路径打开)
在main.c里面的while(1)写入代码,实现led闪烁。
按键实现led关灭
- 按下按键:Input
- 点亮LED灯:Output
- 0.5s:延时的过程
- 熄灭:Output
CubeMX配置
虽然输出引脚比输入引脚多了更多的配置,但是目前需要你修改的就是GPIO Pull-up/Pull-down。(见后面的GPIO中的配置引脚)
点击System view
再点击GPIO
把led那个引脚配置成一开始就是高电平,上拉模式
按键那个引脚配置成上拉模式
最后生成代码
keil中的配置
1.输入模式下
HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); //读取PA0引脚状态
2.输出模式下
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);//将引脚状态改为低电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);//将引脚状态改成高电平
按键实现 led关灭的主函数代码如下
最终主函数的代码如下
while (1)
{
//为了代码的可读性,读取引脚一般不写0,写GPIO_PIN_RESET
if(HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
while(HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)//按键检测
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
HAL_Delay (500);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
GPIO
理论相关部分
GPIO 8种工作模式
1.GPIO_Mode_AIN 模拟输入
2.GPIO_Mode_IN_FLOATING 浮空输入
3.GPIO_Mode_IPD 下拉输入
4.GPIO_Mode_IPU 上拉输入
5.GPIO_Mode_Out_OD 开漏输出
6.GPIO_Mode_Out_PP 推挽输出
7.GPIO_Mode_AF_OD 复用开漏输出
8.GPIO_Mode_AF_PP 复用推挽输出
各个模式应用总结
- 上拉输入、下拉输入可以用来检测外部信号;例如,按键等;
- 浮空输入模式,由于输入阻抗较大,一般把这种模式用于标准通信协议的 I2C、USART 的接收端;
- 普通推挽输出模式一般应用在输出电平为 0 和 3.3V 的场合。而普通开漏输出模式一般应用在电平不匹配的场合,如需要输出 5V 的高电平,就需要在外部一个上拉电阻,电源为 5V,把 GPIO 设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出 5V 电平。
- 对于相应的复用模式(复用输出来源片上外设),则是根据 GPIO 的复用功能来选择,如 GPIO 的引脚用作串口的输出(USART/SPI/CAN),则使用复用推挽输出模式。如果用在 I2C、SMBUS 这些需要线与功能的复用场合,就使用复用开漏模式。
- 在使用任何一种开漏模式时,都需要接上拉电阻。
Cube MX相关配置
选择引脚类型
GPIO_Input 输入引脚
GPIO_Output 输出引脚
配置引脚
- 输入引脚
对于输入引脚,可以配置的就是GPIO Pull-up/Pull-down。这分别对应的就是Pull-up(输入上拉)与Pull-down(输入下拉)。
Pull-up:输入上拉就是把点位拉高,比如拉到Vcc,上拉就是将不确定的信号通过一个电阻嵌位在高电平。电阻同时起到限流的作用。弱强只是上拉电阻的阻值不同,没有什么严格区分。
Pull-up:输入下拉就是把电压拉低,拉到GND。与上拉原理相似。
简单的说,如果你希望你的引脚平时处于高电平用于检测低电平,你就使用Pull-up,如果你希望你的引脚平时处于低电平用于检测高电平,你就使用Pull-down。
- 输出引脚
对于输出引脚,比输入多了更多的配置:
GPIO output level -> 初始化输出电平
GPIO mode -> 输出方式 -> 开漏或推挽输出
GPIO Pull-up/Pull-down -> 上拉或下拉输出
Maximum output speed 选中 GPIO 管脚的速率
选中GPIO管脚的速率:
I/O 口的输出模式下,有 3 种输出速度可选 (Low - 2MHz、Medium - 10MHz、High -50MHz),这个速度是指 I/O 口驱动电路的响应速度而不是输出信号的速度,输出信号的速度与程序有关(芯片内部在 I/O 口的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要选择合适的驱动电路)。通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。高频的驱动电路,噪声也高,当不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的 EMI 性能。当然如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。
举个例子:
1.USART 串口,若最大波特率只需 115.2k,那用 2M 的速度就够了,既省电也噪声小。
2.I2C 接口,若使用 400k 波特率,若想把余量留大些,可以选用 10M 的 GPIO 引脚速度。
3.SPI 接口,若使用 18M 或 9M 波特率,需要选用 50M 的 GPIO 的引脚速度。
keil中的代码
初始化及重置相关
IO口操作相关
同时HAL库帮我们定义好了GPIO_PIN_RESET与GPIO_PIN_SET,代表着0(低电平)、1(高电平)。
User Label
对于任意引脚,它都有这么一个选项。我想告诉你这个选项特别特别好用!这个选项简单的说就是它帮你在 main.h 中生成 define 语句。但是对于 HAL 库编程,main.h 会被用户的每个模块调用,也就是这些 define 语句的作用域几乎是全局。
举个例子让你感受一下,在一次开发中,我使用 PA0 来作为输出引脚。如果随着开发的继续 PA0 被迫要用于其他功能,那么你该怎么办?那你必须使用另外一个引脚(假设是 PB1)来替代它。如果你没有配置 User Label 选项,那你的代码中可能大量的充斥着
然后你又需要用 PB1 来代替 PA0,那你就需要将整个代码中有关 PA0 的 GPIOA 改成 GPIOB,将 GPIO_PIN_0 改成 GPIO_PIN_1。这会导致巨大的工作量,并且容易出错。
那么我们来看看使用了 User Label 会带来什么变化,使用 User Label 把他取名 R1。那你的代码中充斥着的不在是
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET),
而是HAL_GPIO_WritePin(R1_GPIO_Port, R1_Pin, GPIO_PIN_RESET)。
当遇到 PA0 被迫要用于其他功能,你只需要把 PB1 的 User Label 取名为 R1 后,代码不需要做丝毫改变。
在这位大佬的开发中,这个应用最典型的两个例子就是“矩阵键盘”和“ADS1256”的开发。用矩阵键盘来举例,需要用到 8 个引脚。
矩阵键盘中的代码全是由 R1-R4、C1-C4 组成,所以在各这个代码的复用性极其强,无论是换引脚还是换单片机型号,他只需要在 Cube MX 中配置一下,就可以马上投入使用。
实际操作(个人在CubeMX上的操作)
- 使用User Label去命名PA1,使得按键的代码可以被复用
- 使用User Label去命名PA2,使得led的代码可以被复用
- 此时在keil工程的main.h里面,可以看到我们自己配置的User Label
- 接着写main.c里面的代码
在写函数里面的参数的时候,只要写入自己之前设定的名称Key1,就会自动检索出全称Key1_GPIO_Port与Key1_Pin,不需要一直去翻main.h。用起来确实非常方便。
接着需要更改引脚的时候,只需要在CubeMX里面配置好更改的引脚,将需要更改的引脚的User Label更改成Key1,然后直接从这边把代码拷贝过去就可以用了。
串口通信
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。
理论相关部分
UART与USART
- UART: 通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作 UART。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART 通常被集成于其他通讯接口的连结上。
- USART:(Universal Synchronous/Asynchronous Receiver/Transmitter) 通用同步/异步串行接收/发送器,USART 是一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备。
CubeMX相关配置
初始化引脚
Mode:
1.Asynchronous : 异步, 整个过程,不会阻碍发送者的工作。
2.Synchronous : 同步, 同步信息一旦发送,发送者必须等到应答,才能继续后续的行为。
3.Single Wire : 单总线, 半双工。
配置引脚
- Baud Rate: 波特率, 波特率表示每秒钟传送的码元符号的个数,是衡量数据传送速率的指标,它用单位时间内载波调制状态改变的次数来表示。对于串口最重要的就是波特率, 常用的波特率为 115200 与 9600。
- Wrod Length : 数据长
- Parity : 奇偶校验 -> 无、奇校验、偶校验
- Stop : 停止位
以上的配置与需要通信双方完全配对
编写keil中的代码
就大佬目前的学习来看,HAL并没有对同步通信的方式做拓展,所以上述都是关于UART的函数。
printf重定向
在Private includes中引入:
在 USER CODE BEGIN 0 添加:
然后你就可以在任意地方使用printf语句方便的输出你想要的内容了。
注意,这一步要在keil里面勾上Options for Target -> Target ->勾选Use MicroLIB
Log信息格式
格式1
参考目前主流嵌入式、安卓等输出方式
格式2
参考Java日志框架的输出方式:
条件编译
因为在进行单片机开发的过程中,会需要大量的 Log 信息,但是在开发结束时,你又不想它一直打印(这会拖慢单片机的速度)。所以大佬提出了他的办法:
在头文件中添加:
再把.c文件中将所有的printf包裹上 #if Log与 #endif:
可变参数宏
关于这个内容,是大佬在阅读国内某云物联网模块源码时发现并学习的
大佬觉得这个解决方案比之前提到的条件编译强100倍,甚至让大佬觉得以前的做法多么的愚蠢。这种方法不仅达到了代码的格式化,同时也完成了条件编译。
大佬的设计:
当需要打印串口信息的时候,define一个USER_MAIN_DEBUG,在不需要的时候将其注释
个性化输出
1.借助下面的网站设计自己的字符
http://patorjk.com/software/taag/
2.编写代码
先逐行复制输入
再用转义字符修补错误
3.串口助手看效果
串口中断
1.CubeMX中开启中断
2.在USER CODE BEGIN 2中打开串口中断
3.在USER CODE BEGIN 4中实现回调函数
实际操作(个人在CubeMX上的操作)
配置USART
- 其中一种是直接在引脚上找USART1的串口
能用作USART通信的串口,在点击后是可以看到的。 - 如果不想配置这个引脚了,可以点击Reset_State
- 接着点那两个三角尖,可以开始配置USART,或者也会自动弹出让你配置
- 然后可以直接进行配置,配置波特率,停止位,长度等等
- 接着是keil里面的代码
这里面的&huart1是在usart里面找到的,跳转HAL_UART_Transmit这个函数的定义就可以知道为什么是&huart1了
第二个参数是需要传输的字符,第三个参数是传输的长度,第四个是传输时间,这个一般非常快,所以给个50就行。
-
接着是在CubeMX配置时,直接去检索适合USART通信的 IO口
点击左边的Connectivity,然后点击需要的串口,比如 USART1,然后再点击上面Mode中选中Asynchronous。 -
如果我将PA10已经用作GPIO_Output,但是我还需要用USART1,这个时候还是点击Connectivity,然后选中USART1,点击Mode中的Asynchronous,再点击下面的GPIO Settings,可以看到,CubeMX自动帮我们复用好了GPIO口
这就更方便了,不用我们手动去复用GPIO口,并且可以自动检索我们需要的GPIO口。
外部中断
理论相关部分
CubeMX相关配置
初始化引脚
如果你想使用 PA1 作为外部中断的接收引脚,那么你只需要点击 PA1,在点击它对应的 GPIO_EXTIx。
使能中断
配置引脚
这个地方不同于之前的GPIO mode
编写keil代码
在 main.c 中的 USER CODE BEGIN 4 编程范围内添加外部中断的回调函数:
测量pwm频率
当有上升沿的时候,就进入外部中断将 pwm_value 的值 +1。it is clear that “1s 钟上升沿的次数就是 pwm 的频率”。所以当我要用 pwm 的频率时,我就先将 pwm_value 置 0,再延时 1s,最后再使用 pwm_value。当然这并不是最终的代码,因为你读到这里还有很多的内容没有学习, 往后的定时器章节将介绍它的滤波算法。
实际操作(个人在CubeMX上的操作)
- 这次用的时f4系列的作为实际操作,选好要作为中断的引脚,然后点击System view,然后点击NVIC,然后勾选中断。
- 接着配置GPIO,点击System view,选择GPIO,然后配置GPIO
然后生成项目 - 中断这里的函数
- 主函数这里的函数
实现的功能就是,如果接收到中断,单片机发送exti,然后换行。
时钟树
理论相关部分
通常我们会让单片机的频率(决定单片机的处理速度)提到最大,然后再进行其他分频操作。
使能外部时钟源
将频率调至最大
不同单片机的最大运行频率是不同的,例如stm32f103为72M,而stm32f407为84M。
按需分频
定时器
理论相关部分
CubeMX相关配置
配置定时器时钟
如之前所说,将定时器时钟设置为最大,f1系列是72M,f4系列是84M
选择时钟源
选择内部时钟
配置定时器
开启中断-基本定时器
勾选Enabled框即可
开启中断-高级定时器
勾选TIM Xupdate interrupt 后的Enabled框即可
编写keil代码
平滑滤波
在这里我想在介绍定时器的另一种用法:平滑滤波。绝大部分人的滤波算法都是用的时候,多次采样再滤波。但是我希望让采样值在另一个“线程”一直滤波,而在我需要他的时候,直接取它的值即可。
当我们在任意时刻需要使用pwm的频率时,只需要使用pwm_value_final的值即可。