子页面开头
超子物联网 HAL库学习 汇总入口:
写作不易,如果您觉得写的不错,欢迎给博主来一波点赞、收藏~让博主更有动力吧!
一、多指针定位+循环收发缓冲区方案设计
1. 介绍
轮询阻塞的方式效率不高,一般会使用中断或者DMA方式收发数据
这时就需要定义一个缓冲区,这里暂定为2048字节。
- 缓冲区为一个、一维数组,循环使用,要注意缓冲区不要出现回卷覆盖,接收的数据要及时处理。
2. 大致思路
标记2048缓冲区每次数据的起始和结束位置
- 创建一个标记结构体,成员为指针pS(Start)和pE(Een)。他们用来在2048的缓冲区中,指向每次接收的数据的开头和起始位置。
- 使用我们刚才创建的'标记结构体',创建一个有10个成员的数组SN
- 创建一个IN和OUT指针,初始化时指向SN数组的起始位置(0号成员)
- 创建一个END指针,指向SN数组的结束位置(9号成员)
巧妙地判断接收到数据,并循环利用标记
- 当第一次接收到数据之后,使用0号成员的pS、pE指针定位数组的起始和结束位置, 同时IN++,指向数组SN的1号成员
- 此时,可以在while循环中判断,当OUT指针与IN指针不指向同一个位置了,那么就代表已经缓冲区收到数据了。在处理完数据之后,使得OUT++,指向第1号成员~
- 当第pS跳到END指向的位置时,应使得PS下次跳的位置为数组SN的起始位置:数据回滚,防止越界
防止2048缓冲区空余位置不够
- 约定每次接收数据的MAX值,防止空余位置不够。
- 所以在每次接收之后,都需要判断空余位置,若小于MAX值,则直接回卷,防止越界
利用空闲中断,完成对数据的处理
- 定义了单次接收的最大值MAX,若MAX=256,那么别人一次发送的值最多为255字节,因为当一次次发送256时,会同时触发完成中断和空闲中断,这是不允许的。
- 我们只利用空闲中断对数据进行处理哦~
二、[实践]HAL库:空闲中断方式实现串口1+不定长数据接收+循环收发缓冲区+收发数据
1. 不定长接收数据的实现思路及相关函数
在使用简单串口的串口收发时,无法知道对方给我发送的确切的数据量。我就没法确切的定义我们需要接收多少个字节的字节。只能接收到固定长度的字节后统一处理。
要实现不定长接收数据,我们通常是利用空闲中断。
当一次连续的接收完成后,会出现空闲帧,然后进入空闲中断中。
我们就可以利用空闲中断,来判断当前为一次数据的接收结束。
然后可以利用RxferCount,来获取本次接受了多少个字节。
在空闲中断回调中,我们可以对数据进行处理或判断
注意:
-
定义了单次接收的最大值MAX,若MAX=256,那么别人一次发送的值最多为255字节,因为当一次次发送256时,会同时触发完成中断和空闲中断,这是不允许的。
-
一定要想明白,位置控制数组rxLocation 和 rxInPtr和 rxOutPtr的关系,可以看图理解
- 我们只利用空闲中断对数据进行处理哦~
相关函数
-
空闲中断打开函数:
__HAL_UART_ENABLE_IT(&uart1.uart,UART_IT_IDLE);
-
在每次进入中断后,判断是否为空闲中断:
if(__HAL_UART_GET_FLAG(&uart1.uart, UART_FLAG_IDLE))
-
清除空闲标志位
__HAL_UART_CLEAR_FLAG(&uart1.uart, UART_FLAG_IDLE);
-
终止当前的接收(会把RxferCount清零)
HAL_UART_AbortReceive_IT(&uart1.uart);
-
终止接收回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
-
发送完成回调函数
HAL_UART_TxCpltCallback
2. 程序设计
STM32C8T6单片机一共有20K的RAM
我们用到2K+2K来作为缓冲区,进行数据的收 然后转发
2.1 设计介绍:
-
定义缓冲区宏
/* 缓冲区宏定义 */ #define U1_RX_SIZE 2048 #define U1_TX_SIZE 2048 #define U1_RX_MAX 256 //这里需要注意,我们是利用空闲中断对数据进行处理,不能单次发送256字节,否则进入完成中断
-
定义接收、发送缓冲区、单次最大接收量(2K 2048字节大小)
/* 缓冲区 */ uint8_t U1_Rx_Buff[U1_RX_SIZE]; uint8_t U1_Tx_Buff[U1_TX_SIZE];
-
定义结构体,成员为 :Start End 指针
/* Location Ctrl Block */ /* (接收/发送)位置控制块 */ typedef struct{ uint8_t* start; uint8_t* end; }LCB;
-
定义结构体串口控制块,其中包含了所有的指针和总控结构体
/* Uart Ctrl Block */ /* 串口控制块 */ typedef struct{ uint32_t rxCount; //记录接收缓冲区中当前已有的数据量 uint32_t txCount; //记录发送缓冲区中当前已有的数据量 LCB rxLocation[10]; //记录接收缓冲区每次接收的位置 LCB txLocation[10]; //记录发送缓冲区每次接收的位置 LCB* rxInPtr; //指向下次接收缓冲区存放位置 LCB* rxOutPtr; //指向下次接收缓冲区读取位置 LCB* rxEndPtr; //指向接收缓冲区结束位置 LCB* txInPtr; //指向下次发送缓冲区存放位置 LCB* txOutPtr; //指向下次发送缓冲区读取位置 LCB* txEndPtr; //指向发送缓冲区结束位置 UART_HandleTypeDef uart; //串口总控结构体 uint8_t TxState; //发送忙碌标志位 }UCB;
-
其他
/* 初始化 */ void U1_Init(uint32_t bandrate); /* 初始化UCB控制块指针 */ void U1_PtrInit(void); /* 转移RxBuff数据到TxBuff */ void U1_DataRxToTx(uint8_t* data, uint32_t data_len); /* 总控结构体 */ extern UCB uart1; /* 缓冲区 */ extern uint8_t U1_Rx_Buff[U1_RX_SIZE]; extern uint8_t U1_Tx_Buff[U1_TX_SIZE]; /* 状态位 */ extern uint8_t rxState;
2.2 文件架构:
- Uart.h :定义了 宏、结构体、函数与变量声明
- Uart.c :主要针对串口 1 进行配置:空闲中断打开和处理、包括初始化参数、设置缓冲区指针....
- stm32fxx_It.c : 主要是
void USART1_IRQHandler(void)
的中断函数:该函数是串口 1 的中断服务函数。首先调用 HAL 库的中断处理函数,后续 检测到串口 1 进入空闲状态时,清除空闲中断标志位,计算接收字节数量并累加,然后终止接收,触发终止接收回调函数。终止接收回调函数在Uart.c中 - main.c :在主循环中,通过判断接收和发送缓冲区的指针状态,实现数据的接收和发送,并在指针到达末尾时进行回卷操作。当接收缓冲区有数据时,将其拷贝到发送缓冲区并移动输出指针;当发送缓冲区有数据且处于空闲状态时,发送数据并移动输出指针。
uart.h
#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"
#include "stdint.h"
#include "string.h"
/* 缓冲区宏定义 */
#define U1_RX_SIZE 2048
#define U1_TX_SIZE 2048
#define U1_RX_MAX 256 //这里需要注意,我们是利用空闲中断对数据进行处理,不能单次发送256字节,否则进入完成中断
/* Location Ctrl Block */
/* (接收/发送)位置控制块 */
typedef struct{
uint8_t* start;
uint8_t* end;
}LCB;
/* Uart Ctrl Block */
/* 串口控制块 */
typedef struct{
uint32_t rxCount; //记录接收缓冲区中当前已有的数据量
uint32_t txCount; //记录发送缓冲区中当前已有的数据量
LCB rxLocation[10]; //记录接收缓冲区每次接收的位置
LCB txLocation[10]; //记录发送缓冲区每次接收的位置
LCB* rxInPtr; //指向下次接收缓冲区存放位置
LCB* rxOutPtr; //指向下次接收缓冲区读取位置
LCB* rxEndPtr; //指向接收缓冲区结束位置
LCB* txInPtr; //指向下次发送缓冲区存放位置
LCB* txOutPtr; //指向下次发送缓冲区读取位置
LCB* txEndPtr; //指向发送缓冲区结束位置
UART_HandleTypeDef uart; //串口总控结构体
uint8_t TxState; //发送忙碌标志位
}UCB;
/* 初始化 */
void U1_Init(uint32_t bandrate);
/* 初始化UCB控制块指针 */
void U1_PtrInit(void);
/* 转移RxBuff数据到TxBuff */
void U1_DataRxToTx(uint8_t* data, uint32_t data_len);
/* 总控结构体 */
extern UCB uart1;
/* 缓冲区 */
extern uint8_t U1_Rx_Buff[U1_RX_SIZE];
extern uint8_t U1_Tx_Buff[U1_TX_SIZE];
/* 状态位 */
extern uint8_t rxState;
#endif
uart.c
#include "uart.h"
/* 创建串口总控结构体 */
UCB uart1;
/* 缓冲区 */
uint8_t U1_Rx_Buff[U1_RX_SIZE];
uint8_t U1_Tx_Buff[U1_TX_SIZE];
/* 初始化串口 */
void U1_Init(uint32_t bandrate){
uart1.uart.Instance = USART1; //使用那个串口
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);
/* 初始化UCB控制块指针 */
U1_PtrInit();
/* 打开空闲中断 */
__HAL_UART_ENABLE_IT(&uart1.uart,UART_IT_IDLE);
/* 开始接收数据 */
HAL_UART_Receive_IT(&uart1.uart, uart1.rxInPtr->start, U1_RX_MAX); //接收位置为当前LCB位置控制块的In指针所指向的缓冲区的位置
}
/* 初始化U1_UCB控制块指针 */
void U1_PtrInit(void){
uart1.rxCount = 0;
uart1.rxInPtr = &uart1.rxLocation[0];
uart1.rxOutPtr = &uart1.rxLocation[0];
uart1.rxEndPtr = &uart1.rxLocation[9];
uart1.rxInPtr->start = &U1_Rx_Buff[0]; //让当前接收位置控制块的start,指向下一次接收到的数据将要存放的位置
uart1.txCount = 0;
uart1.txInPtr = &uart1.txLocation[0];
uart1.txOutPtr = &uart1.txLocation[0];
uart1.txEndPtr = &uart1.txLocation[9];
uart1.txInPtr->start = &U1_Tx_Buff[0]; //让当前发送位置控制块的start,指向下一次需要发送的数据的存放位置
}
/* 转移U1_Rx_Buff数据到 U1_Tx_Buff */
void U1_DataRxToTx(uint8_t* data, uint32_t data_len){
/* 判断剩余空间是否足够,要不要回卷 */
if((U1_TX_SIZE - uart1.txCount) > data_len){
/* 如果够 */
uart1.txInPtr->start = &U1_Tx_Buff[uart1.txCount];
}
else{/* 如果剩余空间不够 */
uart1.txCount = 0;
uart1.txInPtr->start = &U1_Tx_Buff[0];
}
/* 复制data到U1_Tx_Buff缓冲区 */
memcpy(uart1.txInPtr->start, data, data_len);
/* 累加txCount */
uart1.txCount += data_len;
/* 标记这次的发送数据的结束位置 */
uart1.txInPtr->end = &U1_Tx_Buff[uart1.txCount - 1];
/* 移动txIn */
uart1.txInPtr++;
/* 判断txIn指针是否需要回卷 */
if(uart1.txInPtr == uart1.txEndPtr){
uart1.txInPtr = &uart1.txLocation[0];
}
}
/* UART硬件初始化回调 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart){
GPIO_InitTypeDef GPIO_InitType;
if(huart->Instance == USART1){ //判断那个串口在进行初始化
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitType.Pin = GPIO_PIN_9;
GPIO_InitType.Mode = GPIO_MODE_AF_PP;
GPIO_InitType.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA,&GPIO_InitType);
GPIO_InitType.Pin = GPIO_PIN_10;
GPIO_InitType.Mode = GPIO_MODE_AF_INPUT;
GPIO_InitType.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA,&GPIO_InitType);
//打开了串口1的总中断
HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}else if(huart->Instance == USART2){
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART2_CLK_ENABLE();
GPIO_InitType.Pin = GPIO_PIN_2;
GPIO_InitType.Mode = GPIO_MODE_AF_PP;
GPIO_InitType.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA,&GPIO_InitType);
GPIO_InitType.Pin = GPIO_PIN_3;
GPIO_InitType.Mode = GPIO_MODE_AF_INPUT;
GPIO_InitType.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA,&GPIO_InitType);
}else if(huart->Instance == USART3){
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_USART3_CLK_ENABLE();
GPIO_InitType.Pin = GPIO_PIN_10;
GPIO_InitType.Mode = GPIO_MODE_AF_PP;
GPIO_InitType.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOB,&GPIO_InitType);
GPIO_InitType.Pin = GPIO_PIN_11;
GPIO_InitType.Mode = GPIO_MODE_AF_INPUT;
GPIO_InitType.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB,&GPIO_InitType);
}
}
/* 强声明的接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
}else if(huart->Instance == USART2){
}else if(huart->Instance == USART3){
}
}
/* 强声明的错误回调函数 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
}else if(huart->Instance == USART2){
}else if(huart->Instance == USART3){
}
}
/* 强声明的发送完成回调函数 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
/* 发送完成,标志位清零 */
uart1.TxState = 0;
}else if(huart->Instance == USART2){
}else if(huart->Instance == USART3){
}
}
/* 强声明的接收终止回调函数 */
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
/* 标记结束位置 */
uart1.rxInPtr->end = &U1_Rx_Buff[uart1.rxCount - 1];
/* 挪动rxIn指针 */
uart1.rxInPtr++;
/* 判断rxIn指针是否需要回卷 */
if(uart1.rxInPtr == uart1.rxEndPtr){
uart1.rxInPtr = &uart1.rxLocation[0];
}
/* 判断接收缓冲区是否需要回卷 */
if((U1_RX_SIZE - uart1.rxCount) < U1_RX_MAX){
uart1.rxCount = 0;
uart1.rxInPtr->start = &U1_Rx_Buff[0];
}else{
/* 剩余位置够 */
uart1.rxInPtr->start = &U1_Rx_Buff[uart1.rxCount];
}
/* 重新开启中断接收 */
HAL_UART_Receive_IT(&uart1.uart, uart1.rxInPtr->start, U1_RX_MAX);
}else if(huart->Instance == USART2){
}else if(huart->Instance == USART3){
}
}
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_13);
}
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
/*-------------------------------------------------*/
/*函数名:不可屏蔽中断处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void NMI_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:硬件出错后进入的中断处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void HardFault_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:软中断,SWI 指令调用的处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void SVC_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:可挂起的系统服务处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void PendSV_Handler(void)
{
}
/*-------------------------------------------------*/
/*函数名:SysTic系统嘀嗒定时器处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void SysTick_Handler(void)
{
HAL_IncTick();
}
/*-------------------------------------------------*/
/*函数名:串口1中断处理函数 */
/*参 数:无 */
/*返回值:无 */
/*-------------------------------------------------*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&uart1.uart);
/* 在每次进入中断后,判断是否为空闲中断 */
if(__HAL_UART_GET_FLAG(&uart1.uart, UART_FLAG_IDLE)){
/* 清除空闲标志位 */
//__HAL_UART_CLEAR_FLAG(&uart1.uart, UART_FLAG_IDLE);
__HAL_UART_CLEAR_IDLEFLAG(&uart1.uart);
/* 获取这次传输了多少字节 */
uart1.rxCount += (U1_RX_MAX - uart1.uart.RxXferCount);
/* 终止当前的接收(会把RxferCount清零) */
HAL_UART_AbortReceive_IT(&uart1.uart);
}
}
main.c
#include "stm32f1xx_hal.h"
#include "rcc.h"
#include "led.h"
#include "sw.h"
#include "uart.h"
int main(void){
HAL_Init();
RccClock_Init();
U1_Init(921600);
while(1){
/* 判断接收缓冲区是否有数据 */
if(uart1.rxInPtr != uart1.rxOutPtr){
/* 转移这次接收的一段数据到发送缓冲区 */
U1_DataRxToTx(uart1.rxOutPtr->start, (uart1.rxOutPtr->end - uart1.rxOutPtr->start + 1));
/* 移动rxOutPtr到下一次cpy的地址 */
uart1.rxOutPtr++;
/* 判断rxOutPtr是否需要回卷 */
if(uart1.rxOutPtr == uart1.rxEndPtr){
uart1.rxOutPtr = &uart1.rxLocation[0];
}
}
/* 判断发送缓冲区是否有数据 */
if((uart1.txInPtr != uart1.txOutPtr) && (uart1.TxState == 0) ){
uart1.TxState = 1;
/* 发送数据 */
HAL_UART_Transmit_IT(&uart1.uart, uart1. txOutPtr->start, (uart1.txOutPtr->end - uart1.txOutPtr->start + 1));
/* 移动txOutPtr到下一次cpy的地址 */
uart1.txOutPtr++;
/* 判断txOutPtr是否需要回卷 */
if(uart1.txOutPtr == uart1.txEndPtr){
uart1.txOutPtr = &uart1.txLocation[0];
}
}
}
}