此文已重新修整,请跳转这里来查看
超子物联网 HAL库学习 汇总入口:
HAL库:不定长数据接收 + 循环收发缓冲区 + 空闲中断 收发数据
串口:多指针定位+收发循环使用缓冲区设计
STM32C8T6单片机一共有20K的RAM
设计介绍:
-
定义接收、发送缓冲区(2K 2048字节大小) 用于在接收时不频繁进入中断。
-
定义结构体,成员为 :Start End 指针 用于在一次串口接收完毕,程序进入空闲中断时。使用Start、End 标记这一段字节的起始和节数地址。 用于读取
-
定义结构体数组 可以存10段上述的一段字节的起始地址。[0:9]
-
定义结构体指针 In Out 初始化时,In、Out都指向结构体数组的第一个元素。当接收到一段字节后。In跳到结构体数组的第二个元素。准备一下次接收。 当Out和In的地址不同时。代表接收到数据,此时Out可通过第一个元素的Start 和 End 读出这段 字节,再跳到下一个元素,等待下次不相同时读出。
-
定义结构体指针 END END指针固定指向结构体数组的最后一个元素 [9] 当Out跳到最后一个成员,即Out 或 In = END时、 在写入、读取后回滚到结构体数组的第一个元素。
-
接收回卷: 约定最大发送值为300。当2048字节的接收缓冲区余量小于最大发送值300时,回卷。重新从起始地址开始缓存
-
发送回卷: 当发送字节数量 小于当前发送缓冲余量时,进行回卷。最大值为2048。
文件架构:
- Uart.h :定义了与串口通信相关的数据结构和函数声明,用于管理串口通信中的接收和发送缓冲区,以及串口的初始化操作。
- Uart.c :主要针对串口 1 进行配置为空闲中断 ,包括初始化参数、设置缓冲区指针、配置 IO 口和使能空闲中断等。 实现了数据收发、中断处理及缓冲区管理,确保串口 1 高效稳定地进行数据通信。
- stm32fxx_It.c : 主要是
void USART1_IRQHandler(void)
的中断函数:该函数是串口 1 的中断服务函数。首先调用 HAL 库的中断处理函数,后续 检测到串口 1 进入空闲状态时,清除空闲中断标志位,计算接收字节数量并累加,然后终止接收,触发终止回调函数。终止函数在Uart.c中 - main.c :在主循环中,通过判断接收和发送缓冲区的指针状态,实现数据的接收和发送,并在指针到达末尾时进行回卷操作。当接收缓冲区有数据时,将其拷贝到发送缓冲区并移动输出指针;当发送缓冲区有数据且处于空闲状态时,发送数据并移动输出指针。
程序部分大体思路
当接收到没有超过最大接收量256 - 1 时,会触发空闲中断。 (如果等于256 会同时触发接收完成中断和空闲中断。)
在接受到后, 会进入串口中断中,
在中断判断空闲中断标志位,记录空闲前接收了多少的字节
然后终止当前的接收(否则会继续等到完成接收)。
进入终止接收回调函数。
在函数中会对每次传入的 一段 字节 的起始位和结束位做标记。并且会根据指针的位置进行回卷等操作。
在主函数中。会不断循环检查是否接收到一段字节。即Rx_In_ArrPtr 结构体指针的位置是否与Rx_Out_ArrPtr 的地址相同,如果不同就代表已经记录了一个接收到的一段字节的起始位置。就可以把他移动到 发送缓冲区。
在主函数中调用这个函数, 在往发送缓冲区移动的时候仍然会对这段字节 的起始位和结束位做标记。并且会根据指针的位置进行回卷等操作。
在检查到发送缓冲区的In和Out不同后,有会去判断并发送这一段的 字节。
这只是一个大概的顺序, 具体的 内容 去看 文件吧。里边的注释挺详细的
Uart.h
#ifndef __UART_H
#define __UART_H
#include "string.h" //memry操作用的头文件
#define U1_RX_SIZE 2048 //接收、发送数组缓冲区长度 宏定义
#define U1_TX_SIZE 2048
#define U1_RX_MAX 256 //约定发送方单次发送字节 最大值 - 1。
//否则当接收满了,就会产生完成回调和空闲回调两个回调。
typedef struct
{
//用于标记接收到的一段字节的起始和结束位置
uint8_t* Start;
uint8_t* End;
}LCB; //location Ctrl Block 位置单元组
typedef struct
{
/*接收缓冲区配置*/
uint32_t Rx_Counter; //接收缓冲区 累计收到的字节
LCB Rx_LocationArr[10]; //接收缓冲区 字节段 位置单元组 数组
LCB *Rx_In_ArrPtr; //用于保存 一段字节的 位置
LCB *Rx_Out_ArrPtr; //用于根据位置 读出缓冲区中的值
LCB *Rx_End_ArrPtr; //用于定义位置单元组 数组的终点位置,防止指针越界
/*发送缓冲区配置*/
uint32_t Tx_Counter; //发送缓冲区 累计发送的字节。用于计算余量
LCB Tx_LocationArr[10]; //发送缓冲区 字节段 位置单元组 数组
LCB *Tx_In_ArrPtr; //用于保存 一段字节的 位置
LCB *Tx_Out_ArrPtr; //用于根据位置 读出缓冲区中的值
LCB *Tx_End_ArrPtr; //用于定义位置单元组 数组的终点位置,防止指针越界
/*串口处理结构体配置*/
UART_HandleTypeDef Uart; //方便一次配置完成。
/*发送忙状态*/
uint8_t Tx_State;
}UCB; //UART Ctrl Block 串口缓冲区单元组
//串口1 初始化
void Uart1_Init(uint32_t Bandrate);
//串口1 接收缓冲区 规则 指针位置 初始化
void Uart1_Ptr_Init(void);
//串口1 发送数据
void Uart1_Tx_Data(uint8_t* data, uint8_t len);
//UART2初始化
void Uart2_Init(uint32_t Bandrate);
//UART3初始化
void Uart3_Init(uint32_t Bandrate);
//外部声明的总控结构体
extern UCB Uart1;
extern UART_HandleTypeDef Uart2;
extern UART_HandleTypeDef Uart3;
#endif
Uart.c
#include "stm32f1xx_hal.h"
#include "Uart.h"
//串口1 配置结构体
UCB Uart1;
UART_HandleTypeDef Uart2;
UART_HandleTypeDef Uart3;
//定义接收和发送的缓冲区
uint8_t U1_Rx_Buff[U1_RX_SIZE];
uint8_t U1_Tx_Buff[U1_TX_SIZE];
UCB uarb1;
//初始化串口1
void Uart1_Init(uint32_t Bandrate)
{
Uart1.Uart.Instance = USART1;//选择串口1
Uart1.Uart.Init.BaudRate = Bandrate;//波特率
Uart1.Uart.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
Uart1.Uart.Init.StopBits = UART_STOPBITS_1;//停止位
Uart1.Uart.Init.Parity = UART_PARITY_NONE;//奇偶校验
Uart1.Uart.Init.Mode = UART_MODE_TX_RX;//收发模式
Uart1.Uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
HAL_UART_Init(&Uart1.Uart);
/*初始化串口1 缓冲区 规则 指针位置*/
Uart1_Ptr_Init();
/*使能空闲中断*/
__HAL_UART_ENABLE_IT(&Uart1.Uart,UART_IT_IDLE);
HAL_UART_Receive_IT(&Uart1.Uart, Uart1.Rx_In_ArrPtr->Start, U1_RX_MAX);
//开启接收中断。缓冲区位置为刚才记录的Start。接受的数量为U1_RX_MAX
}
//初始化串口1 缓冲区 规则 指针位置函数
void Uart1_Ptr_Init(void)
{
Uart1.Rx_In_ArrPtr = &Uart1.Rx_LocationArr[0]; //输入 位置数组对 指向 第一个元素
Uart1.Rx_Out_ArrPtr = &Uart1.Rx_LocationArr[0]; //输出 位置数组对 指向 第一个元素
Uart1.Rx_End_ArrPtr = &Uart1.Rx_LocationArr[9]; //结束 位置指向 最后一个元素
Uart1.Rx_Counter = 0; //用于累加收到的字节数量
Uart1.Rx_In_ArrPtr->Start = U1_Rx_Buff; //记录接收缓冲区的起始地址
Uart1.Tx_In_ArrPtr = &Uart1.Tx_LocationArr[0]; //输入 位置数组对 指向 第一个元素
Uart1.Tx_Out_ArrPtr = &Uart1.Tx_LocationArr[0]; //输出 位置数组对 指向 第一个元素
Uart1.Tx_End_ArrPtr = &Uart1.Tx_LocationArr[9]; //结束 位置指向 最后一个元素
Uart1.Tx_Counter = 0; //用于累加收到的字节数量
Uart1.Tx_In_ArrPtr->Start = U1_Tx_Buff; //记录接收缓冲区的起始地址
}
//串口1 发送数据
void Uart1_Tx_Data(uint8_t* data, uint8_t len)//参数为数据的起始位置,和发送多少
{
//判断这次要发送的数据量是不是 小于 剩余缓冲区的大小.是:还能放
if( len < (U1_TX_SIZE - Uart1.Tx_Counter) )
{
Uart1.Tx_In_ArrPtr->Start = &U1_Tx_Buff[Uart1.Tx_Counter]; //记录下一次 开始记录的位置
}
else //不够了。回卷
{
Uart1.Tx_Counter = 0; //重置计数
Uart1.Tx_In_ArrPtr->Start = U1_Tx_Buff; //标记起始地址为缓冲区起始位置
}
memcpy(Uart1.Tx_In_ArrPtr->Start, data, len); //拷贝源地址到标记的Start位置
Uart1.Tx_Counter += len; //累加,为下次拷贝做准备
Uart1.Tx_In_ArrPtr->End = &U1_Tx_Buff[Uart1.Tx_Counter - 1];//标记End
Uart1.Tx_In_ArrPtr++;//准备记录下一次的 起始位置
//判断是否指向最后一个元素
if(Uart1.Tx_In_ArrPtr == Uart1.Tx_End_ArrPtr)
{
Uart1.Tx_In_ArrPtr = &Uart1.Tx_LocationArr[0]; //回到0号位置
}
}
//初始化串口2
void Uart2_Init(uint32_t Bandrate)
{
Uart2.Instance = USART2;//选择串口2
Uart2.Init.BaudRate = Bandrate;//波特率
Uart2.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
Uart2.Init.StopBits = UART_STOPBITS_1;//停止位
Uart2.Init.Parity = UART_PARITY_NONE;//奇偶校验
Uart2.Init.Mode = UART_MODE_TX_RX;//收发模式
Uart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
HAL_UART_Init(&Uart2);
}
//初始化串口3
void Uart3_Init(uint32_t Bandrate)
{
Uart3.Instance = USART3;//选择串口3
Uart3.Init.BaudRate = Bandrate;//波特率
Uart3.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
Uart3.Init.StopBits = UART_STOPBITS_1;//停止位
Uart3.Init.Parity = UART_PARITY_NONE;//奇偶校验
Uart3.Init.Mode = UART_MODE_TX_RX;//收发模式
Uart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
HAL_UART_Init(&Uart3);
}
//定义 强声明 的IO口和时钟打开的 回调函数
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//判断传入的结构体中第一个成员 来判断是串口1 、 2、 3
if(huart->Instance == USART1)
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //打开GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //打开串口1 的时钟
GPIO_InitTypeDef GPIO_InitStructure;
//TX初始化 PA9
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
//RX初始化 PA10
GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
GPIO_InitStructure.Pin = GPIO_PIN_10;
GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn); //配置优先级和使能中断
}
else if(huart->Instance == USART2)//如果是串口2
{
__HAL_RCC_GPIOA_CLK_ENABLE();//打开GPIOA(PA2 PA3)时钟
__HAL_RCC_USART2_CLK_ENABLE();//打开串口2 的时钟
GPIO_InitTypeDef GPIO_InitStructure;
//TX初始化 PA2
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
GPIO_InitStructure.Pin = GPIO_PIN_2;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
//RX初始化 PA3
GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
GPIO_InitStructure.Pin = GPIO_PIN_3;
GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
}
else if(huart->Instance == USART3)//如果是串口3
{
__HAL_RCC_GPIOB_CLK_ENABLE();//打开GPIOB(PB10 PB11)时钟
__HAL_RCC_USART3_CLK_ENABLE();//打开串口3 的时钟
GPIO_InitTypeDef GPIO_InitStructure;
//TX初始化 PB10
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
GPIO_InitStructure.Pin = GPIO_PIN_10;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
//RX初始化 PB11
GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
GPIO_InitStructure.Pin = GPIO_PIN_11;
GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
}
}
//定义 强声明的 UART 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//判断
if(huart->Instance == USART1)
{
}
}
//定义 强声明的 UART中断错误回调函数
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
//判断串口
if(huart->Instance == USART1)
{
}
}
//定义 强声明的 UART发送完成回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
Uart1.Tx_State = 0; //退出忙状态
}
}
//强定义的 串口终止 回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
//判断串口
if(huart->Instance == USART1)
{
// 标记这次字节段的结束的地址
Uart1.Rx_In_ArrPtr->End = &U1_Rx_Buff[Uart1.Rx_Counter - 1]; //从0开始存,所以要减1
Uart1.Rx_In_ArrPtr++; //In指向下一个 位置对结构体 数组的下一个元素
//判断是否指向最后一个元素
if(Uart1.Rx_In_ArrPtr == Uart1.Rx_End_ArrPtr)
{
Uart1.Rx_In_ArrPtr = &Uart1.Rx_LocationArr[0]; //回到0号位置
}
//判断缓冲区余量是否小于U1_RX_MAX
if( (U1_RX_SIZE - Uart1.Rx_Counter) < U1_RX_MAX )
{
//如果小于就回滚
Uart1.Rx_Counter = 0; //计数清零
Uart1.Rx_In_ArrPtr->Start = U1_Rx_Buff; //开始指针指向缓冲区起始地址
}
else//余量还够
{
//从第一个字节段末尾的下一个地址开始存
Uart1.Rx_In_ArrPtr->Start = &U1_Rx_Buff[Uart1.Rx_Counter];
//重新打开中断接收
HAL_UART_Receive_IT(&Uart1.Uart, Uart1.Rx_In_ArrPtr->Start, U1_RX_MAX);
}
}
}
stm32fxx_It.c
/*-------------------------------------------------*/
/* */
/* 实现各种中断服务函数的源文件 */
/* */
/*-------------------------------------------------*/
#include "stm32f1xx_hal.h"
#include "stm32f1xx_it.h"
#include "Uart.h"
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
}
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&Uart1.Uart);
if( __HAL_UART_GET_FLAG(&Uart1.Uart,UART_FLAG_IDLE) )//如果进入空闲状态
{
__HAL_UART_CLEAR_IDLEFLAG(&Uart1.Uart); //清除空闲中断标志位
Uart1.Rx_Counter += (U1_RX_MAX - Uart1.Uart.RxXferCount); //提前读取Count,计算此次字节数量并累加
HAL_UART_AbortReceive_IT(&Uart1.Uart); //终止接收(会清除记录的接收量:Count。有终止回调函数)
}
}
/*-------------------------------------------------*/
/*函数名:不可屏蔽中断处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void NMI_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:硬件出错后进入的中断处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void HardFault_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:软中断,SWI 指令调用的处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void SVC_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:可挂起的系统服务处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void PendSV_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:SysTic系统嘀嗒定时器处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void SysTick_Handler(void)
{
HAL_IncTick();
}
main.c
#include "stm32f1xx_hal.h"
#include "rcc.h"
#include "led.h"
#include "key.h"
#include "Uart.h"
/*函 数 名:HAL库:中断方式。实现UART串口数据收发。 定长 20字节 */
int main (void)
{
HAL_Init();//初始化HAL库
RccClock_Init();//配置时钟树72M
LED_Init();
Uart1_Init(921600);//初始化串口1
while(1)
{
//如果接收缓冲区 In指针和Out指针不是一个地址。那么就说明接收缓冲区有数据了
if(Uart1.Rx_Out_ArrPtr != Uart1.Rx_In_ArrPtr)
{
//拷贝这次接收到的字节 到发送缓冲区 .注意是Out 的地址,不是in
Uart1_Tx_Data( Uart1.Rx_Out_ArrPtr->Start, (Uart1.Rx_Out_ArrPtr->End - Uart1.Rx_Out_ArrPtr->Start +1));
Uart1.Rx_Out_ArrPtr++; //向后挪
//如果等于END 则回卷
if( Uart1.Rx_Out_ArrPtr == Uart1.Rx_End_ArrPtr )
{
Uart1.Rx_Out_ArrPtr = &Uart1.Rx_LocationArr[0]; //记录下一次 开始记录的位置
}
}
//如果发送缓冲区 In指针和Out指针不是一个地址。那么就说明发送缓冲区有数据了。并且此时处于空闲状态
if( (Uart1.Tx_Out_ArrPtr != Uart1.Tx_In_ArrPtr) && (Uart1.Tx_State == 0) )//0为空闲。1 为忙
{
Uart1.Tx_State = 1; //进入忙状态
HAL_UART_Transmit_IT( &Uart1.Uart, Uart1.Tx_Out_ArrPtr->Start, (Uart1.Tx_Out_ArrPtr->End - Uart1.Tx_Out_ArrPtr->Start + 1) );
Uart1.Tx_Out_ArrPtr++; //向后挪
//如果等于END 则回卷
if( Uart1.Tx_Out_ArrPtr == Uart1.Tx_End_ArrPtr )
{
Uart1.Tx_Out_ArrPtr = &Uart1.Tx_LocationArr[0]; //记录下一次 开始记录的位置
}
}
}
}