STM32F103基于标准库开发串口中断接收数据环形队列例程

2023-05-16

STM32F103基于标准库开发串口中断接收数据环形队列例程


✨本示例源码来源于野火-STM32库开发实战指南,是一个值得学习借鉴的资源。

  • 📑一个完整的串口数据包通讯协议一般包含:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾。在一般开发过程当中,如果仅仅是作为调试信息使用,那么串口的使用就没有必要代码这么健壮。毕竟功能越完善,所需要提供的代码越复杂,处理的逻辑增加,以及资源的占用。

🔖有关环形列队的优点可以自行去搜索科普,这里不做介绍了。

🛠环形列队实现

🔖接收数据环形列队实现依托开启串口中断接收以及空闲中断。

  • rx_data_queue.h
#ifndef __ESP_DATA_QUEUE_H_
#define __ESP_DATA_QUEUE_H_

#include "stm32f10x.h"

#include <string.h>
#include <stdio.h>

//缓冲队列的个数需要为2的幂
#define QUEUE_NODE_NUM        (2)            //缓冲队列的大小(有多少个缓冲区)
#define QUEUE_NODE_DATA_LEN   (2*1024 )       //单个接收缓冲区大小

//队列的主体数据类型接口
#define QUEUE_DATA_TYPE  				ESP_USART_FRAME
//队列的调试输出接口
#define DATA_QUEUE_LOG  				QUEUE_DEBUG
#define DATA_QUEUE_LOG_ARRAY 	QUEUE_DEBUG_ARRAY


//数据主体
typedef struct 
{
	char  *head; 	//缓冲区头指针	
	uint16_t len; //接收到的数据长度

}ESP_USART_FRAME;


//队列结构
typedef struct {
	int         size;  /* 缓冲区大小          */
	int         read; /* 读指针              */
	int         write;   /* 写指针  */
	int read_using;	/*正在读取的缓冲区指针*/
	int write_using;		/*正在写入的缓冲区指针*/
	QUEUE_DATA_TYPE    *elems[QUEUE_NODE_NUM];  /* 缓冲区地址                   */
} QueueBuffer;


extern QueueBuffer rx_queue;


/*信息输出*/
#define QUEUE_DEBUG_ON         1
#define QUEUE_DEBUG_ARRAY_ON		0

#define QUEUE_INFO(fmt,arg...)           printf("<<-QUEUE-INFO->> "fmt"\n",##arg)
#define QUEUE_ERROR(fmt,arg...)          printf("<<-QUEUE-ERROR->> "fmt"\n",##arg)
#define QUEUE_DEBUG(fmt,arg...)          do{\
                                          if(QUEUE_DEBUG_ON)\
                                          printf("[%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)

#define QUEUE_DEBUG_ARRAY(array, num)    do{\
																									 int32_t i;\
																									 uint8_t* a = array;\
																									 if(QUEUE_DEBUG_ARRAY_ON)\
																									 {\
																											printf("\n<<-QUEUE-DEBUG-ARRAY->>\n");\
																											for (i = 0; i < (num); i++)\
																											{\
																													printf("%02x   ", (a)[i]);\
																													if ((i + 1 ) %10 == 0)\
																													{\
																															printf("\n");\
																													}\
																											}\
																											printf("\n");\
																									}\
																								 }while(0)	

//输出队列的状态信息
#define cbPrint(cb)		    DATA_QUEUE_LOG("size=0x%x, read=%d, write=%d\n", cb.size, cb.read, cb.write);\
	  DATA_QUEUE_LOG("size=0x%x, read_using=%d, write_using=%d\n", cb.size, cb.read_using, cb.write_using);


QUEUE_DATA_TYPE* cbWrite(QueueBuffer *cb);
QUEUE_DATA_TYPE* cbRead(QueueBuffer *cb);
void cbReadFinish(QueueBuffer *cb);
void cbWriteFinish(QueueBuffer *cb);
//void cbPrint(QueueBuffer *cb);
QUEUE_DATA_TYPE* cbWriteUsing(QueueBuffer *cb) ;
int cbIsFull(QueueBuffer *cb) ; 
int cbIsEmpty(QueueBuffer *cb) ;

void rx_queue_init(void);
void pull_data_from_queue(void);
void push_data_to_queue(char *src_dat,uint16_t src_len);


#endif




  • rx_data_queue.c
/**
  ******************************************************************************
  * @file    rx_data_queue.c
  * @author  fire
  * @version V1.0
  * @date    2015-01-xx
  * @brief   环形缓冲区,适用于接收外部数据时用作缓冲
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 IOT STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */ 

#include "./usart/rx_data_queue.h"



//实例化节点数据类型
QUEUE_DATA_TYPE  node_data[QUEUE_NODE_NUM]; 
//实例化队列类型
QueueBuffer rx_queue;

//队列缓冲区的内存池
__align(4) char node_buff[QUEUE_NODE_NUM][QUEUE_NODE_DATA_LEN] ;



/*环形缓冲队列*/

/**
  * @brief  初始化缓冲队列
  * @param  cb:缓冲队列结构体
  * @param  size: 缓冲队列的元素个数
  * @note 	初始化时还需要给cb->elems指针赋值
  */
void cbInit(QueueBuffer *cb, int size) 
{
    cb->size  = size;	/* maximum number of elements           */
    cb->read = 0; 		/* index of oldest element              */
    cb->write   = 0; 	 	/* index at which to write new element  */
//    cb->elems = (uint8_t *)calloc(cb->size, sizeof(uint8_t));  //elems 要额外初始化
}
 
//(此函数改成了宏,在头文件)
/**
  * @brief  输出缓冲队列当前的状态信息
  * @param  cb:缓冲队列结构体
  */
//void cbPrint(QueueBuffer *cb) 
//{
//    DATA_QUEUE_LOG("size=0x%x, read=%d, write=%d\n", cb->size, cb->read, cb->write);
//	  DATA_QUEUE_LOG("size=0x%x, read_using=%d, write_using=%d\n", cb->size, cb->read_using, cb->write_using);
//}
 
/**
  * @brief  判断缓冲队列是(1)否(0)已满
  * @param  cb:缓冲队列结构体
  */
int cbIsFull(QueueBuffer *cb) 
{
    return cb->write == (cb->read ^ cb->size); /* This inverts the most significant bit of read before comparison */ 
}
 
/**
  * @brief  判断缓冲队列是(1)否(0)全空
  * @param  cb:缓冲队列结构体
  */		
int cbIsEmpty(QueueBuffer *cb) 
{
    return cb->write == cb->read; 
}

/**
  * @brief  对缓冲队列的指针加1
  * @param  cb:缓冲队列结构体
  * @param  p:要加1的指针
  * @return  返回加1的结果
  */	
int cbIncr(QueueBuffer *cb, int p) 
{
    return (p + 1)&(2*cb->size-1); /* read and write pointers incrementation is done modulo 2*size */
}
 
/**
  * @brief  获取可写入的缓冲区指针
  * @param  cb:缓冲队列结构体
  * @return  可进行写入的缓冲区指针
  * @note  得到指针后可进入写入操作,但写指针不会立即加1,
           写完数据时,应调用cbWriteFinish对写指针加1
  */
QUEUE_DATA_TYPE* cbWrite(QueueBuffer *cb) 
{
    if (cbIsFull(cb)) /* full, overwrite moves read pointer */
    {
			return NULL;
		}		
		else
		{
			//当wriet和write_using相等时,表示上一个缓冲区已写入完毕,需要对写指针加1
			if(cb->write == cb->write_using)
			{
				cb->write_using = cbIncr(cb, cb->write); //未满,则增加1
			}
		}
		
	return  cb->elems[cb->write_using&(cb->size-1)];
}



/**
  * @brief 数据写入完毕,更新写指针到缓冲结构体
  * @param  cb:缓冲队列结构体
  */
void cbWriteFinish(QueueBuffer *cb)
{
    cb->write = cb->write_using;
}
 
/**
  * @brief  获取可读取的缓冲区指针
  * @param  cb:缓冲队列结构体
  * @return  可进行读取的缓冲区指针
  * @note  得到指针后可进入读取操作,但读指针不会立即加1,
					 读取完数据时,应调用cbReadFinish对读指针加1
  */
QUEUE_DATA_TYPE* cbRead(QueueBuffer *cb) 
{
		if(cbIsEmpty(cb))
			return NULL;
		
	//当read和read_using相等时,表示上一个缓冲区已读取完毕(即已调用cbReadFinish),
	//需要对写指针加1
	if(cb->read == cb->read_using)	
		cb->read_using = cbIncr(cb, cb->read);
	
	return cb->elems[cb->read_using&(cb->size-1)];
}


/**
  * @brief 数据读取完毕,更新读指针到缓冲结构体
  * @param  cb:缓冲队列结构体
  */
void cbReadFinish(QueueBuffer *cb) 
{	
		//重置当前读完的数据节点的长度
		cb->elems[cb->read_using&(cb->size-1)]->len = 0;
	
    cb->read = cb->read_using;
}



//队列的指针指向的缓冲区全部销毁
void camera_queue_free(void)
{
    uint32_t i = 0;

    for(i = 0; i < QUEUE_NODE_NUM; i ++)
    {
        if(node_data[i].head != NULL)
        {
					//若是动态申请的空间才要free
//            free(node_data[i].head);
            node_data[i].head = NULL;
        }
    }

    return;
}


/**
  * @brief  缓冲队列初始化,分配内存,使用缓冲队列时,
  * @param  无
  * @retval 无
  */
void rx_queue_init(void)
{
  uint32_t i = 0;

  memset(node_data, 0, sizeof(node_data));
		 
	/*初始化缓冲队列*/
	cbInit(&rx_queue,QUEUE_NODE_NUM);

    for(i = 0; i < QUEUE_NODE_NUM; i ++)
    {
        node_data[i].head = node_buff[i];
        
        /*初始化队列缓冲指针,指向实际的内存*/
        rx_queue.elems[i] = &node_data[i];
        
        DATA_QUEUE_LOG("node_data[i].head=0x%x,\r\nrx_queue.elems[i] =0x%x", (uint32_t)node_data[i].head,(uint32_t)rx_queue.elems[i]->head);

        memset(node_data[i].head, 0, QUEUE_NODE_DATA_LEN);
    }
		
//	cbPrint(rx_queue);	
}



/**
  * @brief  往队列中写入数据的样例
  */
void push_data_to_queue(char *src_dat,uint16_t src_len)
{
	QUEUE_DATA_TYPE *data_p;
	uint8_t i;
	
	for(i=0;i<src_len;i++)
	{
		/*获取写缓冲区指针,准备写入新数据*/
		data_p = cbWrite(&rx_queue);
		
		if (data_p != NULL)	//若缓冲队列未满,开始传输
		{		
			//往缓冲区写入数据,如使用串口接收、dma写入等方式
			*(data_p->head + i) = src_dat[i];
				data_p->len++;
			printf("\r\ndata_p->len =%d",data_p->len);
		}else return;	
		
		cbPrint(rx_queue);	
	}	
	
	/*写入缓冲区完毕*/
	cbWriteFinish(&rx_queue);
	
	cbPrint(rx_queue);	

}


/**
  * @brief  从队列中取数据的样例
  */
void pull_data_from_queue(void)
{
	QUEUE_DATA_TYPE *rx_data;	
		
	/*从缓冲区读取数据,进行处理,*/
	rx_data = cbRead(&rx_queue); 

	if(rx_data != NULL)//缓冲队列非空
	{		
		//加上字符串结束符,方便直接输出字符串
		*(rx_data->head+rx_data->len) = '\0';
		
		QUEUE_DEBUG("接收到的数据:%s",rx_data->head);
		QUEUE_DEBUG_ARRAY((uint8_t*)rx_data->head,rx_data->len);

		//使用完数据必须调用cbReadFinish更新读指针
		cbReadFinish(&rx_queue);
	}
}



  • 串口配置函数
 /**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = 
	USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);
	
	// 串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
	USART_ITConfig ( DEBUG_USARTx, USART_IT_IDLE, ENABLE ); //使能串口总线空闲中断 	

	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}
  • printf重定向代码
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

  • main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "./usart/rx_data_queue.h"

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{	
  /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  USART_Config();
		
	rx_queue_init();
	
	/* 发送一个字符串 */
	Usart_SendString( DEBUG_USARTx,"这是一个串口中断接收回显实验\n");
	printf("STM32F103VCT6\r\n");

  while(1)
	{	
		//获取数据并输出
		//实际应用中可参考pull data的方式获取数据进行处理
		pull_data_from_queue();
	}	
}
  • 📜串口打印信息
    在这里插入图片描述

📚程序源码

⚡基于Keil5,ARMcompiler:5.06

链接:https://pan.baidu.com/s/1zDyGGNJTdWg6jI9RSsqvxw 
提取码:wdr0
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32F103基于标准库开发串口中断接收数据环形队列例程 的相关文章

  • 七日杀服务器怎么修改天数,七日杀如何调整天数 | 手游网游页游攻略大全

    发布时间 xff1a 2016 01 21 有这种报错的是硬件加速的问题 调整硬件加速的方法 右键桌面属性 点设置 在设置界面中点高级 然后弹出一个界面 点击疑问解答 然后把硬件加速调到最大 如果还是不能解决可能是软件本身有问题 建议重新安
  • 如何给服务器传输文件,给服务器传输文件

    给服务器传输文件 内容精选 换一换 公有云通常指第三方供应商为用户提供的能够通过Internet使用的云端基础设施和服务 xff0c 其核心属性是共享资源服务 华为云是公有云品牌 xff0c 在SAP系统迁移的过程中 xff0c 您可以单独
  • 服务器架设文件服务器,服务器架设文件服务器

    服务器架设文件服务器 内容精选 换一换 安装完操作系统后的临时云服务器需要进行相关配置 xff0c 并安装原生的XEN和KVM驱动 xff0c 才能保证后续创建的云服务器正常使用 该任务指导用户完成Linux云服务器的相关配置与驱动安装 x
  • 基于YOLOv3的视频中无人机的目标检测(包括CUDA等环境配置)

    在SLAM毫无建树的我又投入了深度学习的大坑 xff0c 由于项目需求需要做无人机的目标检测 xff0c 这里把搭建系统中的一些问题记录下来 xff0c 方便之后复现 系统搭建 采用YOLOv3来进行无人机的检测 xff0c yolo的安装
  • 【CAN】CAN基础概念2

    摘自VECTOR官网E Learning的资料 9 帧类型 数据帧 为传输用户数据 xff0c ISO 11898 1定义了数据帧 数据帧可以传输的最大有效负载为八个字节 xff0c 即数据场 除了数据场 xff0c 数据帧还包括执行CAN
  • 如何设计一个锂电池充电电路(TP4056)

    这个是个单节18650锂电池的充电模块 xff0c 这个是个18650的锂电池 xff0c 18指的是它的直径是18mm xff0c 65指的是它的高度为65mm 这个18650电池的标称电压是3 7V xff0c 电池充满时电压为4 2V
  • 黑马程序员C++系统班学习笔记(一)——Linux操作系统一

    黑马程序员C 43 43 系统班链接 xff1a https www bilibili com video av37403127 from 61 search amp seid 61 5509409937472093685 本笔记旨在记录学
  • ubunbtu下基于c++实现MQTT客户端通信

    文章目录 一 MQTT简介1 1 MQTT 服务器 xff08 Broker xff09 是发布 订阅架构的核心1 2 MQTT 网络协议1 3 服务质量1 4 MQTT 数据包结构1 4 1 MQTT固定头1 4 2 MQTT可变头 Va
  • python 提取页面验证码

    以下代码都使用python 43 selenium 实现 xff0c 请先搭建好开发环境 1 页面的验证码若为静态验证码可以使用截屏的方式获取代码如下 driver save screenshot 39 C crawlerScript pi
  • 基于单片机的GPS开发 (four) GPS基础知识

    一 GPS硬件模块 xff1a VCC xff1a 兼容3 3V和5V GND xff1a 地 TXD xff1a 接51单片机的RXD P3 0 RXD xff1a 可接单片机的TXD P3 1 或者直接悬空 PPS xff1a 时钟脉冲
  • 记录:C++打印堆栈信息并优化打印结果

    1 介绍打印堆栈信息函数 头文件 xff1a span class token macro property span class token directive hash span span class token directive k
  • Ubuntu “无法定位软件包”

    Ubuntu 无法定位软件包 问题描述 xff1a 在使用Ubuntu 18 0 4 安装ceres库时安装依赖项时报错 xff0c 如图所示 xff1a 原因分析 xff1a libcxsparse3 1 2软件包是Ubuntu 14 0
  • NVIDIA Jetson TX2:TX2平台介绍

    一 xff1a TX2平台概述 TX2是快速高效的嵌入式AI计算设备 低功耗 xff08 标准 xff09 模式 xff08 7 5w xff09 xff1b 高功耗模式 xff08 15w xff09 xff0c 性能是TX1的两倍 具有
  • VSCode安装配置C++开发环境

    真相需要去探寻 之前已经在VSCode上配置了Golang的开发环境 xff0c 使用起来还是十分舒服的 xff0c 特别是设置了镜像下载地址和掌握了go mod的使用 xff0c 小巧轻便 xff0c 舒爽流畅 最近要用到C 43 43
  • 图像处理的基本操作(灰度化,二值化)

    基本的概念 xff1a 图像的深度 xff1a 图像中像素点占得bit位数 xff0c 就是图像的深度 xff0c 并不是每个像素点分配了多少位内存空间 xff0c 就一定能够要用完 xff0c 深度仅仅标识用于真真能表示颜色的位数 xff
  • Ubuntu如何切换Python版本

    这几天一直在搞小米官方提供的ESP32 WiFi SDK xff0c 过程中遇到了很多坑 xff0c 其中包括Python版本兼容的问题 xff0c 我的Ubuntu 上安装的Python版本是Python3 xff0c 而脚本的使用的是p
  • C++面试宝典:__FILE__,__func__,__LINE__

    C语言中 xff0c FILE xff0c func xff0c LINE 常用于logout xff0c debug调试 注意 xff1a 其使用不需要定义 xff0c FILE 指示当前文件名 xff0c func 指示当前函数名 xf
  • C++面试宝典:头文件引用的顺序

    头文件引用的顺序 当我们有多个头文件的时候 xff0c 特定情况下要注意引用的顺序 如果要在文件a h中声明一个在文件b h中定义的变量 xff0c 而不引用b h 那么要在a cpp文件中引用b h文件 xff0c 并且要先引用b h x
  • c++部署yolov5模型

    C 43 43 部署yolov5模型 前言一 准备模型二 Fastdeploy准备三 调用总结 前言 不可否认 xff0c yolov5在目标检测方面大杀四方 xff0c 在 SOTA 榜上留下过万众瞩目的成绩 xff0c 但是官网代码给的
  • 【信息技术】【2004】基于计算机视觉的无人机自主避障系统中的目标跟踪研究

    本文为瑞典皇家理工学院 xff08 作者 xff1a Johan Driessen xff09 的硕士论文 xff0c 共68页 这篇硕士论文的目的是研究利用商用货架 COTS 硬件和免费 公开可用的计算机视觉库开发一个足够有效的实时自主避

随机推荐