超子物联网HAL库笔记:多指针定位+循环收发缓冲区方案设计

子页面开头

超子物联网 HAL库学习 汇总入口:

超子物联网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,来获取本次接受了多少个字节。

在空闲中断回调中,我们可以对数据进行处理或判断

注意:

  1. 定义了单次接收的最大值MAX,若MAX=256,那么别人一次发送的值最多为255字节,因为当一次次发送256时,会同时触发完成中断和空闲中断,这是不允许的。

  2. 一定要想明白,位置控制数组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];
            }
        }
        
    }
}
相关推荐
TT哇3 小时前
【Java】数组的定义与使用
java·开发语言·笔记
黑叶白树4 小时前
包和模块(上) python复习笔记
开发语言·笔记·python
我是水怪的哥4 小时前
一些有用的科研数据网站
经验分享·笔记
zhilanguifang5 小时前
ERC论文阅读(02)--SAC;-LSTM论文阅读笔记
论文阅读·笔记·lstm
尘佑不尘8 小时前
shodan5,参数使用,批量查找Mongodb未授权登录,jenkins批量挖掘
数据库·笔记·mongodb·web安全·jenkins·1024程序员节
Iqnus_1239 小时前
vue下载安装
前端·vue.js·笔记
CLCNboss9 小时前
Mac安装Ruby
开发语言·经验分享·笔记·macos·ruby
呵呵哒( ̄▽ ̄)"11 小时前
尚硅谷-react教程-求和案例-优化3-整合UI组件和容器组件-总结优化-笔记
前端·笔记·react.js
l1x1n012 小时前
【IT基础中英笔记】符号系统与数据类型 | CompTIA ITF+
笔记·学习