一、GPIO
1.1 什么是GPIO
1.2 GPIO简介
1.2.1 GPIO特点
1.2.2 GPIO电气特性
1.2.3 GPIO引脚分布
1.3 IO端口基本结构介绍
1.4 GPIO的八种模式
1.5 GPIO的寄存器介绍
1.6 通用外设驱动模型(四步法)
1.7 GPIO配置步骤
1.8 编程实战:点灯
1.9 编程实战:按键点灯
二、中断
2.1 什么是中断
2.2 NVIC
2.2.1 NVIC基本概念
2.2.2 NVIC相关寄存器
2.2.3 NVIC工作原理
2.2.4 STM32中断优先级基本概念
2.2.5 STM32中断优先级分组
2.2.6 STM32 NVIC的使用
2.3 EXTI
2.3.1 EXTI基本概念
2.3.2 EXTI主要特性
2.3.3 EXTI工作原理
2.4 EXTI和IO映射关系
2.5 如何使用中断
2.6 通用外设驱动模型
2.7 HAL库中断回调处理机制介绍
2.8 编程实战:外部中断控制LED
三、串口通信
3.1 数据通信的基本概念
3.2 串口(RS-232)
3.3 STM32的USART
3.3.1 STM32的USART简介
3.3.2 STM32的USART主要特征
3.3.3 STM32 F1/F4/F7的USART框图
3.3.4 设置USART/UART波特率(F1)
3.3.5 USART寄存器介绍
3.4 HAL库外设初始化MSP回调机制
3.5 HAL库中断回调机制
3.6 USART/UART异步通信配置步骤
3.7 IO引脚复用功能
3.8 编程实战:串口接收或发送一个字符
3.9 串口实验源码解读
四、IWDG独立看门狗
4.1 IWDG简介
4.2 IWDG工作原理
4.3 IWDG框图
4.4 IWDG寄存器
4.5 IWDG溢出时间计算
4.6 IWDG配置步骤
4.7 编程实战:验证不及时喂狗
五、WWDG窗口看门狗
5.1 WWDG简介
5.2 WWDG工作原理
5.3 WWDG框图
5.4 WWDG寄存器
5.5 WWDG超时时间计算
5.6 WWDG配置步骤
5.7 编程实战:验证窗口看门狗功能
5.8 IWDG和WWDG的区别
用的是正点原子STM32F103ZET6精英版
芯片数据手册中,标了FT的就是TTL端口,TTL端口兼容5v和3.3v
不会每个IO口都是25mA,因为最大电流150mA
其他用大电流外设要考虑扩流
F1只有输入才选择上下拉,F4、F7、H7输入输出都可以选择上下拉
前面4个输入。后面4个输出
IIC:数据SDA,时钟SCL
输入:
输出控制这里有一个反相器
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为灭
}
正点原子跑马灯的代码,看的我太舒服了!
按键有物理上的抖动,所以需要延时5~10ms消抖,一般就10ms了
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; // 按键没有按下
}
按键输入例程:
中断服务函数被定义在中断向量表里面
32*8=256, 一共有240个外部中断,所以有16个位保留了
如果AIRCR设置优先级分组为3,那么IPR中的bit7、6、5是设定抢占的,bit4是设定响应的
这里若是 IP[0] 指的就是编号为 0 的WWDG寄存器,共240个,f103只用了60个
因为F1不是互联型开发板,所以没有EXTI19,重点就是0~15
上升沿触发选择寄存器(EXTI_RTSR)
中断屏蔽寄存器(EXTI_IMR)
挂机寄存器(EXTI_PR)
参考中文参考手册:9.2.5
共有7个中断服务函数,0~4有5个,5~9共1个,10~15也共1个,一共7个
#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\r\n");
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\r\n");
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,没跑出结果,编译并没有报错还找了好一阵错误
码元:在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。而这个间隔被称为码元长度。值得注意的是当码元的离散状态有大于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时,码元率(波特率)= 比特率。
由此可得波特率和比特率的关系
波特率 = 比特率/每符号含的比特数。(比特率也叫数据率)
现在的电脑基本没有 DB9 的接口了,而是使用 USB 接口
LSB:最低有效位,这里就是位0
MSB: 最高有效位,这里就是位1
异步通信没有 SCLK 时钟
这里的框图是F1的
SW-RX是芯片内部的引脚,不用管他
F4/F7/H7等型号在后面几讲
+0.5 起到四舍五入的作用
fraction 的 *16 就是左移4位的意思 ,以整数的方式保存小数点后面的部分
如果 CR1 寄存器选择 8 个数据位,那么这里 DR 寄存器就用 0~7 的8位;9位就是 0~8
PPP 是指任意外设 ,HAL_PPP_MspInit()实际是个空函数且允许重定义
多个外设共用MSP回调函数时,会有3个 .c 文件,此时就不确定在哪个文件中定义
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) /* 重新开启中断并接收数据 */
// {
// /* 如果出错会卡死在这里 */
// }
}
RC振荡器的时间精度会差一点
prer 就是低三位的值
右边的结构体 F1/F4 和 F7/H7 有些许差异,F7/H7 多一个 Window ,就是窗口寄存器
HAL_IWDG_Init()函数
#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("您还没喂狗,请及时喂狗!!!\r\n");
while (1)
{
delay_ms(1000);
iwdg_feed();
printf("已经喂狗!\r\n");
}
}
窗口下限值是固定的,窗口上限值和计数器初始值是由用户编程决定的 ,计数值低于窗口上限值时才可以进行喂狗,若在窗口上限值上面就喂狗,系统会复位
#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("窗口看门狗复位\r\n\r\n");
__HAL_RCC_CLEAR_RESET_FLAGS();
}
else
{
printf("外部复位\r\n");
}
delay_ms(500);
printf("请在窗口期内喂狗\r\n");
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();
}
}