cubemx stm32 串口终端的实现与使用 (可以由用户自定义各种终端指令任意) 驱动代码

20 篇文章 2 订阅
11 篇文章 2 订阅

简言

我之前学linux的时候,觉得linux的命令很酷,最近又有写代码的热情,于是手撸了一个串口终端。

初次使用

介绍特性

  1. 像终端一样使用

    • 输入niubi后,键入回车,输出自己写的回调函数中的内容。

    image-20221026143340286

  2. 支持tab键补全

    • 按下n后按下tab键后,到库中匹配关键词,匹配到niubi,显示到下一行。

    image-20221026143340286

    • 当有多个关键词可以匹配时,提示匹配关键词,并在新的一行显示刚刚输入的内容。

      在这里插入图片描述

  3. 支持Backspace键

    • 当输入有误时,可以按Backspace键,回退到上一格。

宏定义的定义与配置

yxy_open_cmd.h 中修改对应的宏:

  • 主要修改第一个串口句柄的宏,以及最后一个换行的宏,其他的根据需求来修改。

在这里插入图片描述

初始化

全局区(添加自定义终端指令变量和对应的回调函数)

// 全局区
User_USART_RX uClear;

/* 关键词clear回调函数 */
static void clear_callback(void)
{
	yxy_DEBUG("\x1b[2J\x1b[H");
}

main函数

// main
uart_RX_init();

rx_Pack_add(&uClear, (uint8_t *)"clear", str_len((uint8_t *)"clear"), (uint8_t *)"清屏", clear_callback);

使用

循环


uart_rx_scan();
HAL_Delay(1);

效果

在这里插入图片描述

主要代码

yxy_open_cmd.c

/* 头文件 */
#include "yxy_open_cmd.h"
#include "stdlib.h"
#include "stdarg.h"
#include "string.h"
#include "usart.h"
#include "yxy_debug.h"



/* 全局变量 */

// 接收数据结构体
rx_Struct rx_InitStr;

// 包链表数据头节点
struct pack_struct * packhandNode = NULL;

// 轮询调用函数指针,以处理对应的包函数
pack_Callback scanHandle = NULL;

// 添加 过 的特殊包的总数量
uint16_t packIdNum = 0;

// 屏幕上的数据数量
uint32_t cmd_buff = 0;


/*********************
 *   内部函数区域
 *********************/

static HAL_StatusTypeDef string_contrast(uint8_t * s1, uint16_t s1Len, uint8_t * s2);
static uint16_t string_compare_hand(uint8_t * s1, uint8_t * s2);
static void rx_struct_Clear(void);
static void pack_classify(void);




/**
  * @brief  字符串对比函数
  * @param  字符串1
  * @param  字符串2
  * @retval 返回HAL_OK,匹配成功;返回HAL_ERROR,匹配失败。
  */
static HAL_StatusTypeDef string_contrast(uint8_t * s1, uint16_t s1Len, uint8_t * s2)
{
	uint32_t len = s1Len;
	uint8_t *pS1 = s1, *pS2 = s2;
	
	while(len>0)
	{
//		yxy_DEBUG("s1: %c, S2: %c\r\n", *pS1, *pS2);
		
		if(*pS1 != *pS2)
		{
//			yxy_DEBUG("%s != %s\r\n", s1, s2);
			return HAL_ERROR;
		}
		pS1++;
		pS2++;
		len--;
	}
	
	return HAL_OK;
}


/**
  * @brief  子串比较函数
  * @param  子串1
  * @param  子串2
  * @retval 从头比较的相同字符的子串长度
  */
static uint16_t string_compare_hand(uint8_t * s1, uint8_t * s2)
{
	uint16_t sonLen = 0;
	uint16_t l1=0, l2=0;
	uint8_t *pS1=s1, *pS2=s2;
	
	l1 = str_len(s1);
	l2 = str_len(s2);
	
//	yxy_DEBUG("comp : l1=%d, l2=%d\r\n", l1, l2);
	
	// 比较 关键词串 和 接收串,一旦 接收串>=关键词串,则不用执行tab。
	if(l1 <= l2)
	{
		return  0;
	}
	
	while(*pS1 == *pS2)
	{
		pS1++;
		pS2++;
		sonLen++;
	}
	
	return sonLen;
}

/* 接收结构体清除 */
static void rx_struct_Clear(void)
{
	int i;
	for(i=0; i<RX_BUF_SIZE; i++)
	{
		rx_InitStr.rx_buf[i] = 0;
	}
	
	rx_InitStr.a_rx_buf = 0;
	rx_InitStr.rx_cnt = 0;
}

/**
  * @brief  tab处理函数,用于查找并适配库中的文本
  * @retval 无
  */
static void tab_handler(void)
{
	uint16_t maxLen = 0;	// 记录最大长度
	uint16_t middleLen = 0; // 记录一次比较的长度
	uint16_t sameCount = 0;	// 记录相同长度的计数
	uint16_t wrapCnt = 0;	// 相同关键词的换行计数,一行超过5个,则换行
	
	uint8_t *pRxBuf = rx_InitStr.rx_buf;
	struct pack_struct * pTemp[5] = {NULL};	// 用来存储子串相同的
	struct pack_struct * pPN = packhandNode->packNext;
	
	while(pPN != NULL)
	{
		middleLen = string_compare_hand(pPN->special, pRxBuf);
		
		if((maxLen == middleLen) && (maxLen != 0))
		{
			pTemp[sameCount++] = pPN;
		}

		if(maxLen < middleLen)
		{
			maxLen = middleLen;
			sameCount = 0;
			
			pTemp[sameCount++] = pPN;
		}
		
		pPN = pPN->packNext;
	}
	
	// 如果只有1个元素
	if(sameCount == 1)
	{
		while(rx_InitStr.rx_cnt <= pTemp[0]->specialLen)
		{
			rx_InitStr.rx_buf[rx_InitStr.rx_cnt] = pTemp[0]->special[rx_InitStr.rx_cnt];
			rx_InitStr.rx_cnt++;
		}
		rx_InitStr.rx_cnt -= 1;
		
		yxy_DEBUG("\r\n%s", pTemp[0]->special);
		return;
	}
	
	// 如果有一堆元素
	yxy_DEBUG("\r\n");
	while(sameCount--)
	{
		// 一行过多字符换行
		wrapCnt++;
		if(wrapCnt >= 3)
		{
			yxy_DEBUG("\r\n");
			wrapCnt=0;
		}
		
		// 显示有可能的字符
		yxy_DEBUG("%s  ", pTemp[sameCount]->special);
	}
	yxy_DEBUG("\r\n");
	
	// 显示原本输入的
//	yxy_DEBUG("\r\nux cnt %d\r\n", rx_InitStr.rx_cnt);
	yxy_DEBUG("%s", rx_InitStr.rx_buf);
}


/**
  * @brief  包处理分类函数
  * @retval 
  */
static void pack_classify(void)
{
	uint16_t iCnt = 0;
	uint8_t *pRxBuf = rx_InitStr.rx_buf;
	struct pack_struct * pPN = packhandNode->packNext; // 节点特殊字节点
	
	while(pPN)
	{
		// 如果输入的词句与关键词匹配成功
		if(string_contrast(pPN->special, pPN->specialLen, pRxBuf) == HAL_OK)
		{
			// 算出关键词后的,数据的长度
			pPN->userRX->dataLen = str_len(pRxBuf) - pPN->specialLen;
			
			// 赋值关键词的数据
			while(iCnt < pPN->userRX->dataLen)
			{
				pPN->userRX->dataVal[iCnt] = pRxBuf[pPN->specialLen+iCnt];
				iCnt++;
			}
			pPN->userRX->dataVal[iCnt] = '\0';
			
//			yxy_DEBUG("keyword data      : %s \r\n", pPN->userRX->dataVal);
//			yxy_DEBUG("keyword data size : %d \r\n", pPN->userRX->dataLen);
//			yxy_DEBUG("keyword           : %s be ready.\r\n\r\n", pPN->special);
			
			// 则置位在 scanHandle 变量中,等待下次在扫描函数中去调用
			scanHandle = pPN->callbackFun;
			return;
		}
		
		pPN = pPN->packNext;
	}
}



/*********************
 *   外部函数区域
 *********************/


/* 手写printf,避免使用微库造成不必要的错误 */
void U_Printf(const char *fmt, ...)
{
    char Uart_buf[TX_BUF_SIZE];

    va_list args;
    va_start(args, fmt);
    int length = vsnprintf(Uart_buf, sizeof(Uart_buf) - 1, fmt, args);
    va_end(args);
	
	cmd_buff += length;

    HAL_UART_Transmit(&uuart, (uint8_t *)Uart_buf, length, 0xfff);
}

/* 串口接收初始化 */
HAL_StatusTypeDef uart_RX_init(void)
{
	// 接收结构体清除
	rx_struct_Clear();
	
	// 串口接收中断初始化
	HAL_UART_Receive_IT(&uuart, &rx_InitStr.a_rx_buf, 1);
	
	packhandNode = (struct pack_struct *)malloc(sizeof(struct pack_struct));
	if(packhandNode == NULL)
	{
		yxy_DEBUG("open usart initialization failed... \r\n");
		return HAL_ERROR;
	}
	packhandNode->packNext = NULL;
	
	packhandNode->userRX = NULL;
	packhandNode->packId = 0xffff;
	
	strcpy((char *)packhandNode->special, "x");
	strcpy((char *)packhandNode->packText, "USER");
	
//	yxy_DEBUG("special : %s, size = %d\r\n", packhandNode->special, sizeof("xxxxxxxxx"));
//	yxy_DEBUG("packText : %s, size = %d\r\n", packhandNode->packText, sizeof("UART link hand node."));
	
	return HAL_OK;
}

/**
  * @brief  串口接收添加可识别的包,包头为特殊字符串
			当接收到这个特殊字符串时,既包开始接收,
			数据不定长,需要用户在自己的回调中处理,
			当接收到回车换行时,处理包的内容,
			并在下次main循环中调用对应的回调函数。
  * @param  userRX 用户定义的初始化结构体
  * @param  special 需要添加的特殊字符串
  * @param  specialLen 特殊字符串的长度
  * @param  packText 包的解释语言
  * @param  callbackFun 解析包的回调函数
  * @retval 返回hal库错误码
  */
HAL_StatusTypeDef rx_Pack_add(User_USART_RX *userRX, uint8_t * special, uint8_t specialLen,
								uint8_t * packText, pack_Callback callbackFun)
{
	uint16_t iCnt = 0;
	struct pack_struct * pPN = packhandNode; // 节点特殊字节点
	
	// 找到空的节点
	while(pPN->packNext != NULL)
	{
		pPN = pPN->packNext;
	}
	
	pPN->packNext = (struct pack_struct *)malloc(sizeof(struct pack_struct));
	
	pPN = pPN->packNext;
	if(pPN == NULL)
	{
		yxy_DEBUG("usart add failed... \r\n");
		return HAL_ERROR;
	}
	pPN->packNext = NULL;
	
	pPN->specialLen = specialLen;
	pPN->userRX = userRX;
	pPN->packId = packIdNum;
	
	strcpy((char *)pPN->special, (const char *)special);
	strcpy((char *)pPN->packText, (const char *)packText);
	
	yxy_DEBUG("add special : %s, size = %d\r\n", pPN->special, str_len(special));
//	yxy_DEBUG("add packText : %s, size = %d\r\n", pPN->packText, str_len(packText));
	
	pPN->callbackFun = callbackFun;
	
	
	// 用户句柄赋值
	userRX->special = pPN->special;
	userRX->packText = pPN->packText;
	userRX->specialLen = pPN->specialLen;
	userRX->packId = packIdNum;
	while(iCnt++ < SPECIAL_DATA_SIZE)
	{
		userRX->dataVal[iCnt] = 0;
	}
	userRX->dataLen = 0;
	
	packIdNum++;
	
	return HAL_OK;
}

/**
  * @brief  串口接收可识的包的删除
  * @param  
  * @retval 返回hal库错误码
  */
HAL_StatusTypeDef rx_Pack_clear(User_USART_RX *userRX)
{
	struct pack_struct * pPN = packhandNode; // 节点特殊字节点
	struct pack_struct * pTextN = NULL; 
	
	// 找到空的节点
	while(pPN->packNext != NULL)
	{
		// 如果id匹配的则删除对应id的特殊包节点
		if(pPN->packNext->packId == userRX->packId)
		{
			pTextN = pPN->packNext;
			pPN->packNext = pPN->packNext->packNext;
			
			free(pTextN);
			pTextN = NULL;
			
			userRX->packId = 0;
			
			yxy_DEBUG("clear rx ok\r\n");
			return HAL_OK;
		}
		
		pPN = pPN->packNext;
	}
	
	return HAL_ERROR;
}

/**
  * @brief  接收扫描函数,一旦接收到关键词,在这里处理 
  * @retval 
  */
void uart_rx_scan(void)
{
	if(scanHandle != NULL)
	{
		scanHandle();
		scanHandle = NULL;
	}
}



/* 串口中断函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(rx_InitStr.rx_cnt >= RX_BUF_SIZE)//溢出判断
	{
		yxy_DEBUG("ESP data overflow\r\n");
//			yxy_DEBUG("%s\r\n", rx_InitStr.rx_buf);
		rx_struct_Clear();
	}
	else //数据存入
	{
		HAL_UART_Transmit(&uuart, (uint8_t *)&(rx_InitStr.a_rx_buf), 1, 0xff);
//			yxy_DEBUG("%c: 0x%02x \r\n", rx_InitStr.a_rx_buf, rx_InitStr.a_rx_buf);

//			yxy_DEBUG("%d : %c\r\n", rx_InitStr.rx_cnt, rx_InitStr.a_rx_buf);
		
		rx_InitStr.rx_buf[rx_InitStr.rx_cnt] = rx_InitStr.a_rx_buf;
		rx_InitStr.rx_cnt++;
		
		// 一旦等于删除建,就退回到之前的
		if((rx_InitStr.a_rx_buf == 0x08) && (rx_InitStr.rx_cnt >= 2))
		{
			rx_InitStr.rx_cnt -= 2;
		}
		
		// 等于水平制表符键位
		if(rx_InitStr.a_rx_buf == 0x09)
		{
			rx_InitStr.rx_cnt--;
			rx_InitStr.rx_buf[rx_InitStr.rx_cnt] = '\0';
			if(rx_InitStr.rx_cnt != 0)
			{
				tab_handler();
			}
		}
		
		
#if (CR_LF_IS_WHITCH == 0)
		// 当上一个字符为\r这一次字符为\n,说明一行的结束
		if(rx_InitStr.rx_buf[rx_InitStr.rx_cnt-2] == '\r' && 
			rx_InitStr.rx_buf[rx_InitStr.rx_cnt-1] == '\n')
		{
			rx_InitStr.rx_buf[rx_InitStr.rx_cnt-2] = '\0';
#elif (CR_LF_IS_WHITCH == 1)
		// 当这一次字符为\n,说明一行的结束
		if(rx_InitStr.rx_buf[rx_InitStr.rx_cnt-1] == '\n')
		{
		
#elif (CR_LF_IS_WHITCH == 2)
		if(rx_InitStr.rx_buf[rx_InitStr.rx_cnt-1] == '\r')
		{
#endif
//			yxy_DEBUG("\r\ninput = %s\r\n", rx_InitStr.rx_buf);
			yxy_DEBUG("\r\n");

			rx_InitStr.rx_buf[rx_InitStr.rx_cnt-1] = '\0';
			pack_classify();
			rx_struct_Clear();
		}
	}
	
	HAL_UART_Receive_IT(&uuart, (uint8_t *)&(rx_InitStr.a_rx_buf), 1);   //再开启接收中断
}





yxy_open_cmd.h

#ifndef __YXY_OPEN_CMD_H_
#define __YXY_OPEN_CMD_H_


#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/

#include "stdio.h"
#include "usart.h"

/*********************
 *      DEFINES
 *********************/

/* 串口的hal库的句柄 */
#define uuart huart1

/* 串口发送缓冲区 */
#define TX_BUF_SIZE 64

/* 串口接收buff大小 */
#define RX_BUF_SIZE 64

/* 包关键词支持的大小 */
#define SPECIAL_CHAR_SIZE 10

/* 包关键词的说明文档支持的大小 */
#define SPECIAL_TEXT_SIZE 50

/* 包关键词的数据支持的大小 */
#define SPECIAL_DATA_SIZE 15

/* 回车换行是\r\n 则设置为0是;为\n(0x0a),则设置为 1 ;为\r(0x0d),则设置成 2*/
#define CR_LF_IS_WHITCH 2


#if (CR_LF_IS_WHITCH == 0)
#	define USER_CRLF \r\n
#elif (CR_LF_IS_WHITCH == 1)
#	define USER_CRLF \n
#elif (CR_LF_IS_WHITCH == 2)
#	define USER_CRLF \r
#else
#	define USER_CRLF
#endif

/**********************
 *      TYPEDEFS
 **********************/
 
 /* 包回调函数格式 */ 
typedef void(*pack_Callback)(void);
 
/* 接收结构体 */
typedef struct rx_struct
{
	uint8_t  rx_buf[RX_BUF_SIZE];	// 接收缓冲存取区
	uint8_t  a_rx_buf;				// 缓冲区元素暂存位置
	uint16_t rx_cnt;				//当前接收计数
}rx_Struct;

/* 用户存储信息指针结构体 */
typedef struct 
{
	uint8_t *special;	// 关键词
	uint8_t *packText;	// 关键词解释
	uint8_t specialLen;	// 关键词长度
	uint8_t dataVal[SPECIAL_DATA_SIZE];	// 数据值
	uint8_t dataLen;	// 数据长度
	uint16_t packId;	// 特殊字id
}User_USART_RX;

/* 接收包的链表结构体 */
struct pack_struct
{
	uint8_t special[SPECIAL_CHAR_SIZE];	// 需要判定的特殊字符串
	uint8_t packText[SPECIAL_TEXT_SIZE];// 特殊字的说明	
	
	uint8_t specialLen;					// 特殊字长度
	
	uint16_t packId;					// 特殊字id
	
	User_USART_RX * userRX;				// 访问用户的指针
	
	pack_Callback callbackFun;			// 包回调函数
	
	struct pack_struct * packNext;		// 下一个节点
};




/**********************
* GLOBAL PROTOTYPES
**********************/
/**
  * @brief  获取字符串长度
  * @param  字符串指针
  * @retval 返回字符串长度
  */
inline static uint16_t str_len(uint8_t * str)
{
	uint16_t len=0;
	
	while(*str != '\0')
	{
		len++;
		str++;
	}
	
	return len;
}


/* 测试 */

//HAL_StatusTypeDef string_cont(uint8_t * s1, uint8_t * s2);

/**********************
* GLOBAL PROTOTYPES
**********************/

extern uint32_t cmd_buff;


void U_Printf(const char *fmt, ...);


/*	//用法

	U_Printf("niubi%d\n", 12);

*/

HAL_StatusTypeDef uart_RX_init(void);
HAL_StatusTypeDef rx_Pack_add(User_USART_RX *userRX, uint8_t * special, uint8_t specialLen,
								uint8_t * packText, pack_Callback callbackFun);
HAL_StatusTypeDef rx_Pack_clear(User_USART_RX *userRX);

void uart_rx_scan(void);


#ifdef __cplusplus
} /*extern "C"*/
#endif

#endif /*__YXY_OPEN_CMD_H_*/

yxy_debug.c

#include "yxy_debug.h"
#include "stdio.h"


#ifdef YXY_DEBUG
#	if (YXY_DEBUG_SELE == 0)
		//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
		int fputc(int ch, FILE *f)
		{
			/* 发送一个字节数据到串口DEBUG_USART */
			HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);	
			
			return (ch);
		}
#	endif
#endif


void error_printf(const char * file, int line, const char * func, const char * format, ...)
{
	USER_PASS;
}

yxy_debug.h

#ifndef __YXY_DEBUG_H_
#define __YXY_DEBUG_H_


#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/

#include "stdint.h"


/*********************
 *      DEFINES
 *********************/
 
// 相当于python的 pass 语句
#define USER_PASS ((void)0U)

// 打印转换后的宏
#define _VNAME(name) (#name)
#define VNAME(name)	(_VNAME(name))


 弱定义
//#define __WEAK                                 __attribute__((weak))

/* 按键串口调试支持 0 不支持 , 1 支持*/
#define YXY_DEBUG 1
#ifdef YXY_DEBUG

	/* debug 选择,
	 * 为 1 选择外部的 用户自定义的打印函数,
	 * 为0则调用stdio库中的printf
	*/
#	define YXY_DEBUG_SELE 1

	/* 开启debug,这里的外部的debug文件可以重写 */
	/* 调试的外部函数,可以是printf或者是其他手写的多参数打印函数
	 * 多参数打印函数的格式是 :
	 * 		void function(const char *, ...);
	 */
#	if (YXY_DEBUG_SELE == 1)
#		include "yxy_open_cmd.h" 
#		define __uprintf__ U_Printf
#	else
#		include "stdio.h"
#		define __uprintf__ printf
#	endif

#endif

/* 调试函数实现 */
#if (YXY_DEBUG == 1)
#	define yxy_DEBUG(...) __uprintf__(__VA_ARGS__)
#else
#	define yxy_DEBUG(...) do{}while(0)
#endif
/**********************
 *      TYPEDEFS
 **********************/
 


/**********************
*  GLOBAL PROTOTYPES
**********************/


#ifdef __cplusplus
} /*extern "C"*/
#endif

#endif /*__YXY_DEBUG_H_*/

完整代码工程

开源终端完整工程代码点这里

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
内置 "cmd-list" 命令获取所有命令列表 支持 tab 键补全命令 支持 backspace 回退,这个在 putty 上有 bug ,还没修复。在secureCRT正常。 支持上下箭头回溯历史,这个功能没有做的很好; 支持左右箭头编辑当前命令行输入; 提供 shell_cmdparam()函数转换命令后所跟的数字参数(字符串转整型),详见 demo 提供 shell_option_suport() 函数,使命令行支持 getopt()函数,详见 demo 系统共有9个文件,全部与硬件无关,编译语言要在 C99 以上(keil 在 project -> Options .. -> c/c++ -> C99 勾上) kernel.h // 一些必要的宏定义 shell.c,shell.h //具体的命令行解释的实现 ustdio.c,ustdio.h //非标准输出文件,重新链接 printf ,并提供一个小巧型的 printk 函数 avltree.c,avltree.h //平衡二叉树支持,shell 默认用链表建立查询机制,有必要可在shell.h 中开启二叉树 getopt.c,getopt.h //网上找的 getopt() 源码 除了 getopt.c,getopt.h 两个文件是我从网上找的源码,主要实现命令行的 getopt()解析,其他的都是笔者所写。 使用: 1,首先把 shell.c ustdio.c 加入工程,这两个文件是必须的。 如果不用 getopt()函数可以不添加getopt.c文件。 shell默认使用链表来构建查询系统,但也提供了平衡二叉树的方式,在shell.h中有开启的开关,如不需要可以不添加avltree.c。 include "shell.h" 2,先对硬件进行基本的初始化。 撰写串口发送函数,形如 void usart_puts(char * str , uint16_t len) ; 调用函数 shell_init("shell >",usart_puts); //初始化shell的输入标志和默认输出指向 串口接收以包为单位。 3,新建全局变量 struct shell_input serial_shell ; 并初始化 SHELL_INPUT_INIT(&serial;_shell ,usart_puts);初始化输入缓存和输出交互; 4,串口接收到一整包函数后,调用 shell_input(&serial;_shell , packet , pktlen) ;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入一下?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值