前言
因为最近老板突发奇想,想要做一个轻量级的AI工具,其他保密,我负责整理采集到的数据并上传到服务器。
趁此大好机会,赶紧复习一下串口的知识。一些和实际开发不相关的基础知识我就直接跳过了。
一、基本理论
1、单片机通信基础
- 同步和异步通信
- 波特率
- 常见串行通信接口
2、串口通信基础
串口概念
RS-232串口电平与CMOS以及TTL的对比
设备间的RS-232通信示意图
STM32串口与电脑USB口通信示意图
RS-232异步通信协议
3、UART 和 USART 的区别
区别点 | USART | UART |
---|---|---|
双工模式 | 半双工 | 全双工 |
速度 | 快 | 慢 |
时钟信号 | 要时钟信号和数据信号互相配合 | 不需要 |
兼容 | USART可以做到UART的功能 | 做不到USART |
数据形式 | 块形式 | 一次一个字节 |
复杂度 | 复杂 | 简单 |
波特率 | 不需要知道 | 需要设置波特率 |
变速 | 恒速传输 | 可变速传输 |
4、USART
引脚数量以及对应的引脚,可以参考MCU手册以及STM32F1硬件手册。
- USART框图
- 设置USART/UART波特率
通过波特率计算得到要设置的寄存器参数。
将两个×16放在一起合并,简化公式。
- USART寄存器介绍
通过波特率计算得到要设置的寄存器参数。
将两个×16放在一起合并,简化公式。
- USART寄存器介绍
5、HAL库的串口机制
HAL库外设初始化MSP回调机制
HAL库中断回调机制
USART/UART异步通信配置步骤
IO引脚复用功能
IO引脚复用 F4 F7 H7
每一个引脚都可以选择相应的复用,对F1进行了改进
同步模式
在同步模式下连接时需要以下引脚:
SCLK:发送器时钟输出。该引脚用于输出发送器数据时钟,以便按照 SPI 主模式进行同步发送(起始位和结束位上无时钟脉冲,可通过软件向最后一个数据位发送时钟脉冲)。RX 上可同步接收并行数据。这一点可用于控制带移位寄存器的外设(如 LCD 驱动器)。时钟相位和极性可通过软件编程。在智能卡模式下,SCLK 可向智能卡提供时钟。在硬件流控制模式下需要以下引脚:
nCTS:“清除以发送”用于在当前传输结束时阻止数据发送(高电平时)。
nRTS:“请求以发送”用于指示 USART 已准备好接收数据(低电平时)。
中断控制
出现以下情况时,可使UART 产生中断:
由于所有中断事件在发送到中断控制器之前会一起进行“或运算”操作,所以任意时刻 UART 只能向中断产生一个中断请求。通过查询中断状态函数,软件可以在同一个中断服务函数里处理多个中断事件(多个并列的if 语句)。
FIFO 操作
Stellaris 系列ARM 的UART 模块包含有2 个16 字节的FIFO:一个用于发送,另一个用于接收。可以将两个FIFO 分别配置为以不同深度触发中断。可供选择的配置包括:1/8、 1/4、1/2、3/4 和7/8 深度。例如,如果接收FIFO 选择1/4,则在UART 接收到4 个数据时产生接收中断。
- 发送FIFO的基本工作过程
只要有数据填充到发送FIFO 里,就会立即启动发送过程。由于发送本身是个相对缓慢的过程,因此在发送的同时其它需要发送的数据还可以继续填充到发送 FIFO 里。当发送 FIFO 被填满时就不能再继续填充了,否则会造成数据丢失,此时只能等待。这个等待并不会很久,以9600 的波特率为例,等待出现一个空位的时间在1ms 上下。发送 FIFO 会按照填入数据的先后顺序把数据一个个发送出去,直到发送 FIFO 全空时为止。已发送完毕的数据会被自动清除,在发送FIFO 里同时会多出一个空位。
- 接收FIFO的基本工作过程
当硬件逻辑接收到数据时,就会往接收FIFO 里填充接收到的数据。程序应当及时取走这些数据,数据被取走也是在接收FIFO 里被自动删除的过程,因此在接收 FIFO 里同时会多出一个空位。如果在接收 FIFO 里的数据未被及时取走而造成接收FIFO 已满,则以后再接收到数据时因无空位可以填充而造成数据丢失。
- FIFO解决的问题
收发FIFO 主要是为了解决UART 收发中断过于频繁而导致CPU 效率不高的问题而引入的。在进行 UART 通信时,中断方式比轮询方式要简便且效率高。但是,如果没有收发 FIFO,则每收发一个数据都要中断处理一次,效率仍然不够高。如果有了收发FIFO,则可以在连续收发若干个数据(可多至14 个)后才产生一次中断然后一并处理,这就大大提高了收发效率。
完全不必要担心FIFO 机制可能带来的数据丢失或得不到及时处理的问题,因为它已经帮你想到了收发过程中存在的任何问题,只要在初始化配置UART 后,就可以放心收发了, FIFO 和中断例程会自动搞定一切。
串口频繁进入中断导致的问题
void USART1_IRQHandler(void)
{
if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
{
USART_ReceiveData(USART1);
USART_ClearFlag(USART1, USART_FLAG_ORE);
}
if( USART_GetITStatus(USART1,USART_IT_RXNE) != RESET )
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
USART_ClearFlag(USART1,USART_IT_RXNE);
Uart1_Val.Real_Data = USART_ReceiveData( USART1 );
Uart1_IT_Reci_Fuc();
}
}
二、CubeMX 配置串口
选择处理器
我这里项目用的是 H750ZBT6 的芯片
选择连接设备的串口
设置为异步通信、不使用硬件流控制。
可以发现另一侧的芯片引脚图上的引脚自动显示为 USART 的 TX、RX 引脚了:
在GPIO Settings中设置引脚
修改为快速输出和上拉电阻之后,modify 标志位会自动更新。
设置Parameter Settings
设置波特率115200,8位字长,无奇偶校验位,1个停止位,接受和发送均开启,过采样8位;
FIFO开了也没问题,开了效果更好。
时钟配置
当你选择了波特率之后,时钟会自动调整为符合波特率的分频系数:
NVIC Settings
在NVIC Settings中USART2 global interrupt勾选Enable:
在A->Z中找到NVIC,并按如下图配置
继续用到了中断,那么就一定要设置中断控制器 NVIC 。
Project Manage设置
创建代码
在创建的工程中找到main.c就会有串口初始化代码:
生成代码一览
初始化函数
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_8;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK)
{
Error_Handler();
}
在stm32h7xx_hal_msp.c中可以看到USART2的回调函数
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(huart->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/* Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART2;
PeriphClkInitStruct.Usart234578ClockSelection = RCC_USART234578CLKSOURCE_D2PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* Peripheral clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}
}
生成的代码真的不行,还是得继续优化优化。
HAL 串口 API
- 串口初始化(波特率/停止位/使能串口)
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
参数:串口句柄
typedef struct
{
USART_TypeDef *Instance; //执行寄存器基地址,HAL库已定义,若为串口1,取值为 USART1
UART_InitTypeDef Init; //设置串口各个参数,下面有结构体
UART_AdvFeatureInitTypeDef AdvancedInit;
uint8_t *pTxBuffPtr; //发送数据缓存指针
uint16_t TxXferSize; //发送数据量
__IO uint16_t TxXferCount; //剩余发送数据量
uint8_t *pRxBuffPtr; //接收数据缓存指针
uint16_t RxXferSize; //单次最大接收量
__IO uint16_t RxXferCount; //剩余要接收的数据量
uint16_t Mask; //
DMA_HandleTypeDef *hdmatx; //DMA发送句柄
DMA_HandleTypeDef *hdmarx; //DMA接收句柄
HAL_LockTypeDef Lock;
__IO HAL_UART_StateTypeDef gState;
__IO HAL_UART_StateTypeDef RxState;
__IO uint32_t ErrorCode;
}UART_HandleTypeDef;
typedef struct
{
uint32_t BaudRate; //波特率
uint32_t WordLength; //字长,一般是8位:8 位字长数据格式 UART_WORDLENGTH_8B
uint32_t StopBits; //停止位,一般是一位:UART_STOPBITS_1
uint32_t Parity; //奇偶校验
uint32_t Mode; //收/发模式设置,全双工:UART_MODE_TX_RX
uint32_t HwFlowCtl; //硬件流设置
uint32_t OverSampling; //过采样设置,一般过采样为 16 倍或 8 倍
uint32_t OneBitSampling; //一位采样
uint32_t Prescaler; //分频
uint32_t FIFOMode; //FIFO 模式
uint32_t TXFIFOThreshold; //发送 FIFO 阈值
uint32_t RXFIFOThreshold; //接受 FIFO 阈值
}UART_InitTypeDef
调用函数 HAL_UART_Init
对串口进行初始化的时候,需要先设置 Instance 和 Init 两个成员变量的值。
函数 HAL_UART_Init 使用的一般格式为:
//UART 初始化设置
UART1_Handler.Instance=USART1; //USART1
UART1_Handler.Init.BaudRate=bound; //波特率
UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为 8 位
UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位
UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler);
HAL_UART_Init 内部会调用串口使能函数使能相应串口,
所以调用了该函数之后我们就不需要重复使能串口了。当然,HAL 库也提供了具体的串口使能和关闭方法,具体使用方法如下:
__HAL_UART_ENABLE(handler); //使能句柄 handler 指定的串口
__HAL_UART_DISABLE(handler); //关闭句柄 handler 指定的串口
这里还需要提醒大家,串口作为一个重要外设,在调用的初始化函数 HAL_UART_Init 内部,会先调用 MSP 初始化回调函数进行 MCU 相关的初始化,函数为:
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
我们在程序中,只需要重写该函数即可。一般情况下,该函数内部用来编写 IO 口初始化,时钟使能以及 NVIC 配置。
- 使能串口和GPIO时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能 USART1 时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟
- GPIO 口初始化设置(速度,上下拉等)以及复用映射配置
HAL 库中 IO 口初始化参数设置和复用映射配置是在函数
HAL_GPIO_Init 中一次性完成。
这里要复用 PA9 和 PA10 为串口发送接收相关引脚,我们需要配置 IO 口为复用,同时复用映射到串口 1。
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10; //PA9/PA10
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed= GPIO_SPEED_FREQ_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9/PA10
- 开启串口相关中断,配置串口中断优先级
HAL 库中定义了一个使能串口中断的标识符 __HAL_UART_ENABLE_IT,可以把它当一个函数来使用,例如我要使能接收完成中断:
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); //开启接收完成中断
huart 是串口句柄,UART_HandleTypeDef 类型。
UART_IT_RXNE 是我们要开启的中断类型值,可选值在头文件 stm32h7xx_hal_uart.h 中有宏定义。
有开启中断就有关闭中断,操作方法为:
__HAL_UART_DISABLE_IT(huart,UART_IT_RXNE); //关闭接收完成中断
对于中断优先级配置,方法就非常简单,详细知识情参考 4.5 小节相关知识。参考方法为:
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能 USART1 中断通道
HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级 3,子优先级 3
- 中断服务函数
串口 1 中断服务函数为:
void USART1_IRQHandler(void) ;
当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写们相应的逻辑代码即可。HAL 库实际上对中断处理过程进行了完整的封装。
- 串口数据接收和发送
STM32H7 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包
含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也
是存在该寄存器内。HAL 库操作 USART_DR 寄存器发送数据的函数是:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
通过该函数向串口寄存器 USART_DR 写入一个数据。
HAL 库操作 USART_DR 寄存器读取串口接收到的数据的函数是:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
通过该函数可以读取串口接受到的数据。
汇总设计
//初始化 IO 串口 1 ; bound:波特率
void uart_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance=USART1; //USART1
UART1_Handler.Init.BaudRate=bound; //波特率
UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为 8 位
UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位
UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能 UART1
//该函数会开启接收中断:标志位 UART_IT_RXNE,同时设置接收缓冲以及接收缓冲接收最大数据量
HAL_UART_Receive_IT(&UART1_Handler,(u8 *)aRxBuffer,RXBUFFERSIZE);
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO 端口设置
GPIO_InitTypeDef GPIO_Initure;
if(huart->Instance==USART1)//如果是串口 1,进行串口 1 MSP 初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能 USART1 时钟
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9
GPIO_Initure.Pin=GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA10
#if EN_USART1_RX
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能 USART1 中断通道
HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级 3,子优先级 3
#endif
}
}
/* 通过判断宏定义标识符 EN_USART1_RX 的值来确定是否开启串口中断通道和设置串口1 中断优先级。
* 标识符 EN_USART1_RX 在头文件 usart.h 中有定义,默认情况下我们设置为1。
*/
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口 1 接收
//编写中断服务函数
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&UART1_Handler); //调用 HAL 库中断处理公用函数
...... //中断处理完成后的结束工作
}
对于中断服务函数中的 HAL_UART_IRQHandler() 在 HAL 库中已经实现了,如下:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->ISR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags;
…//此处省略部分代码
if (errorflags == RESET)
{
if(((isrflags & USART_ISR_RXNE) != RESET) &&
( ((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_RXFTIE) != RESET)) )
{
UART_Receive_IT(huart);
return;
}
} …//此处省略部分代码
}
HAL_UART_IRQHandler 内部通过判断中断类型是否为接收完成中断,确定是否调用 HAL 另外一个函数 UART_Receive_IT()。函数 UART_Receive_IT()的作用是把每次中断接收到的字符保存在串口句柄的缓存指针 pRxBuffPtr 中,同时每次接收一个字符,其计数器 RxXferCount 减 1,直到接收完成 RxXferSize 个字符之后 RxXferCount 设置为 0,同时调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。
串口接收中断的一般流程:
三、案例:ESP8266
static ESP_T gEsp = {0};
static UART_HandleTypeDef gUART2 = {0};
// 功能描述: ESP模块初始化
int32_t ESP_Init(void)
{
GPIO_InitTypeDef tGPIO;
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
//TX
tGPIO.Pin = GPIO_PIN_2;
tGPIO.Mode = GPIO_MODE_AF_PP;
tGPIO.Pull = GPIO_PULLUP;
tGPIO.Speed = GPIO_SPEED_FREQ_HIGH;
tGPIO.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &tGPIO);
//RX
tGPIO.Pin = GPIO_PIN_3;
tGPIO.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &tGPIO);
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_NVIC_SetPriority(USART2_IRQn, 3, 2);
gUART2.Instance = USART2;
gUART2.Init.BaudRate = 115200;
gUART2.Init.WordLength = UART_WORDLENGTH_8B;
gUART2.Init.StopBits = UART_STOPBITS_1;
gUART2.Init.Parity = UART_PARITY_NONE;
gUART2.Init.Mode = UART_MODE_TX_RX;
gUART2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
gUART2.Init.OverSampling = UART_OVERSAMPLING_16;
gUART2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
gUART2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
gUART2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&gUART2) != HAL_OK) {}
if(HAL_UARTEx_SetTxFifoThreshold(&gUART2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK) {}
if(HAL_UARTEx_SetRxFifoThreshold(&gUART2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK) {}
if(HAL_UARTEx_DisableFifoMode(&gUART2) != HAL_OK) {}
HAL_UART_Receive_IT(&gUART2, &gEsp.bRxData, 1);
ESP_ExitTransmit();
while(ESP_ConnectAP(CFG_SSID, CFG_PASSWORD) < 0);
while(ESP_ConnectServer(CFG_IP, CFG_PORT) < 0);
return 0;
}
/****************************************************************************************
* 功能描述: 中断接收处理
* 输入参数: void
* 输出参数: void
* 返 回 值: void
****************************************************************************************/
void USART2_IRQHandler(void)
{
uint32_t iCnt = 0;
HAL_UART_IRQHandler(&gUART2);
while(HAL_UART_GetState(&gUART2) != HAL_UART_STATE_READY)
{
iCnt++;
if(iCnt > 0x1FFFF) break;
}
iCnt=0;
while(HAL_UART_Receive_IT(&gUART2, &gEsp.bRxData, 1) != HAL_OK)
{
iCnt++;
if(iCnt > 0x1FFFF) break;
}
if(gEsp.iCnt >= sizeof(gEsp.aBuf))
{
gEsp.iCnt = 0;
}
gEsp.aBuf[gEsp.iCnt++] = gEsp.bRxData;
//Shell_Putc(gEsp.bRxData);
if(gEsp.iCnt > 6)
{
gEsp.iState = ESP_GetAckSta(gEsp.aBuf);
if((gEsp.iState != AT_ACK_UNKNOW) && (gEsp.iState != AT_ACK_BUSY))
{
gEsp.pCmd = ESP_GetAckAt(gEsp.aBuf);
ESP_Handle();
gEsp.iCnt = 0;
memset(gEsp.aBuf, 0, sizeof(gEsp.aBuf));
}
}
}
//中断接收处理
void USART2_IRQHandler(void)
{
uint32_t iCnt = 0;
HAL_UART_IRQHandler(&gUART2);
while(HAL_UART_GetState(&gUART2) != HAL_UART_STATE_READY)
{
iCnt++;
if(iCnt > 0x1FFFF) break;
}
iCnt=0;
while(HAL_UART_Receive_IT(&gUART2, &gEsp.bRxData, 1) != HAL_OK)
{
iCnt++;
if(iCnt > 0x1FFFF) break;
}
if(gEsp.iCnt >= sizeof(gEsp.aBuf))
{
gEsp.iCnt = 0;
}
gEsp.aBuf[gEsp.iCnt++] = gEsp.bRxData;
}
//发送 AT 指令
static int32_t ESP_SendCmd(const char *pCmd)
{
HAL_UART_Transmit(&gUART2, (const uint8_t *)pCmd, strlen(pCmd), 1000);
while(__HAL_UART_GET_FLAG(&gUART2, UART_FLAG_TC)!=SET); //等待串口回应
return 0;
}
#define AT_ACK_ERROR -1
#define AT_ACK_OK 0
#define AT_ACK_BUSY 1
#define AT_ACK_UNKNOW 2
//ESP8266对象结构体
typedef struct
{
uint8_t aIP[4]; //IP
uint8_t aMAC[6]; //MAC
uint8_t bRxData; //接收单字节数据
uint8_t *pCmd; //应答命令
int32_t iState; //应答状态
uint32_t iCnt; //接收数据计数
uint8_t aBuf[512]; //接收数据缓冲
int32_t iAck;
}ESP_T;
/****************************************************************************************
* 功能描述: 获取应答状态
* 输入参数: pBuf: 接收数据缓存
* 返 回 值: 应答命令状态
****************************************************************************************/
static int32_t ESP_GetAckSta(uint8_t *pBuf)
{
if(strstr((const char *)pBuf, "ERROR") != NULL)
{
return AT_ACK_ERROR;
}
else if( (strstr((const char *)pBuf, "OK") != NULL) ||
(strstr((const char *)pBuf, ">") != NULL))
{
return AT_ACK_OK;
}
else if((strstr((const char *)pBuf, "WIFI ") != NULL) ||
strstr((const char *)pBuf, "busy") != NULL)
{
return AT_ACK_BUSY;
}
else
{
return AT_ACK_UNKNOW;
}
}
/****************************************************************************************
* 功能描述: 等待应答
* 输入参数: pAt: AT指令指针
timeout: 超时时间, 单位秒
* 返 回 值: 0.成功 -1.失败
****************************************************************************************/
static int32_t ESP_WaitAck(uint8_t *pCmd, uint32_t timeout)
{
int8_t i=0;
gEsp.pCmd = AT_UNKNOW;
gEsp.iState = AT_ACK_UNKNOW;
do
{
for(i=0; i<100; i++)
{
HAL_Delay(10);
if(gEsp.iState == AT_ACK_OK || gEsp.iState == AT_ACK_ERROR)
{
return 0;
}
}
if(gEsp.iState == AT_ACK_BUSY)
{
continue;
}
}while(timeout--);
return -1;
}
//举例:获取GMR信息
int32_t ESP_GetGMR(uint8_t *pBuf)
{
ESP_SendCmd(AT_GMR);
if(ESP_WaitAck(AT_GMR, 3) < 0)
{
return -1;
}
return 0;
}