接前面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文件)
| HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)
|
| HAL_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)
|
| HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
使用
cubemx或cubeide可以直接生成配置代码。剩下的编写读写代码就行了。单独控制一个灯闪烁,很简单:
| /* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);
HAL_Delay(1000);
/* USER CODE END WHILE */
|
引脚可以通过cubemx设置别名(对应C语言的宏),那么:
| HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
|
如果不使用toggle,可以直接设置值:
| 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 中定义:
| // 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
// 切换 LED 状态
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}
}
|
这是一个弱定义函数。
它能工作,另一方面是因为在stm32xxx_it.c中的中断处理函数调用HAL的函数。
| void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
|
UART
RS232等uart串口概念简单
HAL函数
| HAL_UART_Init(UART_HandleTypeDef *huart)
HAL_UART_DeInit(UART_HandleTypeDef *huart)
|
| HAL_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)
|
| HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
使用
初始化代码cubemx可以生成。只需关注数据传输,数据传输有三个编程模式
先只考虑其中的中断方式。
全局变量
main函数外,定义全局变量,代表发送和接受缓冲区
| /* USER CODE BEGIN PV */
uint8_t recv_buffer[128] = "";
uint8_t send_buffer[128] = "";
/* USER CODE END PV */
|
接收
main函数中,在while之外,启动接收
| /* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, recv_buffef, 3);
/* USER CODE END 2 */
|
中断处理
main函数外,定义回调函数,内部书写比较自由
| /* 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三种传输方式。
| HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
|
| HAL_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);
|
| HAL_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 实现类似功能。
参考