本文章立足于浅层应用的方法而非深入原理的理解,这两者满足的是不同的需求,但是之后可能会补足原理的学习。
GPIO (General Purpose Input/Output)是通用输入输出端口的简称,GPIO 端口(元器件上的一个引脚)可以输出高低电平或者读取外部输入电平,以此实现与外部的通讯。
(实际上,操作 GPIO 端口的函数修改了 GPIO 的硬件寄存器数据,硬件的状态根据硬件寄存器数据的改变而改变)
通常来说,GPIO 有三种状态:高电平、低电平、高阻态。在 STM32 中,首先需要对特定的引脚设置为输入/输出模式,还可以在 GPIO Pull-up/Pull-down 中预先设置特定的状态。(Pull-up: 将不确定的信号通过一个电阻设置成高电平,Pull-down: 将不确定的信号通过一个电阻设置成低电平)
函数操作(HAL库):
/*
#define GPIO_PIN_RESET 0 // 低电平
#define GPIO_PIN_SET 1 // 高电平
*/
typedef enum
{
GPIO_PIN_RESET = 0, // 低电平
GPIO_PIN_SET = 1 // 高电平
}GPIO_PinState; // 使用枚举类
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin, GPIO_PinState PinState); // 写入电平
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin) // 读取电平
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin); // 对电平取反
/*
GPIOx 为 GPIOA/B/C/G 等之一,是 STM32 中对于 GPIO 端口的分组
GPIO_Pin 为 GPIO 的端口,具体见下文
GPIO_PinState 为枚举类的高电平/低电平之一
*/
一些细节:
一个 GPIO_Pin 对应着一个位(0或1),将其他部分补全以后就得到一个uint16_t(无符号 16 位,占用两字节,范围 0-65535 ,相当于short),在定义中就是:
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
#define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
同时上面的三个函数支持同时对多个端口操作,因此只要将几个 GPIO 口通过或运算即可得到操控多个 GPIO 口的参数。
UART(Universal Asynchronous Receiver/Transmitter)是通用异步收发传输器的简称,可以实现全双工通信。
几个概念:
- 双工通信(Duplex Communication) 是指在同一时刻信息可以进行双向传输。这种发射机和接收机分别在两个不同的频率上能同时进行工作的双工机也称为 异频双工机。
- 半双工通信(Half Duplex Communication),又称为双向交替通信,即通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。这种通信方式是一方发送另一方接收,过一段时间后再反过来。
- 全双工通信(Full Duplex Communication),又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式。
- 同步通信(Synchronous Communication) 要求发收双方具有同频同相的同步时钟信号,只需在传送报文的最前面附加特定的同步字符,使发收双方建立同步,此后便在同步时钟的控制下逐位发送/接收。
- 异步通信(Asynchronous Communication) 是指通信中两个字符之间的时间间隔是不固定的,而在一个字符内各位的时间间隔是固定的。相对于同步通信,异步通信在发送字符时,所发送的字符之间的时隙可以是任意的。接收端必须时刻做好接收的准备,发送端可以在任意时刻开始发送字符。因此必须在每一个字符的开始和结束的地方加上标志,即加上开始位和停止位,以便使接收端能够正确地将每一个字符接收下来。内部处理器在完成了相应的操作后,通过一个回调的机制,以便通知发送端发送的字符已经得到了回复。
UART通信协议将每个字符一位一位地传输,在没有资料传输时恒为 “1”(空闲位)。开始传输一个字符时,先发送一个 “0”(起始位)表示传输开始,接着一位一位地发送字符的 ACSLL 码(资料位),然后发送一个 “1” 或 “0” (奇偶校验位)表示资料位加上这一位后,“1” 的位数应为偶数(偶校验)或奇数(奇校验),最后发送一个高电平作为停止位表示一个字符数据传输结束并一次矫正时间同步。
波特率(baud):是衡量资料传送速率的指标,表示每秒钟传送的符号数。一个符号代表的信息量(比特数)与符号的阶数有关。因此波特率和比特率不一样。
符号的阶数:若传输使用 256 阶符号,每 8bit 代表一个符号。
函数操作(HAL库):
typedef enum
{
HAL_OK = 0x00U,
HAL_ERROR = 0x01U,
HAL_BUSY = 0x02U,
HAL_TIMEOUT = 0x03U
} HAL_StatusTypeDef; // 16 进制 8 位无符号数,后面的 U(小写也可) 代表无符号
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout); // Transmit 代表发送消息
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout); // Receive 代表接收消息
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size);
/*
*huart 为 UART 端口的地址(记得取地址!),下面有详细定义
*pData 为发送/接收的信息起始位置(地址),可以为一个 uint8_t 或者 unsigned char 类型的数组
Size 为发送/接收信息的字节数,发送 uint8_t 类型变量的数量
Timeout 起到限制时间的作用,若超时则停止并返回 HAL_TIMEOUT
_IT 表示使用串口中断模式
_DMA 表示使用串口 DMA 模式
*/
串口中断模式(以接收数据为例):
- 首先使能串口中断功能并等待。
- 接收到数据时进入中断处理函数处理数据。
- 数据接收完成以后,进入中断回调函数。
// 中断回调函数,Tx 为发送,Rx 为接收
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
// 中断处理函数(不分发送接收,在函数内判断)
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
// 中断处理函数接着选择执行接收中断处理函数或者发送中断处理函数
void UART_Receive_IT(UART_HandleTypeDef *huart);
void UART_Receive_IT(UART_HandleTypeDef *huart);
注意:
Transmit 的决定权在自己手里,当然是随时随地都能发,但是我们却不知道在执行 Receive 之后什么时候能收到数据。因此,这里的 Receive 其实是“使程序能够接收数据”,故在每一次接收完成以后若想继续接收,需要重新调用 HAL_UART_Receive_IT 函数。