目录
一、开发环境的安装、工程搭建和烧录
1.Keil5
2.ST-Link
烧录测试
STlink接线
STM32F1 模板工程
Keil烧录
3.STM32CubeMX
安装
快速搭建工程
二、STM32简介
单片机
STM系列单片机命名规则
STM32F103C8T6:
编辑STM32F103C8T6单片机简介
寄存器和各种库的对比
1. 寄存器
2. 标准库
3. HAL库
4.LL库
三、通用输入输出端口GPIO
1.简介
定义
命名规则
内部框架图
推挽输出与开漏输出
2.简单实现亮灯交替闪烁效果(GPIO写、HAL_Delay)
3.按键点灯(GPIO读、轮询法)
多此一举的宏定义和函数:
四、复位、时钟、中断
复位
系统复位
电源复位
备份区复位
时钟控制
什么是时钟?
时钟来源
如何使用CubeMX配置时钟
五、中断和事件
中断概述
什么是中断?
什么是EXTI?
什么是优先级?
抢占优先级和响应优先级的区别:
什么是优先级分组?
什么是NVIC?
什么是中断向量表?
五、按键点亮LED灯(中断法)
1. 配置时钟
2. 配置GPIO口
3. 使能中断
4. 配置工程
按键点亮LED灯
加上消抖
个人情况:
已经有C语言、C51基础,并玩过各种外设,现需要快速上手使用STM32,所以选择了学习cubeMX+HAL库开发。
软件: Keil5 和 STM32CubeMX
使用 Keil4 写 STM32 代码其实也是可以,但需要很复杂的配置,不建议新手操作。比较推荐 Keil5 编写 STM32 ,只需要一些简单的设置就可以上手,对新手友好。
等待下载固件包,漫长的过程,也可以用已有的固件包直接导入。
十几块买一个,插上电脑,打开设备管理器查看:
可以看到已经连接,点更新驱动程序。选择驱动程序所在文件夹,完成更新
驱动官网下载(慢)https://www.st.com/en/development-tools/stsw-link009.html
在Keil中配置
更新一下,device connect如果不成功,重新插拔一下
可以找个模板程序,用STlink烧录测速一下。
在Keil里点击编译,烧录,成功后,板子复位一下就可以看到效果
作用:通过界面的方式,快速生成工程文件。
下载:官网(慢)https://www.st.com/zh/development-tools/stm32cubemx.html#overview
安装:一路下一步,建议不要安装在C盘
配置:更新固件包位置(比较大,默认在C盘,可以更改到其它盘)
help ---> update settings --> Firmware Repository
按图走
根据原理图可知LED1、2分别对应PB8、PB9
设置PB8、PB9为GPIO输出口,默认低电平,灯会亮
可以看到串口会自动配置
生成文件
打开Keil烧录即可
单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处
理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功
能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成
到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。
ST -- 意法半导体
M -- Microelectronics 微电子
32 -- 总线宽度
F103 -- STM32 基础型
C -- 48引脚(&49)
8 -- 64kb闪存
T -- QFP封装
6 -- 温度范围-40 ~ 85
项目 | 介绍 |
内核 | Cortex-M3 |
Flash | 64K x 8bit |
SRAM | 20K x 8bit |
GPIO | 37个GPIO,分别为PA0-PA15、PBO-PB15、PC13-PC15、PDO-PD1 |
ADC | 2个12bit ADC合计12路通道,外部通道: PAO到PA7+PBO到PB1内部通道: 温度传感器通道ADC Channel 16和内部参考电压通道ADC Channel 17 |
定时 器/计 数器 |
4个16bit定时器/计数器,分别为TIM1、TIM2、TIM3、TIM4TM1带死区插入,常用于产生PWM控制电机 |
看门狗定时器 |
2个看门狗定时器 (独立看门狗IWDG、窗口看门狗WWDG) |
滴答定时器 | 1个24bit向下计数的滴答定时器systick |
工作电压、温度 | 2V3.6V、-40°C85°C |
通信串 口 |
2 * IIC,2 * SPI,3 * USART,1 * CAN |
系统时钟 | 内部8MHz时钟HSI最高可倍频到64MHZ,外部8MHZ时钟HSE最高可倍频到 72MHZ |
寄存器众多,需要经常翻阅芯片手册,费时费力;
更大灵活性,可以随心所欲达到自己的目的;
深入理解单片机的运行原理,知其然更知其所以然。
将寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用
每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的;
配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能;
大大降低单片机开发难度,但是在不同芯片间不方便移植。
ST公司目前主力推的开发方式,新的芯片已经不再提供标准库;
为了实现在不同芯片之间移植代码;
为了兼容所有芯片,导致代码量庞大,执行效率低下。
弥补了HAL库效率低的问题。
GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
简单来说我们可以控制GPIO引脚的电平变化,达到我们的各种目的。
组编号+引脚编号
组编号:GPIOA, GPIOB, GPIOC, GPIOD .. GPIOG
引脚编号:0,1,2,3,4...15
组合起来:
PA0, PA1, PA2 .. PA15
PB0, PB1, PB2 .. PB15
PC0, PC1, PC2 .. PC15
...
有一些特殊功能的引脚是不能用作IO的。
下图来源于官方参考手册,了解即可。
内部结构图
推挽输出: 可以真正能真正的输出高电平和低电平
开漏输出: 开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动
在生成的代码中可以看看大概都是什么内容
Keil5中按F12可以溯源(要先编译)
主函数中找到
找到cubeMXGPIO初始化函数,
里面有HAL库的GPIO写函数,阅读得知前两个参数是选择IO口,
第三个RESET表示低电平,而SET表示高电平
在main函数while循环里就可以复制并改写这段代码,
用HAL的delay函数延时500ms,两口SET、RESET交替,两LED交替闪烁
while (1)
{
/* USER CODE END WHILE */
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
继续溯源,可以看一些GPIO相关源码
常用的GPIO HAL库函数:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinStatePinState);
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
结构体 GPIO_InitTypeDef 定义:
typedef struct
{
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
} GPIO_InitTypeDef;
位于A0、A1,
按下变为低电平。
设置GPIO_Input,其它配置一样,生成
HAL_GPIO_ReadPin 源码:
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
GPIO_PinState bitstatus;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
{
bitstatus = GPIO_PIN_SET;
}
else
{
bitstatus = GPIO_PIN_RESET;
}
return bitstatus;
}
根据 HAL_GPIO_ReadPin 函数,写代码,添加一个 while() 用于软件消抖。
while (1)
{
/* USER CODE END WHILE */
//循环检测A0是否低电平
if (HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
{
//软件按钮消抖:检测如果一直按住,直到松手再继续
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
//B8状态翻转
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
//循环检测A1是否低电平
if (HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
{
//软件按钮消抖:检测如果一直按住,直到松手再继续
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
//B9状态翻转
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
/* USER CODE BEGIN 3 */
}
或者也可以定义一个返回按钮状态的函数,用 unsigned char 型,查看源码可知为 uint8_t
/* 7.18.1.1 */
/* exact-width signed integer types */
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed __INT64 int64_t;
/* exact-width unsigned integer types */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;
宏定义、函数:
注意宏定义结尾不能+“ ; ” ,
函数只有一个return,这是一种规范形式
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define KEY_ON 0
#define KEY_OFF 1
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t Key_scan(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
uint8_t Key_Status;
if (HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET)
{
while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET);
Key_Status = KEY_ON;
}
else
{
Key_Status = KEY_OFF;
}
return Key_Status;
}
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
if (Key_scan(GPIOA, GPIO_PIN_0) == KEY_ON)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
if (Key_scan(GPIOA, GPIO_PIN_1) == KEY_ON)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
当发生以下任一事件时,产生一个系统复位:
1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位
当以下事件中之一发生时,产生电源复位:
1. 上电/掉电复位(POR/PDR复位)
2. 从待机模式中返回
备份区域拥有两个专门的复位,它们只影响备份区域。
当以下事件中之一发生时,产生备份区域复位。
1. 软件复位,备份区域复位可由设置备份域控制寄存器 (RCC_BDCR)(见6.3.9节)中的
BDRST位产生。
2. 在VDD和VBAT两者掉电的前提下,VDD或VBAT上电将引发备份区域复位。
时钟打开,对应的设备才会工作。
三种不同的时钟源可被用来驱动系统时钟(SYSCLK)
HSI振荡器时钟(高速内部时钟)
HSE振荡器时钟(高速外部时钟)
PLL时钟(锁相环倍频时钟)
二级时钟源:
40kHz低速内部RC(LSIRC)振荡器
32.768kHz低速外部晶体(LSE晶体)
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的
程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
外部中断/事件控制器(EXTI)管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一
个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可以实现对每个中断/事
件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不
同。
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软
件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传
输,属于硬件级的。
EXTI初始化结构体:
typedef struct
{
uint32_t Line; /*!< The Exti line to be configured. This parameter
can be a value of @ref EXTI_Line */
uint32_t Mode; /*!< The Exit Mode to be configured for a core.
This parameter can be a combination of @ref EXTI_Mode */
uint32_t Trigger; /*!< The Exti Trigger to be configured. This parameter
can be a value of @ref EXTI_Trigger */
uint32_t GPIOSel; /*!< The Exti GPIO multiplexer selection to be configured.
This parameter is only possible for line 0 to 15. It
can be a value of @ref EXTI_GPIOSel */
} EXTI_ConfigTypeDef;
中断/事件线:
#define EXTI_LINE_0 (EXTI_GPIO | 0x00u) /*!< External interrupt line 0 */
#define EXTI_LINE_1 (EXTI_GPIO | 0x01u) /*!< External interrupt line 1 */
#define EXTI_LINE_2 (EXTI_GPIO | 0x02u) /*!< External interrupt line 2 */
#define EXTI_LINE_3 (EXTI_GPIO | 0x03u) /*!< External interrupt line 3 */
#define EXTI_LINE_4 (EXTI_GPIO | 0x04u) /*!< External interrupt line 4 */
#define EXTI_LINE_5 (EXTI_GPIO | 0x05u) /*!< External interrupt line 5 */
#define EXTI_LINE_6 (EXTI_GPIO | 0x06u) /*!< External interrupt line 6 */
#define EXTI_LINE_7 (EXTI_GPIO | 0x07u) /*!< External interrupt line 7 */
#define EXTI_LINE_8 (EXTI_GPIO | 0x08u) /*!< External interrupt line 8 */
#define EXTI_LINE_9 (EXTI_GPIO | 0x09u) /*!< External interrupt line 9 */
#define EXTI_LINE_10 (EXTI_GPIO | 0x0Au) /*!< External interrupt line 10 */
#define EXTI_LINE_11 (EXTI_GPIO | 0x0Bu) /*!< External interrupt line 11 */
#define EXTI_LINE_12 (EXTI_GPIO | 0x0Cu) /*!< External interrupt line 12 */
#define EXTI_LINE_13 (EXTI_GPIO | 0x0Du) /*!< External interrupt line 13 */
#define EXTI_LINE_14 (EXTI_GPIO | 0x0Eu) /*!< External interrupt line 14 */
#define EXTI_LINE_15 (EXTI_GPIO | 0x0Fu) /*!< External interrupt line 15 */
#define EXTI_LINE_16 (EXTI_CONFIG | 0x10u) /*!< External interrupt line 16 Connected to the PVD Output */
#define EXTI_LINE_17 (EXTI_CONFIG | 0x11u) /*!< External interrupt line 17 Connected to the RTC Alarm event */
#if defined(EXTI_IMR_IM18)
#define EXTI_LINE_18 (EXTI_CONFIG | 0x12u) /*!< External interrupt line 18 Connected to the USB Wakeup from suspend event */
#endif /* EXTI_IMR_IM18 */
#if defined(EXTI_IMR_IM19)
#define EXTI_LINE_19 (EXTI_CONFIG | 0x13u) /*!< External interrupt line 19 Connected to the Ethernet Wakeup event */
#endif /* EXTI_IMR_IM19 */
EXTI模式:产生中断、产生事件
#define EXTI_MODE_NONE 0x00000000u
#define EXTI_MODE_INTERRUPT 0x00000001u
#define EXTI_MODE_EVENT 0x00000002u
触发类型:上升沿、下降沿
#define EXTI_TRIGGER_NONE 0x00000000u
#define EXTI_TRIGGER_RISING 0x00000001u
#define EXTI_TRIGGER_FALLING 0x00000002u
#define EXTI_TRIGGER_RISING_FALLING (EXTI_TRIGGER_RISING | EXTI_TRIGGER_FALLING)
EXTI控制:
使能 EXTI ,一般都是使能, ENABLE
高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第0组:所有4位用于指定响应优先级
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级
第4组:所有4位用于指定抢占式优先级
STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断的管理 。
NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK不是由NVIC控制的。
每个中断源都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程序入口的跳转指令构成一张表,称为中断向量跳转表。
同上
A0、A1 设置为 EXIT0、EXIT1,
B0、B1 改为默认高电平 GPIO_Output
下降沿触发
使能中断
同上
生成的代码:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
/*Configure GPIO pins : PA0 PA1 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
}
溯源可以找到一个虚函数(weak) 可以重写它
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case GPIO_PIN_0:
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
break;
case GPIO_PIN_1:
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
}
/* USER CODE END 0 */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
HAL_Delay(20);//延时再判断,排除抖动
case GPIO_PIN_0:
//消抖:检测A0是否低电平
if (HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);//B8状态翻转
break;
case GPIO_PIN_1:
//消抖:检测A1是否低电平
if (HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);//B9状态翻转
break;
}
}
/* USER CODE END 0 */