更新日期:2025.04.21
持续更新中...
By ziFly
本文章使用 ARM Cortex-M3 内核的 STM32F103C8T6 作为演示平台
创建HAL库模板
STM32Cube HAL库启动文件(例如:startup_stm32f103xb.s)
在 \Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm
里
STM32F103C8T6的FLASH容量为64KB,所以选择startup_stm32f103xb.s
手动新建STM32 HAL库模板
①导入startup_stm32f103xb.s
②导入system_stm32f1xx.c
文件(位于\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates
)
③拷贝官方模板文件夹中的stm32f1xx_hal_conf.h
stm32f1xx.h
stm32f1xx_it.h
到User文件夹中
下面是HAL库工程模板
注意:需要在Keil中全局定义 USE_HAL_DRIVER
STM32F103xB
(在Options
-> C/C++
-> Define
里)
点亮一颗LED灯
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //设置为推挽输出模式
GPIO_InitStruct.Pull = GPIO_PULLUP; //设置为上拉模式(即高电平)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
GPIO_InitStruct.Pin = GPIO_PIN_13; //使用PIN13
HAL_GPIO_Init(GPIOA, &GPIO_InitSrtuct); //初始化GPIO口
通用输入/输出
GPIO
GPIO (General - Purpose Input/Output) 即 通用输入/输出
GPIO 有以下两种功能:
输出(用于控制)
输入(用于采集)
复用是通过片上外设来控制的
通用是通过寄存器来控制的
上拉(复用)输入模式:
下拉(复用)输入模式:
开漏(复用)输出模式:
上、下拉电阻关闭;施密特触发器打开。
往ODR寄存器写0时,N-MOS管导通,P-MOS管截止,输出低电平。
往ODR寄存器写1时,N-MOS管截止,P-MOS管截止,输出高阻态。
若要输出高电平,需要在外部接上拉电阻
推挽(复用)输出模式:
上、下拉电阻关闭;施密特触发器打开。
往ODR寄存器写0时,N-MOS管导通,P-MOS管截止,输出低电平。
往ODR寄存器写1时,N-MOS管不导通,P-MOS管截止,输出高电平。
特点:可以输出高低电平,驱动能力强。电平翻转速度快(因为没有上下拉电阻)。
操作GPIO
在stm32f1xx_hal_gpio.h
中,定义了以下枚举:
typedef enum
{
GPIO_PIN_RESET = 0u,
GPIO_PIN_SET
} GPIO_PinState;
HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
在枚举类型GPIO_PinState
中,GPIO_PIN_SET = 1u,GPIO_PIN_RESET = 0u;
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
HAL_GPIO_ReadPin
函数的返回类型是 GPIO_PinState
。对于 GPIO_PinState
,它有两个参数,分别是GPIO_PIN_RESET
和 GPIO_PIN_SET
中断
GPIO口电平变化中断(外部中断EXTI)
中断触发类型:上升沿触发中断、下降沿触发中断、双边沿触发中断
aka外部中断线
GPIO_PIN_x对应EXTIx
事件(event)->送往相应的外设
中断 ->送往处理器
软件中断事件寄存器:可以由软件产生一个模拟中断
请求挂起寄存器:
中断屏蔽寄存器:
NVIC嵌套向量中断控制器在所有的外部中断线中,只有EXTI[4:0]有独立的中断处理函数;
EXTI[9:5]共享中断向量处理函数EXTI9_5_IRQHandler()
EXTI[15:10]共享中断向量处理函数EXTI15_10_IRQHandler()
若不清除请求挂起寄存器,则单片机会反复执行中断。需要使用以下函数来清除请求挂起寄存器:
__HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__);
其中,__EXIT_LINE__
是要清除的外部中断线,可以填GPIO_PIN_x
HAL_GPIO_EXTI_Callback()
在STM32中,中断向量的优先级分为两层,分为 抢占优先级 和 响应优先级 。优先级的数字越小代表越优先。
两个中断同时发生时,先比较抢占优先级,再比较响应优先级。
若某个中断正在执行中,另外一个中断突然发生,只比较二者的抢占优先级
串口
在单片机中最常见的串口:TTL串口(异步通信)
TX
RX
两机通常要共地
USART 通用同步或异步接收器和发送器
在stm32f1xx_hal_uart.c
中,有以下定义:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);
对于枚举类型HAL_StatusTypeDef
,它的结构是这样的
typedef enum
{
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef;
第一个参数是需要操作的串口的结构体指针
第二个参数是需要发送的信息的指针。
第三个参数是发送数据的长度,
第四个参数是超时时间(ms)可以使用HAL_MAX_DELAY
来设置最大超时时间。
接收串口数据:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
第二个参数是存放接收到的数据的指针,
第三个参数是接收数据的长度
其余和HAL_UART_Transmit()
函数一样
轮询模式会造成程序阻塞,建议使用中断模式或者DMA模式。
中断发送模式下不会阻塞程序,可以节约CPU时间,
中断发送模式相比于轮询发送模式,少了个超时时间
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
每当移位寄存器将一帧数据转移到发送数据寄存器(TDR) 中,会触发一次TDR非空中断。
其中,中断处理函数为:
void USART2_IRQHandler(void);
但是,由于每一次发送或者接收都要调用这个中断,我们一般使用下面这个中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
当数据发送完之后,单片机会自动调用此函数
同理,当数据接收完成之后,单片机会自动调用下面这个函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
DMA发送/接收模式与中断发送/接收模式类似。
串口空闲中断 ——用于接收不定长数据
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
第一个参数:用于接收的串口指针
第二个参数:存放数据的数组
第三个参数:最大接收长度(一般填写数组的最大长度)
HAL_UARTEx_ReceiveToIdle_DMA();
的回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);
用于接收不定长数据,需要传入接收字节的长度
注意:DMA传输过半也会触发 HAL_UARTEx_RxEventCallback
函数
可以使用
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
第一个参数:DMA通道的指针地址
确认是谁触发了回调函数
if (huart == &huart1) function();
IIC 通讯
IIC分为 SDA 数据线 和 SCL 时钟线。SDA用于传输数据,SCL用于传输时钟信号
半双工通讯 主从模式,可以连接多个设备 | 总线协议
IIC通讯例子(以AHT20温湿度传感器为例,STM32和AHT20)
通讯未开始时,SDA、SCL由外部上拉电阻拉至高电平。
主机(STM32)将SDA下拉,发送IIC通信启动信号,I2C通讯开始。
主机(STM32)在SCL为低电平时,设置SDA的电平;从机(AHT20)在SCL为高电平时,读取SDA的电平。
循环8次(即传输 1字节)后,从机(AHT20)在SCL为低电平的时候,将SDA拉低(发送ACK信号)
此时,从机(AHT20)地址发送完成。
双方开始通讯,此时,STM32为从机,AHT20为主机,双方重复步骤4,传输数据。
主机在SCL为1的时候,将SDA拉高,完成数据的传输。
主机发送从机地址(0x71):
如果主机发起通信的目的是为了设置(写)从机,第0位是0。
如果主机发起通信的目的是为了从从机读取数据,第0位是1。
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
第一个参数:外设操纵指针
第二个参数:需要读取的从机地址
接收数据变量的指针
读取多少位数据
超时时间
#include "aht20.h"
#define AHT20_ADDRESS 0x70
void AHT20_Init(void) {
uint8_t readBuffer;
HAL_Delay(40);
HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &readBuffer, 1, HAL_MAX_DELAY);
if ((readBuffer & 0x08) == 0) {
uint8_t sendBuffer[3] = {0xBE, 0x08, 0x00};
HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY);
}
}
状态机
void AHT20_Measure() {
static uint8_t sendBuffer[3] = {0xBE, 0x08, 0x00};
HAL_I2C_Master_Transmit_IT()
}
状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。有限状态机简写为FSM(Finite State Machine),主要分为2大类:
第一类,若输出只和状态有关而与输入无关,则称为Moore状态机
第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
typedef enum _Car_StateTypeDef{
INIT,
SENDING,
SENT,
READING,
READ
} Car_StateTypeDef;
typedef enum _Car_EventTypeDef{
INIT,
SENDING,
SENT,
READING,
READ
} Car_StateTypeDef;