一、开发环境
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
。
芯片包结构:
2. 配置
stm32f4xx_hal_conf.h
是STM32 HAL
配置文件。
往项目添加文件和宏定义:
例程代码(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/.h
是Cortex
内核通用函数声明和定义(中断优先级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
修饰符,修饰函数,用户可以重定义函数,不定义编译器也不会报错。
三、架构
1. 总线
总线架构图:
STM32的总线矩阵用于主控总线之间的访问总裁管理(循环调度算法):Cortex内核总线、DMA总线、FlASH总线、SRAM总线。
2. 时钟树
STM32的外设时钟使能是在RCC相关寄存器中配置的。
四、STM32CubeMX
使用STM32CubeMX
配置工程:1. 新建工程;2. RCC设置(配置时钟源);3. 时钟树配置;4. 引脚配置;5.生成源码;6. 编写代码。
1. 时钟
RCC:
时钟树:
配置好SYSCLK
、AHB
总线时钟源HCLK
和Cortex
系统定时器SysTick
时钟源。
2. 修改引脚
3. 生成源码
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
启动USART1
:Connectivity --> Mode --> Asynchronous
,然后在所生成的项目工程里去掉usart.c
和在main.c
去掉#include "usart.h
和MX_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
}
}
串口中断流程: