接前面Keil MDK 社区版小记与STM32CubeMX小记,已经可以实现,让 CubeMX 生成的代码,同时兼容Keil MDK和CubeIDE两套环境:一套代码,两个IDE可以自由切换,两个人可以用不同的IDE协同工作。

只是想不通,国内各种资料为什么执着于Keil MDK这个开发环境(其社区版又不能商用)。

现在可以回到起点,了解一下STM32的系统外设了...

系统外设?

ST的 wiki 中列的4类14种:

系统外设(System Peripherals):GPIO,EXTI,PWR,LPBAM,DMA

基本通讯外设(Basic communication peripherals):UART,SPI,I2C,I3C

定时外设(Timing peripherals):RTC,WDG,HRTIM

模拟外设(Analog peripherals):DAC,ADC

列个表

模块

全称

功能

主要特点

GPIO

General-Purpose Input/Output

通用输入/输出端口,用于控制和读取外部设备的状态

可配置为输入、输出、复用功能;支持上拉、下拉电阻

EXTI

External Interrupt/Event Controller

外部中断/事件控制器,用于处理外部信号触发的中断

支持多种触发模式(上升沿、下降沿、双边沿);灵活的中断优先级配置

PWR

Power Control

电源管理模块,用于控制微控制器的电源状态和低功耗模式

支持不同的低功耗模式(睡眠、停止、待机);电压监控和电池管理

LPBAM

Low Power Background Autonomous Mode

低功耗自动模式,用于在低功耗状态下执行后台任务

允许在低功耗模式下执行特定任务,如数据传输和采集

DMA

Direct Memory Access

直接存储器访问,用于在外设和内存之间快速传输数据

减少CPU负载,提高数据传输效率;支持多通道和多种传输模式

UART

Universal Asynchronous Receiver/Transmitter

通用异步收发器,用于串行通信

支持全双工通信,波特率可编程;具有硬件流控制和多种错误检测机制

SPI

Serial Peripheral Interface

串行外设接口,用于高速同步串行通信

支持主从模式、多从设备;全双工通信,数据传输速率高

I2C

Inter-Integrated Circuit

集成电路间通信,用于短距离通信的多主多从总线

支持多主多从模式;具有从设备地址识别和7位或10位地址模式

I3C

Improved Inter-Integrated Circuit

改进型集成电路间通信,I2C 的升级版

兼容 I2C;更高的速度和更低的功耗;支持动态地址分配

RTC

Real-Time Clock

实时时钟,用于提供准确的时间和日期信息

支持闹钟、中断和时间戳功能;内置备份电池,掉电后仍能保持时间

WDG

Watchdog Timer

看门狗定时器,用于防止系统失效或卡死

定时器溢出时触发系统复位;可编程超时时间

HRTIM

High-Resolution Timer

高分辨率定时器,用于精确的时间控制和信号生成

提供高分辨率的PWM输出;支持复杂的时间和事件控制

DAC

Digital-to-Analog Converter

数模转换器,用于将数字信号转换为模拟信号

支持多通道输出;分辨率和采样率可编程

ADC

Analog-to-Digital Converter

模数转换器,用于将模拟信号转换为数字信号

高速采样、分辨率可调;支持多通道输入和多种触发模式

东西太多了,先了解一下其中的 GPIO、UART、SPI

GPIO

这个东西...

GPIO是什么?

GPIO(General Purpose Input/Output,通用输入/输出)是微控制器或其他集成电路上的一种通用引脚,用于与外部设备进行数字信号交互。GPIO 引脚没有特定的功能,可以通过软件配置来实现多种用途,因此称为“通用”。

先借ST官方GPIO internal peripheral一张图:

对于GPIO的几种模式,可以列个表:

模式名称

宏定义

详细描述

输入模式

GPIO_Mode_IN

配置引脚为输入模式,用于读取外部信号。

输出模式

GPIO_Mode_OUT

配置引脚为输出模式,用于驱动外部设备。可以选择推挽输出(Push-pull Output)或开漏输出(Open-drain Output)。

复用功能模式

GPIO_Mode_AF

配置引脚为复用功能模式,用于连接内部外设(如 UART、I2C、SPI 等)。具体的复用功能由引脚的 AF(Alternate Function)寄存器配置。

模拟模式

GPIO_Mode_AN

配置引脚为模拟模式,用于模拟信号处理。常用于 ADC 输入或 DAC 输出。

对于输出模式PuPd:

推挽输出(Push-pull Output):标准的输出模式,能够提供强驱动能力。

开漏输出(Open-drain Output):适用于需要外部上拉电阻的应用,如 I2C 总线。

HAL函数

HAL库中主要的一些GPIO相关函数(具体可以查看stm32xxxx_hal_gpio.h文件)

初始化和配置相关

1

2HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)

HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)

状态读写

1

2

3HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

中断配置和处理

1

2HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)

HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

使用

cubemx或cubeide可以直接生成配置代码。剩下的编写读写代码就行了。单独控制一个灯闪烁,很简单:

1

2

3

4

5

6

7

8 /* Infinite loop */

/* USER CODE BEGIN WHILE */

while (1)

{

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);

HAL_Delay(1000);

/* USER CODE END WHILE */

引脚可以通过cubemx设置别名(对应C语言的宏),那么:

1 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);

如果不使用toggle,可以直接设置值:

1

2

3

4

5

6 while (1)

{

HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);

HAL_Delay(1000);

HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);

HAL_Delay(1000);

使用Delay延迟比较比较傻,正常应该使用定时器中断,比如配置使用CubeMX配置,TIM2定时器。而后在main.c 中定义:

1

2

3

4

5

6

7

8

9// 定时器中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

if (htim->Instance == TIM2)

{

// 切换 LED 状态

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

}

}

这是一个弱定义函数。

它能工作,另一方面是因为在stm32xxx_it.c中的中断处理函数调用HAL的函数。

1

2

3

4void TIM2_IRQHandler(void)

{

HAL_TIM_IRQHandler(&htim2);

}

UART

RS232等uart串口概念简单

HAL函数

初始化与配置

1

2HAL_UART_Init(UART_HandleTypeDef *huart)

HAL_UART_DeInit(UART_HandleTypeDef *huart)

UART数据传输

1

2

3

4

5

6

7

8HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

中断处理

1

2

3HAL_UART_IRQHandler(UART_HandleTypeDef *huart)

HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

使用

初始化代码cubemx可以生成。只需关注数据传输,数据传输有三个编程模式

轮询方式

中断方式(IT)

DMA方式(DMA)

先只考虑其中的中断方式。

全局变量

main函数外,定义全局变量,代表发送和接受缓冲区

1

2

3

4/* USER CODE BEGIN PV */

uint8_t recv_buffer[128] = "";

uint8_t send_buffer[128] = "";

/* USER CODE END PV */

接收

main函数中,在while之外,启动接收

1

2

3/* USER CODE BEGIN 2 */

HAL_UART_Receive_IT(&huart1, recv_buffef, 3);

/* USER CODE END 2 */

中断处理

main函数外,定义回调函数,内部书写比较自由

1

2

3

4

5

6

7

8

9/* USER CODE BEGIN 4 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);

memcpy(send_buffer, recv_buffer, 3);

HAL_UART_Transmit_IT(huart, send_buffer, 3);

HAL_UART_Receive_IT(huart, recv_buffer, 3);

}

/* USER CODE END 4 */

注意:这个函数名一定不能拼错了(Cplt是完成Complete的意思),拼错后也不会报错,没有这个函数也不会报错。因为:

在 STM32 HAL 库中,HAL_UART_RxCpltCallback 是一个弱定义(__weak)的函数。这意味着,如果用户没有显式地定义这个函数,链接器会使用库中提供的默认实现;如果用户定义了这个函数,链接器会使用用户提供的实现而不是默认的实现。

可用的回调,可以通过stm32xxxx_hal_uart.h文件查看:

1

2

3

4

5

6

7

8

9

10

11

12

13 void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Half Complete Callback */

void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Complete Callback */

void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Half Complete Callback */

void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */

void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Error Callback */

void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Complete Callback */

void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */

void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Receive Complete Callback */

void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Wakeup Callback */

void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); /*!< UART Reception Event Callback */

void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback */

void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInit callback */

SPI

SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行通信协议,通常用于微控制器与各种外围设备(如传感器、存储设备、显示屏等)之间的通信。SPI 由 Motorola 公司开发,是一种全双工通信协议,具有高速、简单和灵活的特点。

四条主要信号线:

MISO(Master In Slave Out):主设备输入、从设备输出。这条线用于从设备向主设备发送数据。

MOSI(Master Out Slave In):主设备输出、从设备输入。这条线用于主设备向从设备发送数据。

SCLK(Serial Clock):串行时钟信号,由主设备生成,用于同步数据传输。

SS(Slave Select)或 CS(Chip Select):从设备选择信号。通常由主设备控制,用于选择哪个从设备进行通信。

HAL函数

和串口一样,也是支持轮询、中断、DMA三种传输方式。

初始化与配置

1HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

数据传输

1

2

3

4

5

6

7

8

9

10

11HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size);

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size);

状态监控

1

2HAL_SPI_StateTypeDef HAL_SPI_GetState(SPI_HandleTypeDef *hspi);

uint32_t HAL_SPI_GetError(SPI_HandleTypeDef *hspi);

C语言知识补充

之前没关注这个

关于弱定义

弱定义(Weak Symbol)是一种符号(可以是函数或变量)的声明方式,它允许在链接过程中,如果没有强定义(Strong Symbol)与之对应,则使用该弱定义。弱定义通常用于库和框架中,以提供默认实现或默认值,而用户可以通过提供自己的强定义来覆盖这些默认行为。

不同的编译器通过不同的方式支持弱定义。以下是一些常见编译器的支持方式:

GCC 和 Clang:使用 attribute((weak))。

ARM 编译器:使用 __weak。

MSVC:不直接支持,但可以通过链接器选项 /alternatename 实现类似功能。

参考

https://wiki.st.com/stm32mcu/wiki/Category:Getting_started_with_STM32_system_peripherals

Weak symbol - Wikipedia