首页 stm32 hal学习笔记1
文章
取消

stm32 hal学习笔记1

一、开发环境

1. 官方

STM32Cube是ST提供,给STM32板子的开发工具,包含:

  • 图形配置工具STM32CubeMX

  • 嵌入式软件包STM32Cube库,包含完整的 HAL 库(STM32 硬件抽象层 API),配套 的中间件(包括 RTOS,USB,TCP/IP 和图形),以及一系列完整的例程。

    嵌入式软件包STM32Cube库完全兼容STM32CubeMX

STM32Cube支持STM32全系列,需要下载对应的STM32Cube包,也可以通过STM32CubeMX软件下载对应STM32芯片的包,然后去到Firmware Repository->Repository Folder找到对应的芯片包文件夹,例如STM32Cube_FW_F4_V1.25.2

芯片包结构:image-20220219221132395

2. 配置

stm32f4xx_hal_conf.hSTM32 HAL配置文件。

image-20220219224729591

往项目添加文件和宏定义:image-20220219230107586

例程代码(main.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "sys.h"
#include "delay.h"
#include "usart.h"
void Delay(__IO uint32_t nCount);
void Delay(__IO uint32_t nCount) {
	while (nCount--);
}
int main(void) {
	GPIO_InitTypeDef GPIO_Initure;
	HAL_Init();						//初始化HAL库
	Stm32_Clock_Init(336, 8, 2, 7); //设置时钟,168Mhz
	__HAL_RCC_GPIOF_CLK_ENABLE(); //开启GPIOF时钟
	GPIO_Initure.Pin = GPIO_PIN_9 | GPIO_PIN_10; // PF9,10
	GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;	 //推挽输出
	GPIO_Initure.Pull = GPIO_PULLUP;			 //上拉
	GPIO_Initure.Speed = GPIO_SPEED_HIGH;		 //高速
	HAL_GPIO_Init(GPIOF, &GPIO_Initure);
	while (1) {
		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);	 // PF9置1
		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET); // PF10置1
		Delay(0x7FFFFF);
		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);  // PF9置0
		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET); // PF10置0
		Delay(0x7FFFFF);
	}
}

stm32f4xx_hal_conf.h

该文件用来使能何种外设和时钟设置。

1
2
3
4
// 修改外部晶振
#if !defined  (HSE_VALUE) 
  #define HSE_VALUE    (8000000U) // 8Mhz
#endif /* HSE_VALUE */

stm32f4xx_hal.c

1
2
// 修改中断优先级分组
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); 

二、HAL库

1. HAL库关键文件

  • stm32f4xx_hal_ppp.c/.h:外设API,stm32f4xx_cortex.c/.hCortex内核通用函数声明和定义(中断优先级NVIC配置,软件软复位,SysTick配置等)。
  • stm32f4xx_hal_ppp_ex.c/.h:外设拓展API。
  • stm32f4xx_hal.c:hal通用api(HAL_Init, HAL_DeInit, HAL_Delay等)。
  • stm32f4xx_hal_def.h:hal通用数据类型定义和宏定义。
  • stm32f4xx_ll_ppp.c/.h:复杂外设底层实现,stm32f4xx_hal_ppp.c中被调用。

  • stm32f4xx.h通过编译环境的宏定义使用到stm32f407xx.h

  • system_stm32f4xx.c/.h声明和定义了系统初始化函数SystemInit()和系统更新函数SystemCoreClockUpdate()
  • stm32f4xx_hal_msp.c,MSP,MCU support package,MCU级别硬件初始化设置。系统启动后调用了HAL_Init(),会自动调用该文件里的硬件初始化函数HAL_MspInit()。

2.基于HAL库的程序执行流程

__weak修饰符,修饰函数,用户可以重定义函数,不定义编译器也不会报错。

image-20220219234654400

三、架构

1. 总线

总线架构图:

image-20220219235439822

STM32的总线矩阵用于主控总线之间的访问总裁管理(循环调度算法):Cortex内核总线、DMA总线、FlASH总线、SRAM总线。

2. 时钟树

image-20220220001243733

STM32的外设时钟使能是在RCC相关寄存器中配置的。

四、STM32CubeMX

使用STM32CubeMX配置工程:1. 新建工程;2. RCC设置(配置时钟源);3. 时钟树配置;4. 引脚配置;5.生成源码;6. 编写代码。

1. 时钟

RCC:

image-20220220002556110

时钟树:

配置好SYSCLKAHB总线时钟源HCLKCortex系统定时器SysTick时钟源。

image-20220220003838797

2. 修改引脚

image-20220220004635439

3. 生成源码

image-20220220004846430

1
2
3
4
5
6
7
8
9
10
11
void Delay(__IO uint32_t n) { while (n--); }
int main(void) {
  HAL_Init(); 
  SystemClock_Config(); // 配置时钟系统
  MX_GPIO_Init(); // 初始化引脚相关配置
  while (1) {
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET);
    Delay(0x7FFFFF);
  }
}

4. 通用SYSTEM代码(uart, sys, delay)

需要先在stm32f4xx_hal_conf.h启动uart(#define HAL_UART_MODULE_ENABLED),然后在编译环境Keil5的项目工程里导入stm32f4xx_hal_uart.c文件。也可以在STM32CubeMX启动USART1Connectivity --> Mode --> Asynchronous,然后在所生成的项目工程里去掉usart.c和在main.c去掉#include "usart.hMX_USART1_UART_Init();,最后在/* USER CODE BEGIN Includes *//* USER CODE END Includes */之间插入#include "delay.h"#include "usart.h",就能在main.c里使用这些通用代码了。

uart.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include "usart.h"
#include "delay.h"
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
	int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
	x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
	while ((USART1->SR & 0X40) == 0)
		; //循环发送,直到发送完毕
	USART1->DR = (u8)ch;
	return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
// bit15,	接收完成标志
// bit14,	接收到0x0d
// bit13~0,	接收到的有效字节数目
u16 USART_RX_STA = 0; //接收状态标记
u8 aRxBuffer[RXBUFFERSIZE];		  // HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; // UART句柄
//初始化IO 串口1
// bound:波特率
void uart_init(u32 bound)
{
	// UART 初始化设置
	UART1_Handler.Instance = USART1;					// USART1
	UART1_Handler.Init.BaudRate = bound;				//波特率
	UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
	UART1_Handler.Init.StopBits = UART_STOPBITS_1;		//一个停止位
	UART1_Handler.Init.Parity = UART_PARITY_NONE;		//无奇偶校验位
	UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
	UART1_Handler.Init.Mode = UART_MODE_TX_RX;			//收发模式
	HAL_UART_Init(&UART1_Handler);						// HAL_UART_Init()会使能UART1
	HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE); //该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
// UART底层初始化,时钟使能,引脚配置,中断配置
//此函数会被HAL_UART_Init()调用
// huart:串口句柄
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
	// GPIO端口设置
	GPIO_InitTypeDef GPIO_Initure;
	if (huart->Instance == USART1) //如果是串口1,进行串口1 MSP初始化
	{
		__HAL_RCC_GPIOA_CLK_ENABLE();  //使能GPIOA时钟
		__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
		GPIO_Initure.Pin = GPIO_PIN_9;			  // PA9
		GPIO_Initure.Mode = GPIO_MODE_AF_PP;	  //复用推挽输出
		GPIO_Initure.Pull = GPIO_PULLUP;		  //上拉
		GPIO_Initure.Speed = GPIO_SPEED_FAST;	  //高速
		GPIO_Initure.Alternate = GPIO_AF7_USART1; //复用为USART1
		HAL_GPIO_Init(GPIOA, &GPIO_Initure);	  //初始化PA9
		GPIO_Initure.Pin = GPIO_PIN_10;		 // PA10
		HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA10
#if EN_USART1_RX
		HAL_NVIC_EnableIRQ(USART1_IRQn);		 //使能USART1中断通道
		HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); //抢占优先级3,子优先级3
#endif
	}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart->Instance == USART1) //如果是串口1
	{
		if ((USART_RX_STA & 0x8000) == 0) //接收未完成
		{
			if (USART_RX_STA & 0x4000) //接收到了0x0d
			{
				if (aRxBuffer[0] != 0x0a)
					USART_RX_STA = 0; //接收错误,重新开始
				else
					USART_RX_STA |= 0x8000; //接收完成了
			}
			else //还没收到0X0D
			{
				if (aRxBuffer[0] == 0x0d)
					USART_RX_STA |= 0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA & 0X3FFF] = aRxBuffer[0];
					USART_RX_STA++;
					if (USART_RX_STA > (USART_REC_LEN - 1))
						USART_RX_STA = 0; //接收数据错误,重新开始接收
				}
			}
		}
	}
}
//串口1中断服务程序
void USART1_IRQHandler(void)
{
	u32 timeout = 0;
	HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
	timeout = 0;
	while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY) //等待就绪
	{
		timeout++; ////超时处理
		if (timeout > HAL_MAX_DELAY)
			break;
	}
	timeout = 0;
	while (HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE) != HAL_OK) //一次处理完成之后,重新开启中断并设置RxXferCount为1
	{
		timeout++; //超时处理
		if (timeout > HAL_MAX_DELAY)
			break;
	}
}
#endif

uart.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef _USART_H
#define _USART_H
#include "sys.h"
#include "stdio.h"
#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1    //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN];   //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA;                 //接收状态标记
extern UART_HandleTypeDef UART1_Handler; // UART句柄
#define RXBUFFERSIZE 1             //缓存大小
extern u8 aRxBuffer[RXBUFFERSIZE]; // HAL库USART接收Buffer
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);
#endif

sys.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "sys.h"
//外部晶振为8M的时候,推荐值:plln=336,pllm=8,pllp=2,pllq=7.
//得到:Fvco=8*(336/8)=336Mhz
//     SYSCLK=336/2=168Mhz
//     Fusb=336/7=48Mhz
//返回值:0,成功;1,失败
void Stm32_Clock_Init(u32 plln, u32 pllm, u32 pllp, u32 pllq)
{
    HAL_StatusTypeDef ret = HAL_OK;
    RCC_OscInitTypeDef RCC_OscInitStructure;
    RCC_ClkInitTypeDef RCC_ClkInitStructure;
    __HAL_RCC_PWR_CLK_ENABLE(); //使能PWR时钟
    //下面这个设置用来设置调压器输出电压级别,以便在器件未以最大频率工作
    //时使性能与功耗实现平衡。
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); //设置调压器输出电压级别1
    RCC_OscInitStructure.OscillatorType = RCC_OSCILLATORTYPE_HSE; //时钟源为HSE
    RCC_OscInitStructure.HSEState = RCC_HSE_ON;                   //打开HSE
    RCC_OscInitStructure.PLL.PLLState = RCC_PLL_ON;               //打开PLL
    RCC_OscInitStructure.PLL.PLLSource = RCC_PLLSOURCE_HSE;       // PLL时钟源选择HSE
    RCC_OscInitStructure.PLL.PLLM = pllm;                         //主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
    RCC_OscInitStructure.PLL.PLLN = plln;                         //主PLL倍频系数(PLL倍频),取值范围:64~432.
    RCC_OscInitStructure.PLL.PLLP = pllp;                         //系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
    RCC_OscInitStructure.PLL.PLLQ = pllq;                         // USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
    ret = HAL_RCC_OscConfig(&RCC_OscInitStructure);               //初始化
    if (ret != HAL_OK)
        while (1)
            ;
    //选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2
    RCC_ClkInitStructure.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStructure.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       //设置系统时钟时钟源为PLL
    RCC_ClkInitStructure.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB分频系数为1
    RCC_ClkInitStructure.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1分频系数为4
    RCC_ClkInitStructure.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2分频系数为2
    ret = HAL_RCC_ClockConfig(&RCC_ClkInitStructure, FLASH_LATENCY_5); //同时设置FLASH延时周期为5WS,也就是6个CPU周期。
    if (ret != HAL_OK)
        while (1)
            ;
    // STM32F405x/407x/415x/417x Z版本的器件支持预取功能
    if (HAL_GetREVID() == 0x1001)
    {
        __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); //使能flash预取
    }
}
#ifdef USE_FULL_ASSERT
//当编译提示出错的时候此函数用来报告错误的文件和所在行
// file:指向源文件
// line:指向在文件中的行数
void assert_failed(uint8_t *file, uint32_t line)
{
    while (1)
    {
    }
}
#endif
// THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
__asm void WFI_SET(void)
{
    WFI;
}
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
    CPSID I
        BX LR
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
    CPSIE I
        BX LR
}
//设置栈顶地址
// addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
    MSR MSP, r0 // set Main Stack value
                 BX r14
}

sys.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#ifndef _SYS_H
#define _SYS_H
#include "stm32f4xx.h"
//定义一些常用的数据类型短关键字
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef const int32_t sc32;
typedef const int16_t sc16;
typedef const int8_t sc8;
typedef __IO int32_t vs32;
typedef __IO int16_t vs16;
typedef __IO int8_t vs8;
typedef __I int32_t vsc32;
typedef __I int16_t vsc16;
typedef __I int8_t vsc8;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef const uint32_t uc32;
typedef const uint16_t uc16;
typedef const uint8_t uc8;
typedef __IO uint32_t vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t vu8;
typedef __I uint32_t vuc32;
typedef __I uint16_t vuc16;
typedef __I uint8_t vuc8;
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了.
// IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
// IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE + 20) // 0x40020014
#define GPIOB_ODR_Addr (GPIOB_BASE + 20) // 0x40020414
#define GPIOC_ODR_Addr (GPIOC_BASE + 20) // 0x40020814
#define GPIOD_ODR_Addr (GPIOD_BASE + 20) // 0x40020C14
#define GPIOE_ODR_Addr (GPIOE_BASE + 20) // 0x40021014
#define GPIOF_ODR_Addr (GPIOF_BASE + 20) // 0x40021414
#define GPIOG_ODR_Addr (GPIOG_BASE + 20) // 0x40021814
#define GPIOH_ODR_Addr (GPIOH_BASE + 20) // 0x40021C14
#define GPIOI_ODR_Addr (GPIOI_BASE + 20) // 0x40022014
#define GPIOJ_ODR_ADDr (GPIOJ_BASE + 20) // 0x40022414
#define GPIOK_ODR_ADDr (GPIOK_BASE + 20) // 0x40022814
#define GPIOA_IDR_Addr (GPIOA_BASE + 16) // 0x40020010
#define GPIOB_IDR_Addr (GPIOB_BASE + 16) // 0x40020410
#define GPIOC_IDR_Addr (GPIOC_BASE + 16) // 0x40020810
#define GPIOD_IDR_Addr (GPIOD_BASE + 16) // 0x40020C10
#define GPIOE_IDR_Addr (GPIOE_BASE + 16) // 0x40021010
#define GPIOF_IDR_Addr (GPIOF_BASE + 16) // 0x40021410
#define GPIOG_IDR_Addr (GPIOG_BASE + 16) // 0x40021810
#define GPIOH_IDR_Addr (GPIOH_BASE + 16) // 0x40021C10
#define GPIOI_IDR_Addr (GPIOI_BASE + 16) // 0x40022010
#define GPIOJ_IDR_Addr (GPIOJ_BASE + 16) // 0x40022410
#define GPIOK_IDR_Addr (GPIOK_BASE + 16) // 0x40022810
// IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n)  //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr, n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr, n)  //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr, n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr, n)  //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr, n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr, n)  //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr, n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr, n)  //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr, n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr, n)  //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr, n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr, n)  //输入
#define PHout(n) BIT_ADDR(GPIOH_ODR_Addr, n) //输出
#define PHin(n) BIT_ADDR(GPIOH_IDR_Addr, n)  //输入
#define PIout(n) BIT_ADDR(GPIOI_ODR_Addr, n) //输出
#define PIin(n) BIT_ADDR(GPIOI_IDR_Addr, n)  //输入
#define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr, n) //输出
#define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr, n)  //输入
#define PKout(n) BIT_ADDR(GPIOK_ODR_Addr, n) //输出
#define PKin(n) BIT_ADDR(GPIOK_IDR_Addr, n)  //输入
void Stm32_Clock_Init(u32 plln, u32 pllm, u32 pllp, u32 pllq); //时钟系统配置
//以下为汇编函数
void WFI_SET(void);      //执行WFI指令
void INTX_DISABLE(void); //关闭所有中断
void INTX_ENABLE(void);  //开启所有中断
void MSR_MSP(u32 addr);  //设置堆栈地址
#endif

delay.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "delay.h"
#include "sys.h"
static u32 fac_us = 0; // us延时倍乘数
//初始化延迟函数
// SYSTICK的时钟固定为AHB时钟
// SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK) {
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); // SysTick频率为HCLK
	fac_us = SYSCLK;									 
}
//延时nus
// nus为要延时的us数.
// nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
	u32 ticks;
	u32 told, tnow, tcnt = 0;
	u32 reload = SysTick->LOAD; // LOAD的值
	ticks = nus * fac_us;		//需要的节拍数
	told = SysTick->VAL;		//刚进入时的计数器值
	while (1)
	{
		tnow = SysTick->VAL;
		if (tnow != told)
		{
			if (tnow < told)
				tcnt += told - tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
			else
				tcnt += reload - tnow + told;
			told = tnow;
			if (tcnt >= ticks)
				break; //时间超过/等于要延迟的时间,则退出.
		}
	};
}
//延时nms
// nms:要延时的ms数
void delay_ms(u16 nms)
{
	u32 i;
	for (i = 0; i < nms; i++)
		delay_us(1000);
}

delay.h:

1
2
3
4
5
6
7
#ifndef _DELAY_H
#define _DELAY_H
#include <sys.h>	  
void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif

五、例程

1. GPIO

1
2
3
4
5
6
7
8
9
10
11
void LED_Init(void) {
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOF_CLK_ENABLE();           //开启GPIOF时钟
    GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10; //PF9,10
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOF,&GPIO_Initure);
    HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);	//PF9置1,默认初始化后灯灭
    HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);	//PF10置1,默认初始化后灯灭
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "main.h"
#include "gpio.h"
// 通用代码
#include "delay.h"
#include "usart.h"
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init(); // 如果使用STM32CubeMX,GPIO初始化函数就会自动生成,
    			  // 就不用自己写上面的LED_Init()代码了。
  delay_init(168); // SYSCLK是168MHz。
  while (1) {
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET);
    delay_ms(500);
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET);
    delay_ms(500);
  }
}

2. 按键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
char led_flag = 0; // LED灯标识
u8 KEY_Scan() {
  if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 1) {
    delay_ms(10); // 消抖
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 1) {
      while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 1); // 直到按键松开
      return 1;
    }
  }
  return 0;  
}
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init(); // 初始化按键PA0和LED灯PF9
  delay_init(168);
  while (1) {
    if (1 == KEY_Scan()) {
      if (led_flag) {
        HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
      } else {
        HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
      }
      led_flag = !led_flag; // LED灯状态转化
    }
  }
}

3. 串口

端口复用步骤:1. 使能GPIO时钟;2. 使能相应的外设时钟;3. 把GPIO模式设为复用。

使用串口:1. 端口复用为串口;2. 串口参数初始化(波特率、停止位等);3. 使能串口;[可选:开启串口中断]4. 设置NVIC中断优先级,编写中断服务函数。

串口设置的一般步骤可以总结为如下几个步骤:

1) 串口时钟使能,GPIO时钟使能。 2) 设置引脚复用器映射:调用GPIO_PinAFConfig 函数。 3) GPIO初始化设置:要设置模式为复用功能。 4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。 5) 开启中断并且初始化NVIC,使能中断(如果需要开启中断才需要这个步骤)。 6) 使能串口。

7) 编写中断处理函数:函数名格式为USARTxIRQHandler(x是对应串口号)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1) 串口时钟使能,GPIO时钟使能。 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 使能USART1时钟,串口挂载在APB2总线下
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 串口1对应PA9和PA10,所以使能GPIOA时钟
// 2) 设置引脚复用器映射:调用GPIO_PinAFConfig 函数。 
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //PA9 复用为USART1 GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //PA10 复用为USART1
// 3) GPIO初始化设置:要设置模式为复用功能。 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9 与GPIOA10 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度 50MHz 
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA9,PA10
// 4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。 
USART_InitStructure.USART_BaudRate = bound;//一般设置为 9600; 
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位数据格式 
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
// 5) 使能串口。
USART_Cmd(USART1, ENABLE);
// 6) 开启中断并且初始化NVIC,使能相应中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; 
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级 3 
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //响应优先级 3 
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断,接收到数据中断 
USART_ITConfig(USART1, USART_IT_TC, ENABLE); //开启中断,数据发送完成中断
// 7) 相应中断服务函数
void USART1_IRQHandler(void)
// 串口数据发送和接收
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
// 查询串口状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)// USART_FLAG:USART_FLAG_RXNE(是否有接收到数据)、USART_IT_TC(是否发送完成)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// UART 初始化设置
u8 aRxBuffer[RXBUFFERSIZE];		  // HAL库使用的串口接收缓冲,每个元素是一个字符char
void uart_init(u32 bound) {
    UART_HandleTypeDef UART1_Handler; // UART_HandleTypeDef来自stm32f4xx_hal_uart.h
	UART1_Handler.Instance = USART1;					// USART1
	UART1_Handler.Init.BaudRate = bound;				//波特率
	UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
	UART1_Handler.Init.StopBits = UART_STOPBITS_1;		//一个停止位
	UART1_Handler.Init.Parity = UART_PARITY_NONE;		//无奇偶校验位
	UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
	UART1_Handler.Init.Mode = UART_MODE_TX_RX;			//收发模式
	HAL_UART_Init(&UART1_Handler);						// HAL_UART_Init()会使能UART1
	HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE); //该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲区以及接收缓冲接收最大数据量
}
// UART底层初始化,时钟使能,引脚配置,中断配置
// 此函数会被HAL_UART_Init()调用
// huart:串口句柄
void HAL_UART_MspInit(UART_HandleTypeDef *huart) {
	// GPIO端口设置
	GPIO_InitTypeDef GPIO_Initure;
	if (huart->Instance == USART1) { //如果是串口1,进行串口1 MSP初始化
		__HAL_RCC_GPIOA_CLK_ENABLE();  //使能GPIOA时钟
		__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
		GPIO_Initure.Pin = GPIO_PIN_9;			  // PA9
		GPIO_Initure.Mode = GPIO_MODE_AF_PP;	  //复用推挽输出
		GPIO_Initure.Pull = GPIO_PULLUP;		  //上拉
		GPIO_Initure.Speed = GPIO_SPEED_FAST;	  //高速
		GPIO_Initure.Alternate = GPIO_AF7_USART1; //复用为USART1
		HAL_GPIO_Init(GPIOA, &GPIO_Initure);	  //初始化PA9
		GPIO_Initure.Pin = GPIO_PIN_10;		 // PA10
		HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA10
		HAL_NVIC_EnableIRQ(USART1_IRQn);		 //使能USART1中断通道
		HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); //抢占优先级3,子优先级3
	}
}

串口中断流程:

image-20220220151501962