简言
我之前学linux的时候,觉得linux的命令很酷,最近又有写代码的热情,于是手撸了一个串口终端。
初次使用
介绍特性
-
像终端一样使用
- 输入niubi后,键入回车,输出自己写的回调函数中的内容。
-
支持tab键补全
- 按下n后按下tab键后,到库中匹配关键词,匹配到niubi,显示到下一行。
-
当有多个关键词可以匹配时,提示匹配关键词,并在新的一行显示刚刚输入的内容。
-
支持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_*/