STM32之HAL库开发(一)

STM32之HAL库开发(一)
AyaHAL 库
STM32CubeMX
简介及下载
在 CubeMX 上,通过傻瓜化的操作便能实现相关配置,最终能够生成 C 语言代码,支持多种工具链,比如 MDK、IAR For ARM、TrueStudio 等 省去了我们配置各种外设的时间,大大的节省了时间。
在蓝桥杯中,可以通过使用 STM32CubeMX 实现 HAL 库,达成快速编写代码的效果。
其 TM32CubeMX 的使用需要 JAVA 支持。
安装 HAL 库
在线安装
打开安装好的 STM32CubeMX 软件 点上面的 Help –> Manage embedded software packages
会跳出来一个选择型号界面 勾选上你要安装的 HAL 库, 点击“Install Now” 直到安装成功。 如下图:
离线安装(需掌握)
蓝桥杯中若主办方提供的固件包有误,需使用离线安装。
直接导入安装包 Help –> Manage embedded software packages –> From Local 选择离线包即可
新建工程方式
在主界面选择 File –> New Project 或者直接点击 ACCEE TO MCU SELECTOR
直接搜索型号
通过可视化界面快速设置引脚,无需进行枯燥的代码编写
淡黄色表示不可配置引脚 电源专用引脚以黄色突出显示。其配置不能更改
深黄色表示你配置了一个 I/O 口的功能,但是没有初始化相对应的外设功能 引脚处于 no mode 状态
绿色表示配置成功
点击 System Core –> SYS –> Debug 将其使能为 Serial Wrire,否则无法使用 STLink 烧录代码
接下来需要配置时钟树:
点击 System Core –> RCC –> HSE 将其使能,否则无法使用外部高速时钟
点击 Clock Configuration 切换至时钟树页面进行配置
其中需要将系统时间配平为 72MHZ , APB1 为 TIM2 - 4、5(普通时钟),APB2 为 TIM1\8 (高级时钟)
点击 Project Manager 进行工程文件设置
其中 Toolchain / IDE 需要设置为 MDK-ARM,存储目录不能有中文
然后点击 Code Generator,进行进一步配置:
注:选择 不复制文件,直接从软件包存放位置导入 .C 和 .H 文件 的话,当工程复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径
然后点击 GENERATE CODE 创建工程
所有自己编写的代码请放在
/_ USER CODE BEGIN XXX _/
和/_ USER CODE END XXX _/
之间这样我们修改工程的时候你自己写的代码就不会被删除
GPIO
GPIO 工作模式
输入模式
GPIO_Mode_IN_FLOATING
浮空输入GPIO_Mode_IPU
上拉输入GPIO_Mode_IPD
下拉输入GPIO_Mode_AIN
模拟输入输出模式
GPIO_Mode_Out_OD
开漏输出(带上拉或者下拉)GPIO_Mode_AF_OD
复用开漏输出(带上拉或者下拉)GPIO_Mode_Out_PP
推挽输出(带上拉或者下拉)GPIO_Mode_AF_PP
复用推挽输出(带上拉或者下拉)输出速度
2MHZ
低速25MHZ
中速50MHZ
快速100MHZ
高速
GPIO 工作模式含义
浮空输入
I/O 端口的电平信号直接进入输入数据寄存器。MCU 直接读取 I/O 口电平,I/O 的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的。上拉输入
IO 内部接上拉电阻,此时如果 IO 口外部没有信号输入或者引脚悬空,IO 口默认为高电平 如果 I/O 口输入低电平,那么引脚就为低电平,MCU 读取到的就是低电平STM32 的内部上拉是”弱上拉“,即通过此上拉输出的电流是很弱的,如要求大电流还是需要外部上拉。
下拉输入
IO 内部接下拉电阻,此时如果 IO 口外部没有信号输入或者引脚悬空,IO 口默认为低电平 如果 I/O 口输入高电平,那么引脚就为高电平,MCU 读取到的就是高电平模拟输入
当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作”模拟输入”功能,此时信号不经过施密特触发器,直接直接进入 ADC 模块,并且输入数据寄存器为空 ,CPU 不能在输入数据寄存器上读到引脚状态当 GPIO 用于模拟功能时,引脚的上、下拉电阻是不起作用的,这个时候即使配置了上拉或下拉模式,也不会影响到模拟信号的输入输出
除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律 要配置为复用功能模式,
开漏输出
若控制输出为 0 (低电平),I/O 端口的电平就是低电平。若控制输出为 1 (高电平),此时 I/O 端口的电平就不会由输出的高电平决定,而是由 I/O 端口外部的上拉或者下拉决定 如果没有上拉或者下拉 IO 口就处于悬空状态。
推挽输出
若控制输出为 0 (低电平),I/O 端口的电平就是低电平。若控制输出为 1 (高电平),I/O 端口的电平就是高电平。外部上拉和下拉的作用是控制在没有输出时 IO 口电平
此时施密特触发器是打开的,即输入可用。复用开漏输出
GPIO 复用为其他外设,输出数据寄存器 GPIOx_ODR 无效;
输出的高低电平的来源于其它外设,施密特触发器打开,输入可用,通过输入数据寄存器可获取 I/O 实际状态 除了输出信号的来源改变 其他与开漏输出功能相同复用推挽输出
GPIO 复用为其他外设(如 I2C),输出数据寄存器 GPIOx_ODR 无效;
输出的高低电平的来源于其它外设,施密特触发器打开,输入可用,通过输入数据寄存器可获取 I/O 实际状态 除了输出信号的来源改变 其他与开漏输出功能相同
HAL 库 GPIO 函数库
stm32f4xx_hal_gpio.h中 一共定义有 8 个函数
1 | void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init); |
GPIO 初始化
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
示例:
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
在函数初始化之后的引脚恢复成默认的状态,即各个寄存器复位时的值
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
示例:
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_4);
读取引脚的电平状态、函数返回值为 0 或 1
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
示例:
HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4)
引脚电平写 0 或 1
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
示例:
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4,0);
翻转引脚的电平状态
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
示例:
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_4);
常用在 LED 上锁住引脚电平
比如说一个管脚的当前状态是 1,当这个管脚电平变化时保持锁定时的值。
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
示例:
HAL_GPIO_LockPin(GPIOC, GPIO_PIN_4);
外部中断服务函数,清除中断标志位
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
示例:
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
示例:
HAL_GPIO_EXTI_Callback(GPIO_PIN_4);
按键消抖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
if(GPIO_Pin==KEY1_GPIO_PIN){
if(HAL_GPIO_ReadPin(KEY1_GPIO,KEY1_GPIO_PIN)==KEY1_DOWN_LEVEL){
BEEP_TOGGLE;
LED1_ON;
LED2_ON;
LED3_ON;
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY1_GPIO_PIN);
}
else if(GPIO_Pin==KEY2_GPIO_PIN){
if(HAL_GPIO_ReadPin(KEY2_GPIO,KEY2_GPIO_PIN)==KEY2_DOWN_LEVEL){
BEEP_TOGGLE;
LED1_OFF;
LED2_OFF;
LED3_OFF;
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY2_GPIO_PIN);
}
}
UART
STM32CubeMX 设置
点击 USATR1 –> 设置 MODE 为 Asynchronous (异步通信) –> 设置波特率 –> 设置 GPIO 引脚 (TX\RX) –> 使能中断
HAL 库 UART 函数库
UART 结构体定义:UART_HandleTypeDef huart1;
串口发送/接收函数
1
2
3
4
5
6HAL_UART_Transmit(); //串口发送数据,使用超时管理机制
HAL_UART_Receive(); //串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT() //串口中断模式发送
HAL_UART_Receive_IT(); //串口中断模式接收
HAL_UART_Transmit_DMA(); //串口DMA模式发送
HAL_UART_Transmit_DMA(); //串口DMA模式接收发送数据
如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
UART_HandleTypeDef *huart
UATR 的别名 (如: UART_HandleTypeDef huart1; 别名就是 huart1)*pData
需要发送的数据Size
发送的字节数Timeout
最大发送时间 (超时时间)示例:
1
HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff); //串口发送三个字节数据,最大传输时间0xffff
中断接收数据
串口中断接收,以中断方式接收指定长度数据。
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
UART_HandleTypeDef *huart
UATR 的别名 (如: UART_HandleTypeDef huart1; 别名就是 huart1)*pData
需要发送的数据Size
发送的字节数示例:
1
HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1); //中断接收一个字符,存储到value中
串口中断函数
1
2
3
4
5
6HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); //串口接收一半回调函数(用的较少)
HAL_UART_ErrorCallback(); //串口接收错误函数中断回调函数
串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
UART_HandleTypeDef *huart
UATR 的别名 (如: UART_HandleTypeDef huart1; 别名就是 huart1)中断处理函数
对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
如果接收数据,则会进行接收中断处理函数:
1
2
3
4if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
UART_Receive_IT(huart);
}如果发送数据,则会进行发送中断处理函数:
1
2
3
4
5if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}查询函数
HAL_UART_GetState();
判断 UART 的接收是否结束,或者发送数据是否忙碌示例:
1
while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测 UART 发送结束
HAL 库重定义 printf 函数
在 stm32f4xx_hal.c 中包含 #include <stdio.h>
1 |
|
在 stm32f4xx_hal.c 中重写 fget 和 fput 函数
1 |
|
在 main.c 中添加
1 |
|
1 |
|
之后便可以使用 Printf 函数和 Scanf,getchar 函数
HAL 库 UART 接收中断
在 main.c 中添加下列定义
1 |
|
在 main()主函数中,调用一次接收中断函数
1 | /* USER CODE BEGIN 2 */ |
在 main.c 下方添加中断回调函数
1 | /* USER CODE BEGIN 4 */ |
随后便可在 XCOM(或其他串口助手) 中接收到发送的数据。
HAL 库 UART 接收帧头帧尾
在 main.c 中添加下列定义
1 |
|
在 usart.c 中添加下列定义
1 |
|
在 main() 主函数中,调用一次接收中断函数
1 | /* USER CODE BEGIN 2 */ |
在 usart.c 中重定义中断接收函数
1 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ |
PWM
工作原理
SMT32F1 系列共有 8 个定时器:
高级定时器:TIM1、TIM8 (APB2) (可同时产生 7 路输出)
通用定时器:TIM2 - TIM5 (APB1)
基本定时器:TIM6、TIM7 (不可以产生 PWM 输出)
在 PWM 输出模式下,除了 CNT(计数器当前值)、ARR(自动重装载值)之外,还多了一个值 CCRx(捕获/比较寄存器值)
当 CNT 小于 CCRx 时,TIMx_CHx 通道输出低电平;
当 CNT 等于或大于 CCRx 时,TIMx_CHx 通道输出高电平。
TIMx_ARR 寄存器确定 PWM 频率
TIMx_CCRx 寄存器确定占空比
STM32CubeMX 设置
设置 RCC
设置高速外部时钟 HSE 选择外部时钟源
设置定时器
Channel1~4 就是设置定时器通道的功能 (输入捕获、输出比较、PWM 输出、单脉冲模式)
Mode
选择 PWM 模式 1Pulse
(占空比值) 先给 0Fast Mode PWM
脉冲快速模式 : 和我们配置无关,不使能PWM 极性
: 设置为低电平
注: 由于 LED 是低电平点亮,所以我们把极性设置为 lowPWM 频率
Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)
arr 是计数器值
psc 是预分频值占空比
duty circle = TIM3->CCR1 / arr(单位:%)
TIM3->CCR1 用户设定值
比如:定时器频率 Tclk = 72MHZ arr=499 psc=71
那么 PWM 频率就是 720000 / 500 / 72= 2000Hz,即 2KHz
arr = 499 ,TIM3 -> CCR = 250 则 pwm 的占空比为 50%
改 CCR1 可以修改占空比,修改 arr 可以修改频率
案例:
使能 TIM3 的 PWM Channel1 输出:
1
2
3/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
/* USER CODE END 2 */修改占空比:
1
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwmVal);
其中, &htim3 为 TIM3, TIM_CHANNEL_1 为 PWM Channel1, pwmVal 为占空比,
由于 arr = 499, 则最大占空比为(499 + 1), 即 500 为 100%。
直接将 LED 接入 PWM, 则占空比表示灯泡的亮度, 占空比越大, LED 越亮。