您现在的位置是:首页 >技术交流 >STM32 HAL库开发——入门篇(1)网站首页技术交流
STM32 HAL库开发——入门篇(1)
目录
用的是正点原子STM32F103ZET6精英版
一、GPIO
1.1 什么是GPIO
1.2 GPIO简介
1.2.1 GPIO特点
超频时才会达到最高50MHz
1.2.2 GPIO电气特性
芯片数据手册中,标了FT的就是TTL端口,TTL端口兼容5v和3.3v
不会每个IO口都是25mA,因为最大电流150mA
其他用大电流外设要考虑扩流
1.2.3 GPIO引脚分布
1.3 IO端口基本结构介绍
F1只有输入才选择上下拉,F4、F7、H7输入输出都可以选择上下拉
1.4 GPIO的八种模式
前面4个输入。后面4个输出
IIC:数据SDA,时钟SCL
输入:
输出:
输出控制这里有一个反相器
4中输出模式下,外接一个5v的上拉电阻才可以输出5v的电平
1.5 GPIO的寄存器介绍
CRL+CRH一共64位,16个 引脚,每个脚4个位控制
上拉还是下拉输入模式就要通过ODR寄存器控制
后面是F4,F7,H7的
总的来说,建议使用BSRR寄存器
1.6 通用外设驱动模型(四步法)
1.7 GPIO配置步骤
1.8 编程实战:点灯
0805贴片发光二极管
该电流的计算忽略了发光二极管的内阻,不过内阻也很小 ,得到约2.88mA与官方的5~8mA还是有差距,不过没关系就是亮度暗一些
该led原理图显示led是共阳的,设置开漏输出的话,因为没有外接的上拉电阻,所以只能输出低电平,输出0的时候可以亮,想输出1的时候就是高阻态,PB5断开,led就灭,所以开漏也可以,如果是共阴则不可以 ,共阴如下
// PB5; PE5
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; // F1里面输出是禁止使用上下拉的,设置了也是无效的
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 初始led为灭
}
正点原子跑马灯的代码,看的我太舒服了!
1.9 编程实战:按键点灯
按键有物理上的抖动,所以需要延时5~10ms消抖,一般就10ms了
KEY_UP:下拉输入
KEY0、1、2:上拉输入
精英版没有KEY2
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOE_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_3;
gpio_init_struct.Mode = GPIO_MODE_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
}
/* 按键扫描 */
uint8_t key_scan(void)
{
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0) // 按下了
{
delay_ms(10); // 消抖
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0)
{
while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) == 0); // 松开后才返回1
return 1; // 按键按下了
}
}
return 0; // 按键没有按下
}
按键输入例程:
二、中断
2.1 什么是中断
2.2 NVIC
2.2.1 NVIC基本概念
中断服务函数被定义在中断向量表里面
10个内核中断:
60个外部中断:
2.2.2 NVIC相关寄存器
32*8=256, 一共有240个外部中断,所以有16个位保留了
2.2.3 NVIC工作原理
2.2.4 STM32中断优先级基本概念
2.2.5 STM32中断优先级分组
如果AIRCR设置优先级分组为3,那么IPR中的bit7、6、5是设定抢占的,bit4是设定响应的
如果设置了多次,则已最后一次为准
2.2.6 STM32 NVIC的使用
这里若是 IP[0] 指的就是编号为 0 的WWDG寄存器,共240个,f103只用了60个
2.3 EXTI
2.3.1 EXTI基本概念
因为F1不是互联型开发板,所以没有EXTI19,重点就是0~15
2.3.2 EXTI主要特性
2.3.3 EXTI工作原理
上升沿触发选择寄存器(EXTI_RTSR)
下降沿触发选择寄存器(EXTI_FTSR)
中断屏蔽寄存器(EXTI_IMR)
挂机寄存器(EXTI_PR)
2.4 EXTI和IO映射关系
参考中文参考手册:9.2.5
2.5 如何使用中断
分:EXTI中断 和 其他外设中断
共有7个中断服务函数,0~4有5个,5~9共1个,10~15也共1个,一共7个
2.6 通用外设驱动模型
2.7 HAL库中断回调处理机制介绍
2.8 编程实战:外部中断控制LED
#include "./BSP/EXTI/exti.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"
void EXTI4_IRQHandler(void)
{
printf("1
");
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_4) != RESET)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}
}
/*
按键按下后,触发中断,调用中断服务函数 EXIT4_IRQHandler;
然后调用公共处理函数 HAL_GPIO_EXTI_IRQHandler;
公共处理函数再调用 callback 函数;
此时,按键 KEY0:PE4 已经按下
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(00); // 消抖
printf("2
");
if(GPIO_Pin == GPIO_PIN_4) // 判断是否是我们的中断线触发了中断
{
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5); // LED0,RED
}
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_4);
}
}
void exti_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOE_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_4;
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(EXTI4_IRQn); // 使能
}
注意代码的细节不要出错, 博主就因为EXTI4_IRQHandler函数,写成了EXIT4_IRQHandler,没跑出结果,编译并没有报错还找了好一阵错误
三、串口通信
3.1 数据通信的基本概念
编码方式很多
码元:在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。而这个间隔被称为码元长度。值得注意的是当码元的离散状态有大于2个时(如M大于2个)时,此时码元为M进制码元。(百度百科)
这里说的比较官方,一下子没反应过来。在谢希仁这本书中有一个实例,假定基带信号为101011000110111010…如果直接传送,则每个码元携带的信息是1bit(可以理解为每个二进制都是一个码元),而将上面的信号分为 101 011 000 110 111 010,则视为6个码元,每个码元为3bit,8种表现形式,2^3.这种表现形式就是说接收方要唯一确定这个码元,官方点就是8种不同的振幅或者频率或者相位。你也可以分为1010 1100 0110 1110 10…这种为5个码元,16种表现形式。
总结:码元说白了就是你以怎样的形式去定义你要发的信息,传输多个bit,还是一个码元。
波特率又称码元率,是指每秒传输码元的数目,单位波特(Band)
比特率为每秒传输的比特(bit)数。
前面已经解释过,码元的大小可以自己定义,如果码元大小定义为1时,码元率(波特率)= 比特率。
由此可得波特率和比特率的关系
波特率 = 比特率/每符号含的比特数。(比特率也叫数据率)
3.2 串口(RS-232)
现在的电脑基本没有 DB9 的接口了,而是使用 USB 接口
PC端需要安装 CH340 USB 虚拟串口驱动
LSB:最低有效位,这里就是位0
MSB: 最高有效位,这里就是位1
异步通信没有 SCLK 时钟
3.3 STM32的USART
3.3.1 STM32的USART简介
3.3.2 STM32的USART主要特征
3.3.3 STM32 F1/F4/F7的USART框图
这里的框图是F1的
SW-RX是芯片内部的引脚,不用管他
3.3.4 设置USART/UART波特率(F1)
F4/F7/H7等型号在后面几讲
baud = f_ck / 16 / usartdiv分频
存放在 USART_BRR 寄存器
+0.5 起到四舍五入的作用
fraction 的 *16 就是左移4位的意思 ,以整数的方式保存小数点后面的部分
3.3.5 USART寄存器介绍
如果 CR1 寄存器选择 8 个数据位,那么这里 DR 寄存器就用 0~7 的8位;9位就是 0~8
RXNEIE就是CR1的位5:使能接收缓冲区非空中断
3.4 HAL库外设初始化MSP回调机制
PPP 是指任意外设 ,HAL_PPP_MspInit()实际是个空函数且允许重定义
多个外设共用MSP回调函数时,会有3个 .c 文件,此时就不确定在哪个文件中定义
3.5 HAL库中断回调机制
3.6 USART/UART异步通信配置步骤
3.7 IO引脚复用功能
有重映射的IO才进行重映射解决冲突,不然没有办法
3.8 编程实战:串口接收或发送一个字符
uint8_t g_usart1_rx_flag = 0; /* 串口接收到数据标志 */
uint8_t g_rx_buffer[1]; /* HAL库使用的串口接收缓冲 ,接收1个字节*/
UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 串口X初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
* 这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
* @retval 无
*/
/* 串口1初始化函数 */
void usart_init(uint32_t baudrate)
{
/*UART 初始化设置*/
g_uart1_handle.Instance = USART1; /* USART_UX */
g_uart1_handle.Init.BaudRate = baudrate; /* 波特率 */
g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
g_uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
g_uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
g_uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&g_uart1_handle); /* HAL_UART_Init()会使能UART1 */
/* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, 1); // 开启接收中断
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */
{
/* (1)使能USART1和对应IO时钟;(2)初始化IO;(3)使能USART1中断,设置优先级 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口RX、TX脚时钟 */
__HAL_RCC_USART1_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_9; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 ,输出可以不设置上下拉*/
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10; /* 串口接收引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 复用推挽输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 ,可以不设置速度*/
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
HAL_NVIC_SetPriority(USART1_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
/**
* @brief 串口数据接收回调函数
数据处理在这里进行
* @param huart:串口句柄
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
g_usart1_rx_flag = 1;
}
/**
* @brief 串口X中断服务函数
注意,读取USARTx->SR能避免莫名其妙的错误
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
/* 上述函数会清除中断标志位并调用回调函数,这里会失能 接收中断使能函数,所以下面重新调用一次 */
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, 1);
// while (HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE) != HAL_OK) /* 重新开启中断并接收数据 */
// {
// /* 如果出错会卡死在这里 */
// }
}
3.9 串口实验源码解读
2^14 B = 16KB
实际就不用 printf了,可以测试代码的时候用
四、IWDG独立看门狗
4.1 IWDG简介
RC振荡器的时间精度会差一点
4.2 IWDG工作原理
LSI 就是RC振荡器
4.3 IWDG框图
LSI 输入经过8分频后进入 12位递减计数器
4.4 IWDG寄存器
psc 为预分频系数
prer 就是低三位的值
4.5 IWDG溢出时间计算
4.6 IWDG配置步骤
右边的结构体 F1/F4 和 F7/H7 有些许差异,F7/H7 多一个 Window ,就是窗口寄存器
HAL_IWDG_Init()函数
4.7 编程实战:验证不及时喂狗
#include "./BSP/WDG/wdg.h"
IWDG_HandleTypeDef g_iwdg_handle;
/* IWDG初始化函数 */
void iwdg_init(uint8_t prer, uint16_t rlr) /* prer只占用了3位,重装载值是12个位 */
{
g_iwdg_handle.Instance = IWDG;
g_iwdg_handle.Init.Prescaler = prer;
g_iwdg_handle.Init.Reload = rlr;
HAL_IWDG_Init(&g_iwdg_handle);
}
/* 喂狗函数 */
void iwdg_feed(void)
{
HAL_IWDG_Refresh(&g_iwdg_handle);
}
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟为72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
iwdg_init(IWDG_PRESCALER_32, 1250); /* 1s的溢出时间,rlr=1*40000/32=1250 */
printf("您还没喂狗,请及时喂狗!!!
");
while (1)
{
delay_ms(1000);
iwdg_feed();
printf("已经喂狗!
");
}
}
五、WWDG窗口看门狗
5.1 WWDG简介
窗口看门狗时钟来自系统总线的时钟
5.2 WWDG工作原理
窗口下限值是固定的,窗口上限值和计数器初始值是由用户编程决定的 ,计数值低于窗口上限值时才可以进行喂狗,若在窗口上限值上面就喂狗,系统会复位
5.3 WWDG框图
T6位时判断是否从64到63,所以是6位递减计数器
5.4 WWDG寄存器
5.5 WWDG超时时间计算
5.6 WWDG配置步骤
5.7 编程实战:验证窗口看门狗功能
#include "./BSP/WDG/wdg.h"
#include "./BSP/LED/led.h"
WWDG_HandleTypeDef g_wwdg_handle;
/* 窗口看门狗初始化函数 */
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
{
g_wwdg_handle.Instance = WWDG;
g_wwdg_handle.Init.Counter = tr;
g_wwdg_handle.Init.Window = wr;
g_wwdg_handle.Init.Prescaler = fprer;
g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE;
HAL_WWDG_Init(&g_wwdg_handle);
}
/* WWDG MSP回调函数 */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
__HAL_RCC_WWDG_CLK_ENABLE();
HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3);
HAL_NVIC_EnableIRQ(WWDG_IRQn);
}
/* WWDG中断服务函数 */
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&g_wwdg_handle);
}
/* WWDG提前唤醒回调函数 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
//delay_ms(1); // 会超时,1个周期是0.9ms
HAL_WWDG_Refresh(&g_wwdg_handle); // 在 0x40=64的时候喂狗
LED1_TOGGLE();
}
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/WDG/wdg.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
usart_init(115200);
if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET)
{
printf("窗口看门狗复位
");
__HAL_RCC_CLEAR_RESET_FLAGS();
}
else
{
printf("外部复位
");
}
delay_ms(500);
printf("请在窗口期内喂狗
");
wwdg_init(0x7F, 0X5F, WWDG_PRESCALER_8);
while(1)
{
delay_ms(57);
//HAL_WWDG_Refresh(&g_wwdg_handle);
// delay_ms(90);
// HAL_WWDG_Refresh(&g_wwdg_handle);
// LED1_TOGGLE();
}
}