STM32G431-Nucleo-64开发板使用小记
STM32G431-Nucleo-64开发板使用小记
STM32G431-Nucleo-64开发板
基本介绍
实物图片:
引脚示意图:
特别提醒:如果要设置频率为170MHz(最大频率),需要修改输入为24MHz
点亮LED灯
软件选择
使用CubeMX与MAD-ARM
STMCubeMX:
STMCubeMX是ST公司推出的一种自动创建单片机工程及初始化代码的工具。
MDK-ARM:
Keil公司开发的ARM开发工具MDK,是用来开发基于ARM核的系列微控制器的嵌入式应用程序。
实验流程
使用 CubeMX 生成初始化代码 -> 使用 MDK-ARM 编写主函数并编译 -> 使用开发板自带的ST-LINK将编译好的程序烧录到开发板 -> 搭建实物电路 -> 开发板上电,观察现象。
具体操作
打开CubeMX,可以看到如下界面。点击红色方框内的ACCESS TO MCU SELECTRO选择芯片型号。
输入芯片型号查找对应芯片,选择“STM32G431RBT6”芯片。具体操作如图所示,操作执行后点击Start Progect进入配置页面。
芯片配置页面如下:
配置包括 “Pinout & Configuration”、“Clock Configuration”、“Project Manager” 和 “Tools”。
首先配置 “Project Manager”,方式如下:其中 “Code Generator” 和 “Advanced Setings” 暂时用不到。
想要点亮LED灯,我们需要对GPIO引脚进行控制,因此返回 “Pinout & Configuration” 配置选项卡,进行输入输出引脚配置。我们选择引脚 PC3 ,将其初始化为高电平,并将输出电平再高低之间进行切换,使用HEL函数延时,每隔1000ms进行一次变换。具体操作如图所示:
最后点击生成代码。
使用MDK-ARM打开工程,查看CubeMX生成的代码:
在主函数中插入我们要编写的LED闪烁代码:
代码如下:
1 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_3, GPIO_PIN_SET); |
连接电路图,结果展示:
开发板配置
开发板中的用户按键是高电平有效,默认状态下接PC13。
ST Board 的默认配置中使用外部中断配置按键。
如果使用GPIO口输入模式来实现按键行为,则需要配置为 Pull-down 模式
基本工程配置:
直接生成工程:
可以看到,针对于这个开发板,默认芯片时钟配置如下:
学习使用CubeMX中的Example
首先打开CubeMX进入工程选项界面,选择 “Example Selector” 。
续:
在生成选项中,选择生成目录,然后直接使用MDK-ARM打开即可:
打开文档中的readme.txt文件,阅读该例程的使用方法:
阅读文档可知,PA08-PA11分别输出四种不同占空比的PWM波形。
编译文件并烧录到开发板中,使用示波器测试PWM波形,得如下结果:
如果对Example中的代码进行了修改并重新编译,可能烧录会出现报错,报错如下:
该报错可阅读此文章寻求解决方案。
按键控制LED灯
首先打开之前 Nucleo-G431RB 的 CubeMX 工程文档,打开其中的 “Nucleo-G431RB_TEST.ioc” 文件:
将其中的 PC2 GPIO口配置为输入 IO 口,具体配置为输入模式、上拉输入。
注意:选择 GPIO 口的输入配置要依据实际电路的连接方式。在我的电路连接中,我将按键的一端与开发板的 GND 相连,另一端与 PC2 相连。则此时应该选择上拉输入——IO 口没有外部信号输入时,STM32 检测到是高电平,有信号时(按键按下时),跟随信号电平(接地,变为低电平)。
将 PC3 GPIO口配置为输出,默认输出为高电平(执行初始化函数
MX_GPIO_Init()
时会输出高电平)。
生成代码,使用MDK-ARM打开生成好的工程文件,在工程文件中的 main.c 文件做如下修改:
1 | /* Private define ------------------------------------------------------------*/ |
修改完成后,即可连接电路,检验效果。上述代码执行效果为:
按下按键时可以检测到按键被按下,松手后执行任务(改变LED灯亮灭)。
效果如图:
串口通信
STM32中串口通信有三种方式:
轮询
- 轮询式串口通信:在主函数的
while(1)
中不停地调用串口通信函数进行通信。 - 优点:实现逻辑简单
- 缺点:查询方式下CPU的负担较重,浪费了处理器的能力,不能够很好的处理其他的事件
- 轮询式串口通信:在主函数的
中断
中断式串口通信:在接收到信息或需要发送数据时产生中断,在中断服务程序中完成数据的接收与发送。
优点:相比于轮询式,中断式对CPU利用率要高。
缺点:
复杂的系统中,比如移动机器人,处理器需要处理串行口通信,多个传感器数据的采集以及处理,实时轨迹的生成,运动轨迹插补以及位置闭环控制等等任务,牵扯到多个中断的优先级分配问题。为了保证数据发送与接收的可靠性,需要把UART的中断优先级设计较高,但是系统可能还有其他的需要更高优先级的中断,必须保证其定时的准确,这样就有可能造成串行通讯的中断不能及时响应,从而造成数据丢失。
DMA
- DMA:Direct Memory Access (直接内存访问)。使用DMA进行串口通信时,CPU只需要数据传输开始和结束时做一点处理外,在传输过程中可以进行其他的工作。
- 如果传输的数据量较大,或者传输速度超过115200时,建议选择DMA方式实现串口通信。
串口通信函数:推荐使用C标准库中的 printf()
函数进行串口通信。想要在单片机使用 printf()
需要:
- 包含头文件
#include <stdio.h>
- 在 Options for Target... 选项卡的 Target 选项栏中的 Code Generation 区域勾选 Use MicroLIB
- 进行串口重定向——即重新实现
fputc()
查询模式 & printf()函数重定向
配置串口:注意在Nucleo-G431RB开发板上只能使用 PA2 和 PA3 引脚来实现开发板接usb线与电脑进行串口通信。其中 PA2 和 PA3 引脚可以选择 LPUART1 和 USART2 两种。这里我们选择 LPUART1 进行测试。串口调试的配置与串口测试软件保持一致即可。
生成代码,打开工程,进行工程设置:
如果不设置使用 Use MicroLIB,也可以添加如下代码实现串口重定向:
1 | /* USER CODE BEGIN 0 */ |
添加代码:
添加头文件:user_log.h文件见目录: “HAL库小记 -> 串口调试策率 -> 日志打印文件”
1
2
3
4
5/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */重写
fputc()
函数:1
2
3
4
5
6int fputc(int ch, FILE *f)
{
uint8_t temp[1] = {ch};
HAL_UART_Transmit(&hlpuart1, temp, 1, 2);//hlpuart1需要根据你的配置修改
return ch;
}记录另一种写法,或许会更加规范(适用范围广)
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
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t*) &ch, 1, 0xffff);
return ch;
}
//网上说现在还需要加上这个函数,但是我没使用过gcc编译所以没有验证过这部分代码
//_write函數在syscalls.c中, 使用__weak定義, 所以可以直接在其他文件中定義_write函數
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}添加测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/* USER CODE BEGIN 2 */
user_main_info("init HAL");
user_main_info("Config System Clock");
user_main_info("GPIO init finished");
user_main_info("UART init finished");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
user_printf("___ ___ _ ");
user_printf("| \\/ | | | ");
user_printf("| . . | _ _ | | ___ __ _ ");
user_printf("| |\\/| || | | || | / _ \\ / _` |");
user_printf("| | | || |_| || |____| (_) || (_| |");
user_printf("\\_| |_/ \\__, |\\_____/ \\___/ \\__, |");
user_printf(" __/ | __/ |");
user_printf(" |___/ |___/ ");
user_main_info("Enter while(1)");
测试结果:
中断方式
使用CubeMX配置串口,打开相应串口中断
打开工程,修改 uart.c 实现串口重定向:
1 | /* USER CODE BEGIN 0 */ |
打开 main.c,配置中断服务函数
DMA
DMA传输方式
方法1:DMA_Mode_Normal,正常模式,
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
方法2:DMA_Mode_Circular ,循环传输模式
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输模式
https://www.its203.com/article/qq_26575553/89374803#:~:text=%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8F1%E7%9A%84MCU%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%98%AFF2...F4%EF%BC%8C%E7%94%9A%E8%87%B3
1.8寸 TFT LCD显示
使用CubeMX配置引脚,其中 PA5 引脚连接VCC供电。
生成代码,然后对代码进行修改:
解压LCD_Driver.zip到CubeMX生成的MDK-ARM文件加中:
打开工程,将解压的文件添加的你的工程中:
将头文件包含到main.c文件中:
1
2
3
4
5
6/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */打开lcd_init.h文件,修改其中的 “LCD端口定义” ,将端口定义与连线相匹配。
注释掉主函数中的
MX_GPIO_Init();
并将复制下述代码进行测试:1
2
3LCD_Init();//LCD初始化
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
LCD_ShowString(24,30,"Hello,World!",RED,WHITE,16,0);
定时器
使用STM32的定时器首先要理解STM32的时钟树,STM32中的时钟源是可配置的。有三种时钟源供选择:
由一个三路选择器组成的系统时钟选择器,可以选择下面三种时钟源的一种作为系统时钟源:
- 高速外部时钟HSE
- 优点:外部时钟产生的时钟频率较为精确
- 高速内部时钟HSI
- 优点:功耗低,不需要额外的器件,起震快
- 缺点:但是精度不能保证
- 锁相环时钟PLLCLK
- 锁相环可以用来倍频,开发板上外接8M晶振,但是STM32主频却能跑72M,这离不开锁相环(PLL)的作用。
定时器要实现计数必须有个时钟源,基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。
F407有三种时钟源可以用作系统时钟:内部高速时钟、外部高速时钟、PLL时钟。 一般我们希望芯片工作在最高频率168MHz,而无论是内部还是外部时钟都是达不到的,所以通常都是用PLL时钟作为系统时钟。 外部时钟通常都比内部时钟要稳定精确,所以一般还会用外部时钟作为PLL的输入。 F407还有一个低速时钟用来驱动RTC,以及满足低电压模式下的功能需求。
通常系统总线AHB的频率设置为168MHz,高速外设总线APB2频率设置为84MHz,低速外设总线APB1频率设置为42MHz。 这些总线频率可以通过配置RCC_CFGR和RCC_PLLCFGR实现。
GPS模块
GPS模块硬件可以将卫星传来的信号进行收集,然后我们可以通过串口通信的方式从GPS模块中读取卫星报文。读取后的报文我们可以将其缓存下来,然后进行译码。
了解了 NMEA 格式有之后,我们就可以编写相应的解码程序了,而程序员 Tim(xtimor@gmail.com)提供了一个非常完善的 NMEA 解码库,在以下网址可以下载到:http://nmea.sourceforge.net/ ,直接使用该解码库,可以避免重复发明轮子的工作。
移植 MultiButton
MultiButton简介
Github里面的嵌入式开源项目,一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,能够实现下述按键事件:
事件 | 说明 |
---|---|
PRESS_DOWN | 按键按下,每次按下都触发 |
PRESS_UP | 按键弹起,每次松开都触发 |
PRESS_REPEAT | 重复按下触发,变量repeat计数连击次数 |
SINGLE_CLICK | 单击按键事件 |
DOUBLE_CLICK | 双击按键事件 |
LONG_PRESS_START | 达到长按时间阈值时触发一次 |
LONG_PRESS_HOLD | 长按期间一直触发 |
在STM32G431-Nucleo-64开发板中使用
首先使用CubeMX建立一个工程,初始化串口、测试LED和测试按键的GPIO口:
生成代码,打开工程文件夹:
然后在此处下载项目文件到本地:
将红框内的源文件放入到工程文件夹的MDK-ARM文件夹中,这里我给源文件了一个单独的文件夹
同时我也移植了我们之前使用的 “user_log.h” 文件。
打开工程,从工程外部添加文件:
源文件中的头文件,可以添加进来,也可以不添加,只要添加头文件的查找地址,然后在写代码时包含头文件即可:
编写测试代码
打开 uart.c 文件,添加串口重定向代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/* USER CODE BEGIN 0 */
PUTCHAR_PROTOTYPE
{
uint8_t temp[1] = {ch};
HAL_UART_Transmit(&hlpuart1, temp, 1, 2);//hlpuart1需要根据你的配置修改
return ch;
}
/* USER CODE END 0 */打开 main.c 文件
添加新的包含
1
2
3
4
5
6/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */添加新的变量声明——按键结构体声明
1
2
3
4
5/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
struct Button button1;
/* USER CODE END PV */回调函数:
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/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t read_button1_GPIO()
{
return HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
}
void button_callback(void *button)
{
uint32_t btn_event_val;
btn_event_val = get_button_event((struct Button *)button);
switch(btn_event_val)
{
case PRESS_DOWN:
printf("---> key1 press down! <---\r\n");
break;
case PRESS_UP:
printf("***> key1 press up! <***\r\n");
break;
case PRESS_REPEAT:
printf("---> key1 press repeat! <---\r\n");
break;
case SINGLE_CLICK:
printf("---> key1 single click! <---\r\n");
break;
case DOUBLE_CLICK:
printf("***> key1 double click! <***\r\n");
break;
case LONG_PRESS_START:
printf("---> key1 long press start! <---\r\n");
break;
case LONG_PRESS_HOLD:
printf("***> key1 long press hold! <***\r\n");
break;
}
}进入
main()
函数,while(1)
之前:初始化对象 -> 注册函数 -> 启动函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LPUART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
printf("MultiButton Test...\r\n");
//初始化按键对象
button_init(&button1, read_button1_GPIO, 1,0);
//注册函数
button_attach(&button1, PRESS_DOWN, button_callback);
button_attach(&button1, PRESS_UP, button_callback);
// button_attach(&button1, PRESS_REPEAT, button_callback);
// button_attach(&button1, SINGLE_CLICK, button_callback);
// button_attach(&button1, DOUBLE_CLICK, button_callback);
// button_attach(&button1, LONG_PRESS_START, button_callback);
// button_attach(&button1, LONG_PRESS_HOLD, button_callback);
//启动按键
button_start(&button1);
/* USER CODE END 2 */设置一个5ms间隔的定时器循环调用后台处理函数,可以在while(1)中使用滴答定时器:
1
2
3
4
5
6
7
8/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//每隔5ms调用一次后台处理函数
button_ticks();
HAL_Delay(5);
/* USER CODE END WHILE */
勾选使用 MicroLIB
编译,烧录,打开串口助手,测试:
移植 GuiLite
简介
GuiLite是一个轻量的,可以运行在MPU平台的开源图形库。
只要能点亮你的显示屏上一个像素点,就可以使用这个图形库绘制出复杂的图形。
GuiLite还支持多个平台:
支持的操作系统:iOS/macOS/WatchOS,Android,Linux(ARM/x86-64),Windows(包含VR),RTOS... 甚至无操作系统的单片机
支持的开发语言: C/C++, Swift, Java, Javascript, C#, Golang...
支持的第3方库:Qt, MFC, Winforms, CoCoa...
⚙️️最低硬件要求:
Processor | Disk/ROM space | Memory |
---|---|---|
24 MHZ | 29 KB | 9 KB |
在STM32G431-Nucleo-64开发板中使用
在TFT屏示例的基础上进行GUI测试:
此处注意,可能会需要扩大默认的堆空间长度,具体操作如下:
CubeMX中修改堆的大小:
打开stm32的启动代码(汇编代码)即可看到修改成功:
移植过程:
将Example中的实例代码放到自己的工程中,以Hello3DWave为例:
将这两个文件复制到自己的工程中,可以新建一个名为UIcode的文件夹存放。
在自己的keil工程中的Target中新建一个Group:
将例程代码添加到Group中:
添加完成后即可编写代码,注意:检查自己的工程中是否勾选了Use MicroLIB,如果勾选了就要取消勾选,因为我们需要编译器去解析.cpp文件,不能使用C库
编写测试代码
在 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/* USER CODE BEGIN 0 */
//延时1ms函数
void delay_ms(int ms)
{
HAL_Delay(ms);
}
//RGB888转RGB565
//Transfer GuiLite 32 bits color to your LCD color
//Encapsulate your LCD driver:
void gfx_draw_pixel(int x, int y, unsigned int rgb)
{
//LCD_Fast_DrawPoint(x, y, GL_RGB_32_to_16(rgb));
//添加带颜色的画点函数
LCD_DrawPoint(x, y, GL_RGB_32_to_16(rgb));
}
//UI entry
struct DISPLAY_DRIVER
{
void (*draw_pixel)(int x, int y, unsigned int rgb);
void (*fill_rect)(int x0, int y0, int x1, int y1, unsigned int rgb);
} my_driver;
//extern function
/* USER CODE END 0 */上面代码中的倒数第二行
//extern function
中的函数声明要去 UIcode.cpp 的最后去找将此函数声明复制到
//extern function
处,然后在while(1)
前添加如下代码:1
2
3my_driver.draw_pixel = gfx_draw_pixel;
my_driver.fill_rect = NULL;//gfx_fill_rect;
//function like: startHelloStar(NULL,128,160,2,&my_driver);编译然后烧录,不需要管警告信息。
HAL库小记
硬件抽象层(HAL,Hardware Abstraction Layer)驱动程序提供了一组功能丰富,易于与应用上层交互的 API,它们涵盖了常见的外围设备,可以非常方便的向其它型号 STM32 微控制器移植。同时还实现了用户回调函数机制,允许并发调用
USART1
以及USART2
等外设,并且支持轮询、中断、DMA 三种 API 编程模式。
GPIO口
初始化及重置函数
1 | //初始化引脚 |
GPIO 口操作相关函数
1 | //读取电平状态 |
GPIO口枚举常量
1 | typedef enum |
GPIO口名称
1 | /* IO口默认定义 -----------------------------------------------------------*/ |
GPIO的API
函数名称 | 功能描述 |
---|---|
HAL_GPIO_ReadPin() |
读取指定输入端口的引脚状态; |
HAL_GPIO_WritePin() |
设置或者清除指定的数据端口位; |
HAL_GPIO_TogglePin() |
切换指定的 GPIO 引脚状态; |
HAL_GPIO_LockPin() |
锁定 GPIO 引脚配置寄存器; |
HAL_GPIO_EXTI_IRQHandler() |
该函数用于处理 EXTI 中断请求; |
HAL_GPIO_EXTI_Callback() |
EXTI 线检测回调函数; |
串口通信
收发函数
1 | //发送数据 |
printf()
重定向
1 |
|
串口调试策略
日志打印文件
keil编译器要求文件的结尾必须要有一个空行,如果没有将给出一个警告
1 | /** |
添加文件时若出现报错,则需要手动增加头文件搜索路径:
日志打印个性化文字
Text to ASCII Art Generator (TAAG)
1 | ___ ___ _ |
代码:要在报错的符号前面加上一个反斜杠,因此代码如下
1 | user_printf("___ ___ _ "); |
分割线内内容摘自Uinlo的个人博客:
HAL 通用命名规则
对于共有的系统外设,无需使用指针或者实例对象,这个规则适用于
GPIO、SYSTICK、NVIC、RCC、FLASH
外设,例如函数 HAL_GPIO_Init()
只需要 GPIO
的地址及其配置参数。
1 | HAL_StatusTypeDef HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *Init) { |
每个外设驱动程序当中都定义有处理中断和特定时钟配置的宏,这些宏会被导出到外设驱动的头文件,以便于扩展文件使用,这些用于处理中断和特定时钟配置的宏如下所示:
宏定义 | 功能描述 |
---|---|
__HAL_PPP_ENABLE_IT(__HANDLE__, __INTERRUPT__) |
使能一个特定的外设中断; |
__HAL_PPP_DISABLE_IT(__HANDLE__, __INTERRUPT__) |
失能一个特定的外设中断; |
__HAL_PPP_GET_IT (__HANDLE__, __ INTERRUPT __) |
获取一个指定外设的中断状态; |
__HAL_PPP_CLEAR_IT (__HANDLE__, __ INTERRUPT __) |
清除一个指定外设的中断状态; |
__HAL_PPP_GET_FLAG (__HANDLE__, __FLAG__) |
获取一个指定外设的标志位状态; |
__HAL_PPP_CLEAR_FLAG (__HANDLE__, __FLAG__) |
清除一个指定外设的标志位状态; |
__HAL_PPP_ENABLE(__HANDLE__) |
使能一个外设; |
__HAL_PPP_DISABLE(__HANDLE__) |
失能一个外设; |
__HAL_PPP_XXXX (__HANDLE__, __PARAM__) |
指定 PPP 外设驱动的宏; |
__HAL_PPP_GET_ IT_SOURCE (__HANDLE__, __INTERRUPT__) |
检查指定的中断源; |
注意:NVIC 和 SYSTICK 是 ARM Cortex-M4 提供的两个核心功能,与之相关的 API 都位于
stm32f4xx_hal_cortex.c
源文件。
当从寄存器读取状态标志位时,其结果由移位值组成,具体取决于读取值的数量与大小。这种情况下,返回的状态宽度为 32 位,例如:
1 | STATUS = XX | (YY << 16) |
外设 PPP 的指针在调用 HAL_PPP_Init()
之前有效,初始化函数会在修改指针字段之前进行检查:
1 | HAL_PPP_Init(PPP_HandleTypeDef) |
可以使用条件式宏定义或者伪代码宏定义:
条件式宏定义:
1
#define ABS(x) (((x) > 0) ? (x) : -(x))
伪代码宏定义(多指令宏):
1
2
3
4
5#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \
do { \
(__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \
(__DMA_HANDLE_).Parent = (__HANDLE__); \
} while (0)
中断处理程序与回调函数
除了各种 API 函数之外,HAL 固件库外设驱动程序当中还包含有:
- 用户回调函数;
- 由
stm32f4xx_it.c
调用的HAL_PPP_IRQHandler()
外设中断处理程序;
回调函数被定义为带有 weak
属性的空函数,使用时必须在用户代码当中进行定义,HAL
固件库当中存在三种类型的用户回调函数:
- 外围系统级初始化与反向初始化回调函数
HAL_PPP_MspInit()
和HAL_PPP_MspDeInit
; - 外理完成回调函数
HAL_PPP_ProcessCpltCallback
; - 错误的回调函数
HAL_PPP_ErrorCallback
;
回调函数 | 示例 |
---|---|
HAL_PPP_MspInit()
HAL_PPP_MspDeInit() |
例如 HAL_USART_MspInit() ,由
API 函数 HAL_PPP_Init()
进行调用,用于进行外设的系统级初始化(GPIO、时钟、DMA、中断); |
HAL_PPP_ProcessCpltCallback |
例如
HAL_USART_TxCpltCallback ,当处理执行完成时,由外设或者 DMA
中断处理程序进行调用; |
HAL_PPP_ErrorCallback |
例如
HAL_USART_ErrorCallback ,当发生错误时,由外设或者 DMA
中断处理程序进行调用; |
HAL 全局初始化
stm32f4xx_hal.c
提供了一组 API 来初始化 HAL
核心实现:
HAL_Init()
:该函数必须在应用程序启动时调用,用于初始化数据和指令,缓存预获取队列,设置 SysTick 定时器(基于 HSI 时钟)每间隔1ms
产生一个最低优先级中断,将优先级分组设置为4
位,调用HAL_MspInit()
用户回调函数来执行系统级初始化(时钟、GPIO、DMA、中断);HAL_DeInit()
:重置所有外设,调用用户回调函数HAL_MspDeInit()
执行系统级反向初始化;HAL_GetTick()
:获取当前 SysTick 定时器的计数值(在 SysTick 中断内递增),用于外设驱动程序处理超时;HAL_Delay()
:通过 SysTick 定时器实现一个以毫秒为单位的延迟;
IO 操作
带有内部数据处理(发送、接收、读/写)的 HAL
函数,通常具备轮询(Polling)
、中断(Interrupt)
、DMA
三种处理方式:
底层探究
新建工程
使用windows系统的PowerShell生成Blank template文件的目录树:
1 | PS C:\Users\qjy\Desktop\Blank template> tree /F |
添加 “startup_stm32g431xx.s” 和 “system_stm32g4xx.c”
两个文件到 Blank template 文件夹目录下:
新建两个文件,编写系统初始化函数和主函数,如图。然后编译。
编译成功,包含一个警告,内容为:void SystemInit(void)
函数没有函数原型,此处可以忽略该警告。
编译后的工程目录树如下:
1 | PS C:\Users\qjy\Desktop\Blank template> tree /F |
其中keil中的project目录树为:
我们知道系统的时钟是通过系统复位和时钟控制(RCC)寄存器配置的。 在第6.3节中列举了25个RCC寄存器的位定义和偏移地址。参考CubeMX中生成的stm32g431xx.h文件,定义如下的结构体用于访问RCC的每个寄存器:
1 | /** |
RCC基地址宏定义
1 |
那么我们就可以通过如下的形式来访问RCC的寄存器了。
1 | RCC->CR |
CubeMX小记
引用:调试接口配置。讲道理SWD应该是首选,如图5所示。如果不设置的话,编译下载后,你就会发现下载不了程序了,有复位键还好,没复位键就有得愁了。
选择Serial Wire是与下图中的SW相匹配
引用:如果在STM32CubeMX中选择SW协议,MDK 也必须 选择SW协议。JTAG协议配置也同理。否则会造成下载和调试失败。在实际项目中SW协议使用使用的比较多,SW与JTAG相比,速度更快,占用的引脚更少,推荐大家配置成SW协议。
Keil配置
Edit—>Configuration 进行配置
Configuration
- Editor
- General—>Encoding—>Chinese GB2312(Simplifies)
- Funcition—>default
- Look & Feel—>default
- Feil & Project Handling—>choose all expect first one
- C/C++ Files—>Change Tab Size to 4
- ASM Files—>default
- Other Files—>default
- Colors & Fonts
- C/C++ Editor files—>Test—>Font: Cascadis Code/Size: 20
- Shortcut Keys
- Project: Rebulid all target files—>Ctrl+R
- View: Bulid Output Windows—>Ctrl+B
- View: Project Windows—>Ctrl+D