STM32单片机学习(3)

1. GPIO —D LED 点灯

1.1 关于GPIO

  • GPIO(General-Purpose IO ports,通用输入/输出接口),用于感知外界信号(输入模式)和控制外部设备(输出模式),如图所示的STM32F103ZET6芯片四周的细引脚就是GPIO。

在这里插入图片描述

  • 在嵌入式开发中,经常需要用到一些外部功能模块,比如LED、按键、蜂鸣器、温度传感器等,这些外设模块都比较简单,只需要MCU的GPIO与模块连接,控制引脚输出/读取高低电平即可。还有一些外部功能模块,需要多个引脚构成的“协议”进行通信,比如UART、I 2 C、SPI接口等。
  • 如今的MCU大都采用引脚复用技术,即一个GPIO,即可以直接控制其输出高低电平,也可以设置为某个协议的引脚之一,比如I 2 C的时钟信号引脚SCK。此外,有些MCU的引脚,还能设置为ADC模式读取模拟信号,或者设置为DAC模式输出模拟信号。

1.1.1STM32的GPIO

  • STM32F103ZET6一共有144个引脚,除去电源引脚、晶振时钟引脚、复位引脚、启动选择引脚、程序下载引脚(大部分为最小系统必须引脚),剩下的则是GPIO引脚。
  • STM32将这些众多按GPIOx(x为A、B、C等)分组,每组包含16个引脚,编号为0~15。STM32F103ZET6就有7组GPIO,每组16个引脚,即112个GPIO引脚。
  • 下图为STM32F103系列GPIO的基本结构,左侧连接MCU内部,中间上半部分为输入,中间下半部分为输出,右侧为MCU引出的外设I/O引脚。

在这里插入图片描述

1.1.2 GPIO 工作模式

  • STM32F103系列的I/O引脚共有8种工作模式,其中输出模式有四种:推挽输出、开漏输出、复用推挽输出、复用开漏输出;输入模式有四种:上拉输入、下拉输入、浮空输入、模拟输入。
  1. 推挽输出(Push-Pull ,PP )
  • 推挽结构由两个MOS管按互补对称的方式连接,任意时刻总是其中一个三极管导通,另一个三极管截止。如上图中①所示,内部由一个P-MOS管和一个N-MOS管组成,两个MOS管的栅极(Gate,G)接到了左侧“输出控制”,漏极(Drain,D)接到外部输出,P-MOS管的源极(Source,S)接到V DD (3.3V),N-MOS管的源极接到V ss (0V)。
  • MOS管作为开关使用,“输出控制”向两个MOS管栅极加一定电压,P-MOS管源极和漏极之间导通,V DD 经过P-MOS管的S->G->D输出,N-MOS管处于高阻态(电阻很大,近似开路),整体对外为高电平;“输出控制”取消向两个MOS管栅极施加电压,P-MOS管源极和栅极截止,P-MOS管处于高阻态,N-MOS管源极和漏极导通,整体对外为低电平。
  • 推挽模式,让“输出控制”变为了V DD /V ss 输出,使得输出电流增大,提高了输出引脚的驱动能力,提高了电路的负载能力和开关的动作速度。
  1. 开漏 开漏 输出(Open-Drain ,OD )
  • 开漏模式下,“输出控制”不会控制P-MOS管,“输出控制”只会向N-MOS管栅极加一定电压,两个
    MOS管都处于截止状态,两个漏极处于悬空状态,称之为漏极开路。“输出控制”取消栅极的施加电压,P-MOS管依旧处于高阻态,N-MOS管导通,整体对外为低电平。
  • 开漏输出模式可以等效将图下中灰色框的P-MOS管看作不存在。即该模式下只能输出低电平,若要输出高电平,则需要外接电阻,所接的电阻称为上拉电阻,此时输出电平取决于此时上拉电阻的外部电源电压情况,如图下中蓝色框的外部电路。
    在这里插入图片描述
  • 推挽输出模式可以直接输出高电平,开漏输出模式需要外接上拉电阻才能输出高电平,但开漏输出拥有一些推挽输出不具有的特性:
    ①利用外部电路驱动能力。“输出控制”只需要提供一个很小的栅极驱动电流,VCC经过上拉电阻为外部负载提高驱动电流;
    ②实现电平转换。推挽输出模式由V DD 提供,即只能提供3.3V电平。使用开漏输出模式后,VCC可以为5V,从而实现了电平转换的效果。
    ③方便实现“逻辑与”功能。多个开漏的引脚可以直接并在一起使用,统一接一个合适的上拉电阻,就可以实现“逻辑与”关系,即当所有引脚均输出高电平时,输出才为高电平,若任一引脚输出低电平,则输出低电平。在I 2 C、SMBUS等总线电路中经常会用到。
  1. 复用 复用 功能 推挽/ 开漏输出(Alternate Function ,AF )
    GPIO引脚除了作为通用输入/输出引脚使用外,还可以作为片上外设(USART、I 2 C、SPI等)专用引脚,即一个引脚可以有多种用途,但同一时刻一个引脚只能使用复用功能中的一个。
    当引脚设置为复用功能时,可选择复用推挽输出模式或复用开漏输出模式,在设置为复用开漏输出模式时,需要外接上拉电阻。
  2. 上拉输入模式(Input Pull-up )
    V DD 经过开关、上拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入高电平。该模式的典型应用就是外接按键,当没有按键按下时候,MCU的引脚为确定的高电平,当按键按下时候,引脚电平被拉为低电平。
  3. 下拉输入模式(Input Pull-down )
    V ss 经过开关、下拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入低电平。
  4. 浮空输入模式(Floating Input )
    两个上/下拉电阻开关均断开,既无上拉也无下拉,I/O引脚直接连接TTL肖特基触发器,此时I/O引脚浮空,读取的电平是不确定的,外部信号是什么电平,MCU引脚就输入什么电平。MCU复位上电后,默认为浮空输入模式。
  5. 模拟输入模式(Analog mode )
    两个上/下拉电阻开关均断开,同时TTL肖特基触发器开关也断开,引脚信号直接连接模拟输入,实现对外部信号的采集。

1.1.3 GPIO 输出速度

  • STM32的I/O引脚工作在输出模式下时,需要配置I/O引脚的输出速度。该输出速度不是输出信号的速度,而是I/O口驱动电路的响应速度。
  • STM32提供三个输出速度:2MHz、10MHz、50MHz。实际开发中需要结合实际情况选择合适的相应速度,以兼顾信号的稳定性和低功耗。通常,当设置为高速时,功耗高、噪声大、电磁干扰强;当设备为低速时,功耗低、噪声小、电磁干扰弱。
  • 通常简单外设,比如LED灯、蜂鸣器灯,建议使用2MHz的输出速度,而复用为I 2 C、SPI等通信信号引脚时,建议使用10MHz或50MHz以提高响应速度。

1.2 硬件设计

  • LED(Light Emitting Diode,发光二极管),是一种能够将电能转化为可见光的半导体器件,当给P极施加正向电压后,空穴和自由电子在P-N结复合,辐射出光子而发光。
    • 如图 1 所示为目前市面常见的LED灯,第一个是插件LED灯,第二个是贴片LED灯,第三个是贴片三色LED灯,由三个红、绿、蓝小灯组成,后面可以通过PWM实验控制每个灯亮度,实现颜色组合,更具可玩性。
  • 图 1 常见 LED 灯
    在这里插入图片描述

-如图 2 所示为开发板三色LED灯部分的原理图,LED灯的正极直接连接了VDD_3V3,LED灯的负极分别连接了三个GPIO引脚,红色LED连接的PB0,绿色LED连接的PB1,蓝色LED连接的PB5,只需要控制PB0、PB1、PB5为相应低电平,即可点亮对应LED灯,输出为高电平时熄灭对应LED灯。

  • 图 2 LED灯部分的原理图
    在这里插入图片描述

1.4 软件设计

  • 软件设计思路
  • 实验目的:体验嵌入式的“Hello Word”,点亮LED灯。
  1. 选择LED对应的GPIO;
  2. 使能所选择GPIO的时钟;
  3. 配置其为上拉输出模式;
  4. 控制其输出高或低来控制LED的亮与灭;
  • 软件设计讲解
  1. GPIO 选择与接口定义
    宏定义GPIO接口的作用是,当实际设计中的LED对应的GPIO发生变化时,只需在宏定义处改变GPIO的值即可完成对整个LED设计的修改,这样就增强了可移植性。
  • 引脚宏定义(driver_led.h)代码如下
/*********************
* 引脚宏定义
**********************/
#define R_LED_GPIO_PIN GPIO_PIN_0
#define R_LED_GPIO_PORT GPIOB
#define R_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define G_LED_GPIO_PIN GPIO_PIN_1
#define G_LED_GPIO_PORT GPIOB
#define G_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define B_LED_GPIO_PIN GPIO_PIN_5
#define B_LED_GPIO_PORT GPIOB
#define B_LED_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()

根据原理图可知,RGB灯三色对应的引脚分别是:红灯-PB0、绿灯-PB1、蓝灯-PB5。因此得到代码段的引脚和引脚对应时钟使能函数的宏定义,当用户工程LED引脚发生变化时,只需在此处做出修改即可。然后是对三个GPIO的输出函数进行接口宏定义以方便移植与阅读,如下代码所示。

  • LED 亮灭函数宏定义(driver_led.h)
/*
* LED 亮灭函数宏定义
*/
#define ON GPIO_PIN_RESET
#define OFF GPIO_PIN_SET
#define RLED(flag) HAL_GPIO_WritePin(R_LED_GPIO_PORT, R_LED_GPIO_PIN, flag)
#define GLED(flag) HAL_GPIO_WritePin(G_LED_GPIO_PORT, G_LED_GPIO_PIN, flag)
#define BLED(flag) HAL_GPIO_WritePin(B_LED_GPIO_PORT, B_LED_GPIO_PIN, flag)

HAL库中,对GPIO的输出控制函数是:

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
  • 第一个参数 GPIOx表示GPIOA/B/C/D/E…/H中某一组端口,此处我们的实验是GPIOB,但是为方便移植我们使用宏定义的端口R_LED_GPIO_PORT、G_LED_GPIO_PORT、B_LED_GPIO_PORT;
  • 第二个参数GPIO_Pin表示在某组端口中的某一个引脚,与选择端口类似,我们选择已宏定义好的R_LED_GPIO_PIN、G _LED_GPIO_PIN、B _LED_GPIO_PIN;
  • 第三个参数PinState表示对这个IO控制输出的状态,是一个枚举类型,包含两个成员:GPIO_PIN_RESET和GPIO_PIN_SET。因为低电平亮灯,所以定义ON对应GPIO_PIN_RESET,OFF对应GPIO_PIN_SET。
  1. GPIO 的初始化
  • 当选择好LED对应的GPIO后,还需要对其进行初始化,以完成对这些GPIO时钟的使能,工作模式的选择以及输出速度的设置。
    GPIO 初始化(driver_led.c)代码如下:
/*
* 函数名:void LedGpioInit(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:初始化 LED 的引脚,配置为上拉推挽输出
*/

void LedGpioInit(void)
{
// 定义 GPIO 的结构体变量
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能 LED 的 GPIO 对应的时钟
R_LED_GPIO_CLK_EN();
G_LED_GPIO_CLK_EN();
B_LED_GPIO_CLK_EN();

GPIO_InitStruct.Pin = R_LED_GPIO_PIN | G_LED_GPIO_PIN | B_LED_GPIO_PIN; // 选择 LED 的引脚
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引脚反转速度设置为快

// 初始化引脚配置
HAL_GPIO_Init(R_LED_GPIO_PORT, &GPIO_InitStruct);

// 默认 LED 灭:OFF-灭,ON-亮
RLED(OFF);
GLED(OFF);
BLED(OFF);
}
  • 在此函数中,首先定义了一个结构体变量GPIO_InitStruct,先看看这个结构体中都有哪些成员。
    结构体 GPIO_InitStruct 定义(stm32f1xx_hal_gpio.h)代码如下:
1 /**
2 * @brief GPIO Init structure definition
3 */
4 typedef struct
5 {
6 uint32_t Pin; 		/*!< Specifies the GPIO pins to be configured.
7                	 	This parameter can be any value of @ref GPIO_pins_define */
8 
9 uint32_t Mode; 		/*!< Specifies the operating mode for the selected pins.
10					 	This parameter can be a value of @ref GPIO_mode_define */
11
12 uint32_t Pull; 		/*!< Specifies the Pull-up or Pull-Down activation for the selected  pins.
13					 	This parameter can be a value of @ref GPIO_pull_define */
14
15 uint32_t Speed; 	/*!< Specifies the speed for the selected pins.
16					 	This parameter can be a value of @ref GPIO_speed_define */
17 } GPIO_InitTypeDef;
  • 6行:第一个成员pin是引脚选择;
  • 9行:第二个成员mode是模式选择:输出、输入、复用、中断、事件;
  • 12行:第三个成员是上拉、下拉、悬空选择;
  • 15行:第四个成员是输出速度选择;
  • 当我们不清楚这些成员可以设置成哪些值时,可以看到每个成员的注释的最后有个@ref xxx,比如第7行末尾的“@ref GPIO_pins_define”,我们只需要选中这个GPIO_pins_define,然后按Ctrl+f寻找这个GPIO_pins_define,就会找到例如pin如下宏定义。
  • GPIO 引脚宏定义(stm32f1xx_hal_gpio.h)代码如下:
/** @defgroup GPIO_pins_define GPIO pins define
* @{
*/
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */

#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
  • 这里只需要选择其中需要的宏定义即可。然后使用三个宏定义的时钟使能函数使能了选择的GPIO的时钟。用上述设置GPIO_InitStruct成员讲解的方法设置每个成员的值之后,使用下述函数对选择的某组端口的GPIO引脚进行初始化。
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
  • 此处我们选择的是红灯的端口,但是在程序中我们的引脚选择是红绿蓝三个引脚都选择且进行了初始化,且红绿蓝三个灯所对应的端口均是GPIO B,所以实际上这个初始化函数是将三个LED的引脚都完成了初始化。
  1. GPIO 输出控制
  • 在初始化函数的末尾,我们使用了宏定义的IO控制接口控制三个IO输出高电平,即让三灯均呈熄灭状态:
    控制 LED 灯熄灭(driver_led.c)代码如下:
// 默认 LED 灭:OFF-灭,ON-亮
RLED(OFF);
GLED(OFF);
BLED(OFF);
  1. HAL 库延时
    HAL库使用系统滴答定时器(此定时器在后序章节中详细讲解)封装了一个延时函数:
__weak void HAL_Delay(uint32_t Delay)

在不对系统滴答定时器进行重新定义的情况下,此延时函数的效果是ms级,即延时Delay个毫秒。

  1. 整体控制逻辑
    LED 灯主函数(main.c)代码如下:
1 int main(void)
2 {
3 		// 初始化 HAL 库函数必须要调用此函数
4 		HAL_Init();
5 		// 系统时钟即 AHB/APB 时钟配置
6 		SystemClock_Config();
7
8 		// 初始化 LED
9 		LedGpioInit();
10
11  	// 点亮三色灯
12 		RLED(ON);
13		HAL_Delay(1000);
14 		RLED(OFF);
15
16 		GLED(ON);
17 		HAL_Delay(1000);
18 		GLED(OFF);
19
20 		BLED(ON);
21		HAL_Delay(1000);
22		BLED(OFF);
23
24	 while(1)
25 	{
26 		RLED(ON);
27 		HAL_Delay(1000);
28		RLED(OFF);
29 		HAL_Delay(1000);
30   }
31 }
  • 4行:对HAL库的初始化,这一步的作用是初始化中断优先级组别以及对系统滴答定时器进行默认的初始化;
  • 6行:时钟初始化,本实验所选择的是外部高速时钟,最终配置为72MHz系统时钟;
  • 9行:初始化LED;
  • 11~22行:初始控制逻辑:只红灯亮->只绿灯亮->只蓝灯亮;
  • 24~30行:死循环逻辑:闪烁红灯,周期是2s;

2. 重点 - 时钟系统

2.1 关于时钟

  • 时钟对于一款芯片非常重要,其作用相当于人的心脏,人只有在心率正常稳定的情况下才能健康生活,同样的,芯片只有工作在合法正常的时钟频率下才能保证程序得到正常的运行。
  • STM32F103的时钟系统,其中包括内部高速/低速时钟源、外部高速/低速时钟源、PLL(锁相环)和系统滴答定时器。以高屋建瓴的形式让用户对STM32F103的时钟系统有一个整体的认识,并在后续的时钟配置实验中让用户进一步的了解HAL库下的时钟配置流程。
  • 首先要学会如何读STM32F103系列的时钟树,如图 1 所示,左侧的①HSI(内部高速时钟)、
    ②HSE(外部高速时钟)、③LSE(外部低速时钟)、④LSI(内部低速时钟)为时钟源,右侧的各种片上外设。图中矩形框内用“/”加数字表示分频器,如:矩形框 /2 ,数字表示几分频;矩形框内用“X”加数字表示的为锁相环,如:矩形框 X2 ,数字表示几倍频;倒梯形表示选择器,长边表示多个输入,短边表示选择其中一个输出。
  • ⑥系统时钟SYSCLK最高为72MHz,从图中左侧的选择器SW可以看到来源有三个,分别是:①内部高速时钟HSI(绿色)、⑤锁相环时钟PLLCLK(紫色)和②外部高速时钟HSE(黄色),而锁相环时钟PLLCLK由内部高速时钟HSI和外部高速时钟HSE,经过分频和PLL锁相环倍频而来。
  • 内部高速时钟HSI可直接经过选择器SW给系统时钟SYSCLK,此时系统时钟SYSCLK为8MHz;内部高速时钟HSI先2分频,再经过选择器PLLSRC进入锁相环PLLMUL,最大倍频为16倍,得到64MHz的锁相环时钟PLLCLK给系统时钟SYSCLK;当外部高速时钟HSE(假设外接晶振为8MHz时)直接给选择器SW,则系统时钟SYSCLK为8MHz;当外部高速时钟HSE(假设外接晶振为8MHz时)直接经过选择器PLLXTPRE给PLLSRC,再经过PLLMUL 9倍频,得到72MHz的PLLCLK给系统时钟SYSCLK。
  • ⑩RTCCLK(实时时钟)的时钟源也有三个,分别是②外部高速时钟HSE的128倍分频(黄色)、③外部低速时钟LSE的32.768kHz(蓝色)、④内部低速时钟LSI的40kHz(橙色)。
  • 11 WDGCLK(独立看门狗)的时钟来源于④内部低速时钟LSI的40kHz(橙色)。
  • 理清各个时钟来源后,再来看各总线的时钟。⑦高速接口总线AHB由⑥SYSCLK系统时钟分频得到,最高是系统时钟的72MHz。⑧外设总线APB1和⑨外设总线APB2,来源于⑦高速接口总线AHB,APB1的输出时钟最高是36MHz,APB2的输出时钟最高是72MHz。APB1和APB2下有各种外设,比如GPIO、USART等。
  • 图 1 时钟树
    在这里插入图片描述

2.2 软件设计

2.2.1 软件设计思路

实验目的:分别使用内部时钟HSI和外部时钟HSE作为系统时钟。

  1. 使用内部时钟HSI配置系统时钟到最大值64Mhz;
  2. 调用库函数读取系统时钟值以验证;
  3. 使用外部时钟HSE配置系统时钟到最大值72Mhz;
  4. 调用库函数读取系统时钟值以验证;

2.2.2 软件设计讲解

  1. 使用 使用 内 部时钟源HSI 作为PLL 的时钟源,然后将PLL 作为系统时钟源
    内部时钟HSI配置如代码段 9.3.1 所示,HAL库定义了两个结构体,只需要设置这两个结构体成员,就完成对时钟的设置。
    使用 HSI 作为系统时钟(stm32f1xx_clk.c)代码如下:
1 /**
2 * @brief System Clock Configuration
3 * The system Clock is configured as follow :
4 * System Clock source = PLL (HSI)
5 * SYSCLK(Hz) = 64000000
6 * HCLK(Hz) = 64000000
7 * AHB Prescaler = 1
8 * APB1 Prescaler = 2
9 * APB2 Prescaler = 1
10 * PLLMUL = 16
11 * Flash Latency(WS) = 2
12 * @param None
13 * @retval None
14 */
15 void SystemClock_Config(void)
16 {
17		RCC_OscInitTypeDef RCC_OscInitStruct = {0};
18		RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
19	
20		RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
21		RCC_OscInitStruct.HSEState = RCC_HSE_OFF;
22		RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
23		RCC_OscInitStruct.HSIState = RCC_HSI_ON;
24		RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
25		RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
26		RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
27		if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
28		{
29			while(1);
30		}
31		
32		/** Initializes the CPU, AHB and APB busses clocks
33		*/
34		RCC_ClkInitStruct.ClockType						= RCC_CLOCKTYPE_SYSCLK| RCC_CLOCKTYPE_HCLK
35	                                                    | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
36		RCC_ClkInitStruct.SYSCLKSource 					= RCC_SYSCLKSOURCE_PLLCLK;
37		RCC_ClkInitStruct.AHBCLKDivider 				= RCC_SYSCLK_DIV1;
38		RCC_ClkInitStruct.APB1CLKDivider 				= RCC_HCLK_DIV2;
39		RCC_ClkInitStruct.APB2CLKDivider 				= RCC_HCLK_DIV1;
40
41		if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
42		{
43			while(1);
44		}
45 }
  • 17行:定义RCC_OscInitTypeDef结构体变量,该结构体主要是选择时钟源、设置时钟开关状态、PLL倍频;
  • 20行:设置时钟来源为内部高速时钟HSI;
  • 21行:不使用外部高速时钟,则HSE关闭;
  • 22行:设置HSE预分频,这里没用上HSE,默认为1分频即可;
  • 23行:内部高速时钟HSI打开;
  • 24行:锁相环PLL打开;
  • 25行:选择PLL时钟源,为HSI的二分频;
  • 26行:PLL的倍频为16倍频;
  • 27行:使用“HAL_RCC_ClockConfig()”函数设置RCC_OscInitStruct;
  • 18行:定义RCC_ClkInitTypeDef结构体变量,该结构体主要是配置系统时钟、AHB、APB1、APB2的分频;
  • 34-35行:设置哪些时钟将被设置;
  • 36行:设置系统时钟SYSCLK的来源为PLLCLK;
  • 37行:设置HCLK时钟(AHB Clock)为1分频(不能超过最大72MHz);  
  • 38行:设置PCLK1时钟(APB1 Clock)为2分频(不能超过最大36MHz);
  • 39行:设置PCLK2时钟(APB2 Clock)为1分频(不能超过最大72MHz);
  • 41行:用“HAL_RCC_ClockConfig()”函数设置RCC_ClkInitStruct;不同的系统时钟需要设置不同的等待周期(LATENCY),这里设置为2,原因参考后面的“35.1 关于内部FLASH”读Flash部分,也可先跳过,不去深究;
  • 将代码和时钟树进行对照,如图 2 ,红色数字为对应代码行号,蓝色数字为时钟频率,红色线段为时钟走向。内部高速时钟HSI经过二分频为4MHz,再经过PLL 16倍频为64MHz,作为系统时钟SYSCLK,再1分频给AHB,AHB再2分频给APB1,1分频给APB2。
  • 图 2 设置 HSI 和时钟树对照示意图
    在这里插入图片描述
  1. 获取系统时钟的函数
  • 主函数里调用HAL库提供的“HAL_RCC_GetSysClockFreq()”函数获取系统时钟验证。
  • 获取系统时钟(main.c)代码如下:
// 此处定义全局变量以便在 debug 的时候可以看到这个变量的值
uint32_t sys_clk = 0;

int main(void)
{

	// 初始化 HAL 库函数必须要调用此函数
	HAL_Init();
	
	/*
	* 系统时钟即 AHB/APB 时钟配置
	* 当使用内部高速时钟 HSI(8MHz)配置系统时钟时,使用 PLL 前会默认先二分频得到 4MHz 的 PLL 分频输入频率
	* 然后经过锁相环放大,最大放大倍数为 16,即 4*16=64MHz 是能配置的最大系统频率,F103 的最大系统频率为 72MHz,64MHz 显然是合法
	的系统频率
	*/
	SystemClock_Config();
	
	// 调用库函数来检验自己的配置是否成功配置为系统频率 64MHz
	sys_clk = HAL_RCC_GetSysClockFreq();
	while(1);
}
  1. 使用 使用 外 部时钟源HSE 作为PLL 的时钟源,然后将PLL 作为系统时钟源与前面类似,这里设置外部高速时钟HSE作为系统时钟,仍是设置两个结构体。
    使用 HSE 作为系统时钟(stm32f1xx_clk.c)代码如下:
1  /**
2  * @brief System Clock Configuration
3  * The system Clock is configured as follow :
4  * System Clock source = PLL (HSE)
5  * SYSCLK(Hz) = 72000000
6  * HCLK(Hz) = 72000000
7  * AHB Prescaler = 1
8  * APB1 Prescaler = 2
9  * APB2 Prescaler = 1
10 * PLLMUL = 9
11 * Flash Latency(WS) = 2
12 * @param None
13 * @retval None
14 */
15 void SystemClock_Config(void)
16 {
17  	RCC_OscInitTypeDef RCC_OscInitStruct = {0};
18  	RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
19	
20  	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
21  	RCC_OscInitStruct.HSEState = RCC_HSE_ON;
22  	RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
23  	RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
24  	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
25  	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
26  	RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
27  	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
28  	{
29  		while(1);
30  	}
31 	
32  	/** Initializes the CPU, AHB and APB busses clocks
33  	*/
34  	RCC_ClkInitStruct.ClockType 		= RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
35 	                                		| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
36  	RCC_ClkInitStruct.SYSCLKSource 		= RCC_SYSCLKSOURCE_PLLCLK;
37  	RCC_ClkInitStruct.AHBCLKDivider 	= RCC_SYSCLK_DIV1;
38  	RCC_ClkInitStruct.APB1CLKDivider 	= RCC_HCLK_DIV2;
39  	RCC_ClkInitStruct.APB2CLKDivider 	= RCC_HCLK_DIV1;
40 	
41  	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
42		{
43			while(1);
44		}
45	}
  • 17行:定义RCC_OscInitTypeDef结构体变量,该结构体主要是选择时钟源、设置时钟开关状态、PLL倍频;
  • 20行:设置时钟来源为外部高速时钟HSE;
  • 21行:内部高速时钟HSE打开;
  • 22行:设置HSE预分频,设置为1分频;
  • 23行:不使用内部高速时钟,则HSI关闭;
  • 24行:锁相环PLL打开;
  • 25行:选择PLL时钟源,为HSE;
  • 26行:PLL的倍频为9倍频;
  • 27行:使用“HAL_RCC_ClockConfig()”函数设置RCC_OscInitStruct;
  • 18行:定义RCC_ClkInitTypeDef结构体变量,该结构体主要是配置系统时钟、AHB、APB1、APB2的分频;
  • 34-35行:设置哪些时钟将被设置;
  • 36行:设置系统时钟SYSCLK的来源为PLLCLK;
  • 37行:设置HCLK时钟(AHB Clock)为1分频(不能超过最大72MHz);
  • 38行:设置PCLK1时钟(APB1 Clock)为2分频(不能超过最大36MHz);
  • 39行:设置PCLK2时钟(APB2 Clock)为1分频(不能超过最大72MHz);
  • 41行:用“HAL_RCC_ClockConfig()”函数设置RCC_ClkInitStruct;
  • 将代码和时钟树进行对照,如图 3 所示,红色数字为对应代码行号,蓝色数字为时钟频率,红色线段为时钟走向。外部高速时钟HSE不分频,通过选择器PLLXTPRE和选择器PLLRC,再PLL 9倍频为72MHz,作为系统时钟SYSCLK,再1分频给AHB,AHB再2分频给APB1,1分频给APB2。当前状态,为MCU全速工作状态。
  • 图 3 设置 HSE 和时钟树对照示意图
    在这里插入图片描述
  1. 获取系统时钟的函数
    与前面类似,主函数里调用HAL库提供的“HAL_RCC_GetSysClockFreq()”函数获取系统时钟验证。
    获取系统时钟(main.c)代码如下:
uint32_t sys_clk = 0;
int main(void)
{
// 初始化 HAL 库函数必须要调用此函数
HAL_Init();
/*
* 系统时钟即 AHB/APB 时钟配置
* 当使用外部高速时钟 HSE(8MHz)配置系统时钟时,使用 PLL 前可以选择不分频或者二分频,我们要配置到最大 72MHz 的系统频率此处当
然是不分频
* 然后经过锁相环放大,最大放大倍数为 16,我们要想得到 72MHz,此处选择 9 倍放大系数,即 8*9=72MHz 即可达到目标
*/
SystemClock_Config();
// 调用库函数来检验自己的配置是否成功配置为系统频率 64MHz
sys_clk = HAL_RCC_GetSysClockFreq();
while(1);
}

3. 重点 — 中断

3.1 Cortex- M3 的中断和优先级

  • 正常情况下,微处理器根据代码内容,按顺序执行指令。执行过程中,如果遇到其它紧急的事件需要处理,则先暂停当前任务,执行紧急事件,待紧急事件处理完后,再恢复到刚才暂停的地方继续执行。这个产生的紧急事件就叫做中断或异常,如图 1 所示。
  • 图 1 中断或异常处理示意图
  • 通常,把CPU内部产生的紧急事件叫做异常,比如非法指令(除零)、地址访问越界等;把来自CPU外部的片上外设产生的紧急事件叫做中断,比如GPIO引脚电平变化、定时器溢出等。异常和中断的效果基本一致,都是暂停当前任务,优先执行紧急事件,因此一般将中断和异常统称为中断。
  • ARM公司设计了Cortex-M3内核,这个内核就包含了中断系统框架,Cortex-M3权威指南.pdf”,后简称《CM3权威指南》。
  • ST公司根据该内核,因地制宜的设计了STM32系列产品,Cortex-M3编程手册.pdf”,后简称《CM3编程手册》。
  • 《CM3权威指南》讲解的是Cortex-M3内核的整个体系,例如指令集、异常、MPU等,《CM3编程手册》中则是关于STM32F10/20/21/L1系列使用到的Cortex-M3的内容。换句话说《CM3编程手册》是《CM3权威指南》的一个子集。
    Cortex-M3内核有256种异常和中断,其中编号1-15是系统异常,16~256是外部中断,如下表 1 所示。
  • 表 11 Cortex-M3 中断异常表
    在这里插入图片描述
  • 如此多的中断,导致了一些新问题。比如两个中断同时发生,应该先执行哪个中断任务?又比如一个中断发生了,又来了一个更紧急的中断,是该继续执行原来的中断,还是执行新的紧急中断?
    针对这些问题,Cortex-M3内核有一个专门管理中断的外设NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器),通过优先级控制中断的嵌套和调度。NVIC是一个总的中断控制器,无论是来在内核的异常还是外设的外部中断,都由NVIC统一进行管理。
  • 上表中,Reset(复位)、NMI(Non Maskable Interrupt,不可屏蔽中断)、HardFault(硬件异常)的优先级是固定的,且优先级是负数,也就是最高的(优先级数字越小,优先级越高)。剩下的异常或中断,都是可以通过修改NVIC的寄存器调整优先级(但不能设置为负数)。NVIC作为在内核里的外设,也是通过存储器映射的方式访问。
  • 在Cortex-M3中,将优先级拆分为抢占优先级(Preempt Priority)和子优先级(Subpriority),每个中断都需要指定这两级,具有高优先级的中断可以打断低优先级的中断,实现中断嵌套。
    通过应用中断和复位控制寄存器(Application Interrupt and Reset Control Register,AIRCR)的Bits[10:8](PRIGROUP)将优先级分组。分组决定每个可编程中断的PRI_n的Bits[7:0]的高低位分配,从而影响抢占优先和子优先级的级数,两者关系如表 2 所示。
  • 表 2 Cortex-M3 优先级分组
    在这里插入图片描述
  • 举个例子,假设将优先级分组(PRIGROUP)设置为2,此时每个中断可设置抢占优先级范围为0-32,子优先级范围为0~8,比如某中断的抢占优先级为2,子优先级为3。
  • 所有可编程的中断都需要指定抢占优先级和子优先级,抢占优先级决定是否可以产生中断嵌套,子优先级决定中断响应顺序,若两种优先级一样则看中断在中断异常表中的位置,越靠前越先响应。
  • 抢占优先级高(值小)的中断可以中断抢占优先级低(值大)的中断处理函数,进而执行高优先级的中断处理函数,执行完毕后再继续执行被中断的低优先级的处理函数。
  • 当两个中断的抢占优先级相同时,即这两个中断将没有嵌套关系,当一个中断到来后,若此时CPU正在处理另一个中断,则这个后到来的中断就要等到前一个中断处理函数处理完毕后才能被处理,当这两个中断同时到达,则中断控制器会根据它们的子优先级决定先处理哪个。
  • 如果两个中断的优先级都设置为一样了,那么谁先触发的就谁先执行;如果是同时触发的,那么就根据中断异常表的位置(靠前)来决定谁先执行。

3.1STM32 的中断和优先级

由表 1 可知,Cortex-M3设计有256种中断,但大多数MCU都用不到这么多中断比如STM32F103
系列就只有70种异常和中断,其中前10个是系统异常,后面60个是外部中断,如下表 10.2.1 所示。
表 3 STM32F103 中断异常表

在这里插入图片描述

  • 在HAL库启动文件“startup_stm32f103xe.s”,可以看到定义的中断向量表,如下代码所示。
    STM32F103xE 中断向量表(startup_stm32f103xe.s)代码如下:
1 __Vectors DCD __initial_sp ; Top of Stack
2		DCD Reset_Handler ; Reset Handler
3		DCD NMI_Handler ; NMI Handler
4		DCD HardFault_Handler ; Hard Fault Handler
5		DCD MemManage_Handler ; MPU Fault Handler
6		DCD BusFault_Handler ; Bus Fault Handler
7		DCD UsageFault_Handler ; Usage Fault Handler
8		DCD 0 ; Reserved
9		DCD 0 ; Reserved
10		DCD 0 ; Reserved
11		DCD 0 ; Reserved
12		DCD SVC_Handler ; SVCall Handler
13		DCD DebugMon_Handler ; Debug Monitor Handler
14		DCD 0 ; Reserved
15		DCD PendSV_Handler ; PendSV Handler
16		DCD SysTick_Handler ; SysTick Handler
17		
18		; External Interrupts
19		DCD WWDG_IRQHandler ; Window Watchdog
20		DCD PVD_IRQHandler ; PVD through EXTI Line detect
21		DCD TAMPER_IRQHandler ; Tamper
22		DCD RTC_IRQHandler ; RTC
23		DCD FLASH_IRQHandler ; Flash
24		DCD RCC_IRQHandler ; RCC
25		DCD EXTI0_IRQHandler ; EXTI Line 0
26		DCD EXTI1_IRQHandler ; EXTI Line 1
27		DCD EXTI2_IRQHandler ; EXTI Line 2
28		DCD EXTI3_IRQHandler ; EXTI Line 3
29		DCD EXTI4_IRQHandler ; EXTI Line 4
30		DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
31		DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
32		DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
33		DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
34		DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
35		DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
36		DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
37		DCD ADC1_2_IRQHandler ; ADC1 & ADC2
38		DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
39		DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
40		DCD CAN1_RX1_IRQHandler ; CAN1 RX1
41		DCD CAN1_SCE_IRQHandler ; CAN1 SCE
42		DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
43		DCD TIM1_BRK_IRQHandler ; TIM1 Break
44		DCD TIM1_UP_IRQHandler ; TIM1 Update
45		DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
46		DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
47		DCD TIM2_IRQHandler ; TIM2
48		DCD TIM3_IRQHandler ; TIM3
49		DCD TIM4_IRQHandler ; TIM4
50		DCD I2C1_EV_IRQHandler ; I2C1 Event
51		DCD I2C1_ER_IRQHandler ; I2C1 Error
52		DCD I2C2_EV_IRQHandler ; I2C2 Event
53		DCD I2C2_ER_IRQHandler ; I2C2 Error
54		DCD SPI1_IRQHandler ; SPI1
55		DCD SPI2_IRQHandler ; SPI2
56		DCD USART1_IRQHandler ; USART1
57		DCD USART2_IRQHandler ; USART2
58		DCD USART3_IRQHandler ; USART3
59		DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
60		DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line
61		DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
62		DCD TIM8_BRK_IRQHandler ; TIM8 Break
63		DCD TIM8_UP_IRQHandler ; TIM8 Update
64		DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
65		DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
66		DCD ADC3_IRQHandler ; ADC3
67		DCD FSMC_IRQHandler ; FSMC
68		DCD SDIO_IRQHandler ; SDIO
69		DCD TIM5_IRQHandler ; TIM5
70		DCD SPI3_IRQHandler ; SPI3
71		DCD UART4_IRQHandler ; UART4
72		DCD UART5_IRQHandler ; UART5
73		DCD TIM6_IRQHandler ; TIM6
74		DCD TIM7_IRQHandler ; TIM7
75		DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
76		DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
77		DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
78		DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
79		__Vectors_End
  • 可以看到第2~16行,为10个系统异常,剩下的全为外部中断。同时这里还定义了所有的中断处理函数名字,当外设产生中断时,则跳到中断向量表中对应中断处理函数位置,比如发生RTC中断事件,则跳到22行执行“RTC_IRQHandler()”函数内容。
  • STM32F103的异常和中断,基于Cortex-M3修改而来,前面的系统异常部分几乎没有变化,外部中断则对应不同的外设。
  • 同样,STM32F103也继承了Cortex-M3的中断优先级规则,因为中断少了很多,中断优先级也用不了那么多,只使用了PRI_n的Bits[7:0]中的Bits[7:4]设置优先级,因此优先级分组为表 4 所示。
  • 表 4 STM32F103 优先级分组
  • 在这里插入图片描述
  • 可见STM32F103系列最多有16级可编程优先级,STM32F103不使用PRIGROUP来命名分组,而采用NVIC_PRIORITYGROUP_x 的 方 式 命 名 , 即 NVIC_PRIORITYGROUP_0 对 应 PRIGROUP 为 7 , 在“stm32f1xx_hal_cortex.h”有相关定义,如下代码所示。
/** @defgroup CORTEX_Preemption_Priority_Group CORTEX Preemption Priority Group
* @{
*/
#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2 0x00000005U /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3 0x00000004U /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
0 bits for subpriority */
  • 通常中断优先级分组只会设置一次,它针对的是系统中所有的中断。后续设置某个中断的中断优先级时,只需要在这个组规定的抢占优先级数和子优先级级数范围内分配优先级级数。后续代码中,不应该再修改中断优先级分组,否则导致中断顺序不按预期触发。本手册所有实验,将设置中断优先级分组放在了“HAL_Init()”里,如下代码的第5行,调用“HAL_NVIC_SetPriorityGrouping()”函数设置中断优先级分组。
    设置中断优先级分组(stm32f1xx_hal.c)代码如下:
1  HAL_StatusTypeDef HAL_Init(void)
2  {
3 ……
4  /* Set Interrupt Group Priority */
5  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
6 
7  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
8  HAL_InitTick(TICK_INT_PRIORITY);
9 ……
10 }
  • 这里默认设置的优先级分组为NVIC_PRIORITYGROUP_4,则后续使“HAL_NVIC_SetPriority()”函数设置优先级时,抢占优先级的范围是0~15,子优先级的值只能选择0。
  • “HAL_NVIC_SetPriority()”函数需要传入三个参数,参数IRQn是中断号,后两个是抢占优先级级数和子优先级级数,注意结合中断分组设置范围。
    “HAL_NVIC_SetPriority()”函数原型(stm32f1xx_hal_cortex.c)代码如下:
	/**
	* @brief Sets the priority of an interrupt.
	* @param IRQn: External interrupt number.
	* This parameter can be an enumerator of IRQn_Type enumeration
	* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm3
	2f10xx.h))
	* @param PreemptPriority: The preemption priority for the IRQn channel.
	* This parameter can be a value between 0 and 15
	* * A lower priority value indicates a higher priority
	* @param SubPriority: the subpriority level for the IRQ channel.
	* This parameter can be a value between 0 and 15
	* A lower priority value indicates a higher priority.
	* @retval None
	*/
	void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
	{
		uint32_t prioritygroup = 0x00U;
		
		/* Check the parameters */
		assert_param(IS_NVIC_SUB_PRIORITY(SubPriority));
		assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority));
		prioritygroup = NVIC_GetPriorityGrouping();
		
		NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
	}

【总结】

  • STM32中断重点理解中断优先级分组,然后根据中断优先级分组确定抢占优先级级数和子优先级级数。体现在编程里,就是根据中断需求,先使用“HAL_NVIC_SetPriorityGrouping()”函数设置中断优先级分组,再使用“HAL_NVIC_SetPriority()”函数设置不同中断的抢占优先级级数和子优先级级数。
  • 假设中断A的抢占优先级比中断B的抢占优先级高,两个中断同时发生,那么中断A优先执行。
  • 假设中断A的抢占优先级和中断B的抢占优先级一样,两个中断同时发生,那么子优先级高的中断优先执行。
  • 假设中断A的抢占优先级比中断B的抢占优先级高,中断B先发生,随后A也发生,那么将暂停中断B,先执行中断A,A执行完后,再回来执行中断B,最后执行主程序,这种效果即中断嵌套。
  • 假设中断A的抢占优先级比中断B的抢占优先级一样,中断A的子优先级比中断B的子优先级高,中断B先发生,随后A也发生,那么中断A将等待中断B执行完后,才会执行中断A,即子优先级不能中断嵌套。
  • 假设中断A的抢占优先级和中断B的抢占优先级一样,且子优先级也一样,两个中断同时发生,那么根据前面表 3 顺序,排在前面的先执行。
    总结中断是否会优先执行依据:首先是抢占先式优先级等级,其次是子优先级等级,只有抢占优先级才可能出现中断嵌套。

4. 重点 —SysTick 定时器

4.1 关于SysTick 定时器

  • SysTick定时器(又名系统滴答定时器)是存在于Cortex-M3的一个定时器,只要是ARM Cotex-M系列内核的MCU都包含这个定时器。使用内核的SysTick定时器来实现延时,可以不占用系统定时器,节约资源。由于SysTick是在CPU核内部实现的,跟MCU外设无关,因此它的代码可以在不同厂家之间移植。
  • 本章将使用系统滴答定时器实现延时函数,注意SysTick用于了HAL库的毫秒级延时函数
    “HAL_Delay()”,不建议日常使用SysTick去作为其它用途,这里只作为演示。
  • SysTick定时器是一个24位递减定时器,即计数器可以从最大值2 24 开始,每个时钟周期减1,当减到0时,会产生Systick异常,同时再自动重载定时初值,开始新一轮计数。通过设置这个定时初值,就可以实现得到指定时间。如下图 1 所示,y为定时器初值,然后随着时间增加,值逐渐减小,直至为0,再重新加载初值,如此往复,x1、x2、x3这些时间段,就是我们需要的延时时间。
  • 图 1 Systick 定时器工作示意图
    在这里插入图片描述
  • 假设STM32F103工作在72MHz,即72000000Hz,意味着1s时间内,会计数72000000次。那么1ms则计数72000000/1000=72000次。这个72000就可以作为系统滴答定时器的初始值,将这个值写入系统滴答定时器,定时器在每个时钟周期减1,减到0时,就刚好是1ms,同时产生中断通知,再次加载72000如此反复。HAL库提供“HAL_SYSTICK_Config()”函数去设置这个初始值。
  • 系统滴答定时器控制寄存器比较少,整体比较简单,借助本次机会详细分析一下寄存器和HAL之间是调用关系。系统滴答定时器只有四个控制寄存器:STK_CTRL,STK_LOAD,STK_VAL和STK_CALIB。
  1. 系统滴答定时器控制和状态寄存器(STK_CTRL )
    在这里插入图片描述
位段名称类型复位值描述
31:17RESERVEDR0保留位
16COUNTFLAGRW0如果在上次读取本寄存器后,计数器已经计到了 0,则该位为 1;读取该位,该位将自动清零
15:3RESERVEDR0保留
2CLKSOURCERW00=AHB/8 1=AHB
1TICKINTRW01=计数器计数到 0 时产生 SysTick 异常请求0=计数器计数到 0 时不产生异常
0ENABLERW0SysTick 定时器的使能位
  • 重点关注Bit[0],用于使能系统滴答定时器,Bit[1]使能系统滴答定时器中断,Bit[2]系统滴答时钟的时钟来源。
  1. 系统滴答定时器 加载值 寄存器(STK_LOAD)
    在这里插入图片描述
位段名称类型复位值描述
31:24RESERVEDR0保留位
23:0RELOADRW0当计数器向下计数到 0 时,下一个时钟滴答定时器将重新装载的值

Bit[23:0],一共24位,用来设置系统滴答定时器的初始值,因此范围为1~ 16777216。

  1. 系统滴答定时器 当前值寄存器(STK_VAL )
    在这里插入图片描述
位段名称类型复位值描述
31NOREFR-1=没有外部参考时钟(STCLK 不可用)0=外部参考时钟可用
30SKEWR-1=校准值不是准确的 10ms 0=校准值是准确的 10ms
29:24RESERVEDR0保留
23:0TENMSRW010ms 校准值。芯片设计者应该通过 Cortex-M3的输入信号提供该数值。若该值读回零,则表示无法使用校准功能

这个寄存器没用到,可以不用管。此外,当处理器在调试期间被暂停(halt)时,系统滴答定时器也将暂停运作。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 如果要学习STM32单片机,有很多可用的学习资料和资源供你参考和学习。首先,STMicroelectronics官方网站是最好的信息来源之一。在官方网站上,你可以找到Data Briefs、Technical Notes、Application Notes和User Manuals等多种文档,这些文档可以帮助你了解不同型号的STM32单片机,并提供详细的技术细节和应用示例。 此外,STMicroelectronics还提供了免费的配套开发工具和软件,如STM32CubeIDE、STM32CubeMX和HAL库等。这些工具可以帮助你开发、调试和烧写STM32单片机的代码,并提供丰富的代码库和实例,方便你快速入门。 除了官方资料外,网络上还有大量的STM32单片机学习资料和教程。你可以通过搜索引擎找到许多相关博客、论坛和视频教程,其中包括了解STM32单片机的基础知识、使用各种开发环境和编程语言进行开发,以及实际项目的应用示例等。这些资源可以帮助你深入学习STM32单片机的各个方面,并解决你在学习和项目中遇到的问题。 同时,还有一些出版的教材和参考书籍,如《精通STM32单片机》、《STM32权威指南》等,这些书籍以系统化的方式解释了STM32单片机的原理和应用,可以作为深入学习的参考资料。 总之,STM32单片机学习资料是丰富多样的,从官方资料到网络资源、教程和书籍都是很好的学习参考。结合多种源的学习材料和实践经验,你可以更好地掌握STM32单片机的开发和应用。 ### 回答2: STM32是一种广泛应用于嵌入式系统开发的32位单片机系列,具有高性能、低功耗和丰富的外设资源。学习STM32单片机需要掌握其基本原理、应用开发和编程技术等方面的知识。 首先,可以通过阅读官方提供的STM32单片机资料来进行学习。STMicroelectronics公司为STM32系列提供了官方的技术手册、应用笔记、教程和参考设计等资料,其中包含了单片机的内部结构、外设使用方法以及开发工具的介绍,有助于初学者对单片机的基本概念和应用进行了解。 其次,可以参考一些经典的STM32单片机编程教程和实例进行学习。在互联网上有很多相关的学习资源,包括视频教程、电子书和在线课程等,这些资源可以帮助初学者快速掌握STM32单片机的编程技巧和开发流程,了解如何使用STM32 HAL库和CubeMX软件进行开发。 此外,参加STM32单片机的实践项目和实验也是非常重要的学习方式。可以利用开发板或者仿真软件进行实验,从简单的LED闪烁开始,逐步深入学习各种外设的使用方法,例如串口通信、PWM输出和ADC采集等,通过实际操作来加深对STM32单片机的理解和应用。 最后,与其他STM32单片机学习者进行交流和探讨也是学习的重要途径。可以加入相关的技术社区、论坛或者参加线下的技术交流活动,与其他爱好者一起交流心得、解决问题和分享经验,共同进步。 综上所述,学习STM32单片机需要结合官方资料、编程教程、实践项目和交流讨论等多种方式,通过理论学习和实践操作相结合的方式来提高自己的技能和能力。只有不断学习和实践,才能逐步掌握STM32单片机的应用开发技术,发挥出其强大的功能。 ### 回答3: STM32单片机是一款由意法半导体公司推出的32位ARM Cortex-M系列微控制器。学习STM32单片机需要掌握一定的电子基础知识和C语言编程能力。以下是一些可供学习STM32单片机的资料推荐: 1. 官方资料:意法半导体官方网站提供了丰富的STM32单片机系列产品的技术文档、数据手册、应用笔记以及示例代码等,这些资料对于初学者和进阶者都非常有帮助。 2. 教材和教程:市面上有很多针对STM32单片机的教材和教程,其中一些是由专业人士撰写的,具有系统性和深度,适合系统学习。另外,也有一些网上的教程、博客和视频教程,可以提供实际操作示例和案例分析。 3. 社区论坛和博客:STM32单片机学习过程中,遇到问题时可以向社区论坛提问和交流。ST社区、电子爱好者论坛、知乎等地都有相关的技术讨论区,可以从其他人的经验中获得帮助。此外,还有一些博客是由学习STM32的爱好者写的,分享各种学习心得和项目经验。 4. 实验平台和开发板:购买一块能够容易上手的STM32开发板,如ST-Link V3 Mini开发板等,这样可以借助官方提供的开发环境和示例程序,快速上手进行实验和开发。 5. 项目实战:在学习的过程中,可以选择一些具体的项目进行实战。可以从简单的LED闪烁开始,逐步扩展到涉及串口通信、蓝牙、传感器和外设等更复杂的项目。 总之,学习STM32单片机需要结合官方资料、教材和教程、社区讨论和项目实战等多种资源,根据自己的兴趣和基础情况选择合适的学习路径,坚持实践,不断积累经验,就能够逐渐掌握STM32单片机的原理和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值