文章目录
一、USMART简介
1.简介
USMART是一个串口调试组件,旨在提高代码调试效率。通过USMART,用户可以直接通过串口调用已编写的函数,并且可以随意修改函数参数,从而快速进行代码调试和功能验证。
使用USMART,开发者可以在不修改代码的情况下,通过串口发送指令来调用预先编写的函数,并且可以实时观察函数的返回值或输出结果。这样的设计极大地简化了调试过程,加快了开发速度,提高了代码的可调试性和可维护性。
总的来说,USMART是一个强大的串口调试工具,能够在嵌入式开发 中发挥重要作用,特别是在调试和验证阶段。

2.主要特点
USMART具有以下主要特点(不支持浮点型参数):
- 调用用户编写的函数:可以直接调用绝大部分用户编写的函数,无需修改代码,方便快捷地进行调试和功能验证。
- 占用资源少:USMART在嵌入式系统中占用的资源较少,最小占用为4KB FLASH和72B SRAM,不会对系统资源造成过大的压力。
- 支持多种参数类型:支持多种参数类型,包括整数(10进制/16进制)、字符串、函数指针等,满足不同类型参数的调试需求。
- 函数返回值显示:支持函数返回值的显示,并且可以对返回值的格式进行设置,便于开发者查看和分析函数执行结果。
- 支持函数执行时间计算:可以计算函数执行的时间,帮助开发者评估函数的执行效率和优化代码。

二、USMART内容
1.工作原理
USMART的工作原理可以概括为以下几个步骤:
-
用户输入字符串:用户通过串口输入字符串,通常以回车换行符 "\r\n" 结束。
-
解析并对比:系统对用户输入的字符串进行解析,提取出函数名和参数。然后将提取出的函数名与本地函数名表进行对比。
-
函数名匹配结果:
- 函数名匹配失败:如果提取出的函数名与本地函数名表中的函数名不匹配,则提示用户输入的函数名有误或者不存在,报错并等待下一次输入。
- 函数名匹配成功:如果提取出的函数名与本地函数名表中的某个函数名匹配成功,则获取该函数名对应的函数指针。然后将参数传递给该函数指针所指向的函数,并执行该函数。
-
返回执行结果:执行完函数后,将执行结果返回给用户,通常是函数的返回值或者执行状态。然后等待下一次用户输入。
通过这样的流程,USMART实现了通过串口调用用户编写的函数,方便快捷地进行代码调试和功能验证。

2.USMART组成
USMART由以下几个文件组成,每个文件负责不同的功能:
usmart.c/h:核心文件,负责处理命令并与外部进行交互。实现了命令解析、函数调用等核心功能。usmart_config.c:函数管理文件,用户可以在此文件中添加需要由USMART管理的函数。通过添加函数声明和对应的函数指针,使得USMART能够识别并调用用户编写的函数。usmart_port.c/h:移植文件,用于USMART的移植。用户根据具体的硬件平台和串口配置,修改这些文件,以适配不同的系统环境和串口设定。usmart_str.c/h:字符串处理文件,负责字符串的转换、参数获取等操作。提供了一些字符串处理的函数,用于解析用户输入的命令和参数。
通过修改usmart_port.c/h文件可以完成USMART的移植工作,确保其能够在特定的硬件平台上正常运行。而通过修改usmart_config.c文件,用户可以添加自己想要由USMART管理的函数,从而实现对这些函数的串口调用。
入口函数:
c
/**
* @brief USMART扫描函数
* @note 通过调用该函数实现USMART的各个控制。该函数需要每隔一定时间被调用一次,
* 以及时执行从串口发过来的各个函数。可以在中断里面调用,从而实现自动管理。
* 如果非正点原子用户,则USART_RX_STA和USART_RX_BUF[]需要用户自己实现。
* @param 无
* @retval 无
*/
void usmart_scan(void)
{
uint8_t sta, len;
char *pbuf = 0;
pbuf = usmart_get_input_string(); /* 获取数据数据流 */
if (pbuf == 0) return ; /* 数据流为空,直接返回 */
sta = usmart_dev.cmd_rec(pbuf); /* 解析函数信息 */
if (sta == 0)
{
usmart_dev.exe(); /* 执行函数 */
}
else
{
len = usmart_sys_cmd_exe(pbuf); /* 执行系统命令 */
if (len != USMART_FUNCERR) sta = len;
if (sta)
{
switch (sta)
{
case USMART_FUNCERR:
USMART_PRINTF("函数错误!\r\n");
break;
case USMART_PARMERR:
USMART_PRINTF("参数错误!\r\n");
break;
case USMART_PARMOVER:
USMART_PRINTF("参数太多!\r\n");
break;
case USMART_NOFUNCFIND:
USMART_PRINTF("未找到匹配的函数!\r\n");
break;
}
}
}
}
三、USMART移植

USMART移植的步骤如下:
- 获取USMART组件:首先,从正点原子等资源处获取完整的USMART组件,包括核心文件(usmart.c/h)、函数管理文件(usmart_config.c)、移植文件(usmart_port.c/h)以及字符串处理文件(usmart_str.c/h)。
- 添加到工程:将获取到的全部组件添加到您的工程中,并设置好路径关联,以确保编译器能够正确地找到这些文件。
- 适配硬件:修改移植文件(usmart_port.c/h),以适配您的硬件平台。主要修改调试串口和定时器相关的配置,确保USMART能够在您的硬件环境中正常工作。
- 添加执行函数:在函数管理文件(usmart_config.c)中添加或修改需要由USMART管理的执行函数。添加函数声明和函数指针,使得USMART能够识别并调用您所添加的函数。
- 通过串口交互:完成移植后,将移植好的USMART组件烧录到目标设备中。通过串口与目标设备交互,可以测试和调用您添加的函数,并进行调试和验证。
1.移植步骤
通过以上步骤,您可以成功地将USMART移植到您的目标设备中,并通过串口进行交互,实现对特定函数的调用和执行。
(1)获取USMART组件

(2)添加到工程


(3)适配硬件


(4)添加执行函数

(5)通过串口交互


四、完整代码
1.main.c
c
#include <stdio.h>
#include <board.h>
#include "usmart.h"
int main(void)
{
usart_init(115200);
usmart_dev.init(84);
while (1)
{
wait_ms(1000);
}
return 0;
}
2.usmart.h
c
/**
****************************************************************************************************
* @file usmart.h
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 串口调试组件
*
* USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
* 序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)
* 、字符串、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.
* V2.1版本以后新增hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进
* 制转换,例如:
* 输入"hex 100" 会在串口调试助手上看到 HEX 0X64.
* 输入"dec 0X64" 会在串口调试助手上看到 DEC 100.
* @note
* USMART资源占用情况@MDK 3.80A@2.0版本:
* FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
* SRAM:72字节(最少的情况下)
* SRAM计算公式: SRAM=PARM_LEN+72-4 其中PARM_LEN必须大于等于4.
* 应该保证堆栈不小于100个字节.
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
*
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
****************************************************************************************************
*/
#ifndef __USMART_H
#define __USMART_H
#include "usmart_port.h"
#define USMART_OK 0 /* 无错误 */
#define USMART_FUNCERR 1 /* 函数错误 */
#define USMART_PARMERR 2 /* 参数错误 */
#define USMART_PARMOVER 3 /* 参数溢出 */
#define USMART_NOFUNCFIND 4 /* 未找到匹配函数 */
#define SP_TYPE_DEC 0 /* 10进制参数显示 */
#define SP_TYPE_HEX 1 /* 16进制参数显示 */
/* 函数名列表 */
struct _m_usmart_nametab
{
void *func; /* 函数指针 */
const char *name; /* 函数名(查找串) */
};
/* usmart控制管理器 */
struct _m_usmart_dev
{
struct _m_usmart_nametab *funs; /* 函数名指针 */
void (*init)(uint16_t tclk); /* 初始化 */
uint8_t (*cmd_rec)(char *str); /* 识别函数名及参数 */
void (*exe)(void); /* 执行 */
void (*scan)(void); /* 扫描 */
uint8_t fnum; /* 函数数量 */
uint8_t pnum; /* 参数数量 */
uint8_t id; /* 函数id */
uint8_t sptype; /* 参数显示类型(非字符串参数):0,10进制;1,16进制; */
uint16_t parmtype; /* 参数的类型 */
uint8_t plentbl[MAX_PARM]; /* 每个参数的长度暂存表 */
uint8_t parm[PARM_LEN]; /* 函数的参数 */
uint8_t runtimeflag; /* 0,不统计函数执行时间;1,统计函数执行时间,注意:此功能必须在USMART_ENTIMX_SCAN使能的时候,才有用 */
uint32_t runtime; /* 运行时间,单位:0.1ms,最大延时时间为定时器CNT值的2倍*0.1ms */
};
extern struct _m_usmart_nametab usmart_nametab[]; /* 在usmart_config.c里面定义 */
extern struct _m_usmart_dev usmart_dev; /* 在usmart_config.c里面定义 */
void usmart_init(uint16_t tclk); /* 初始化 */
uint8_t usmart_cmd_rec(char*str); /* 识别 */
void usmart_exe(void); /* 执行 */
void usmart_scan(void); /* 扫描 */
uint32_t read_addr(uint32_t addr); /* 读取指定地址的值 */
void write_addr(uint32_t addr,uint32_t val); /* 在指定地址写入指定的值 */
#endif
3.usmart.c
c
/**
****************************************************************************************************
* @file usmart.c
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 串口调试组件
*
* USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
* 序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)
* 、字符串、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.
* V2.1版本以后新增hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进
* 制转换,例如:
* 输入"hex 100" 会在串口调试助手上看到 HEX 0X64.
* 输入"dec 0X64" 会在串口调试助手上看到 DEC 100.
* @note
* USMART资源占用情况@MDK 3.80A@2.0版本:
* FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
* SRAM:72字节(最少的情况下)
* SRAM计算公式: SRAM=PARM_LEN+72-4 其中PARM_LEN必须大于等于4.
* 应该保证堆栈不小于100个字节.
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
*
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
*
****************************************************************************************************
*/
#include "usmart.h"
#include "usmart_str.h"
#include "usmart_port.h"
/* 系统命令 */
char *sys_cmd_tab[] =
{
"?",
"help",
"list",
"id",
"hex",
"dec",
"runtime",
};
/**
* @brief 处理系统指令
* @param str : 字符串指针
* @retval 0,成功处理;其他,错误代码;
*/
uint8_t usmart_sys_cmd_exe(char *str)
{
uint8_t i;
char sfname[MAX_FNAME_LEN]; /* 存放本地函数名 */
uint8_t pnum;
uint8_t rval;
uint32_t res;
res = usmart_get_cmdname(str, sfname, &i, MAX_FNAME_LEN); /* 得到指令及指令长度 */
if (res)return USMART_FUNCERR; /* 错误的指令 */
str += i;
for (i = 0; i < sizeof(sys_cmd_tab) / 4; i++) /* 支持的系统指令 */
{
if (usmart_strcmp(sfname, sys_cmd_tab[i]) == 0)break;
}
switch (i)
{
case 0:
case 1: /* 帮助指令 */
USMART_PRINTF("\r\n");
#if USMART_USE_HELP
#if 0
USMART_PRINTF("------------------------USMART V3.5------------------------ \r\n");
USMART_PRINTF(" USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 \r\n");
USMART_PRINTF("它,你可以通过串口助手调用程序里面的任何函数,并执行.因此,你可\r\n");
USMART_PRINTF("以随意更改函数的输入参数(支持数字(10/16进制,支持负数)、字符串\r\n"),
USMART_PRINTF("、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持\r\n"),
USMART_PRINTF("函数返回值显示.支持参数显示进制设置功能,支持进制转换功能.\r\n");
USMART_PRINTF("技术支持:www.openedv.com\r\n");
USMART_PRINTF("USMART有7个系统命令(必须小写):\r\n");
USMART_PRINTF("?: 获取帮助信息\r\n");
USMART_PRINTF("help: 获取帮助信息\r\n");
USMART_PRINTF("list: 可用的函数列表\r\n\n");
USMART_PRINTF("id: 可用函数的ID列表\r\n\n");
USMART_PRINTF("hex: 参数16进制显示,后跟空格+数字即执行进制转换\r\n\n");
USMART_PRINTF("dec: 参数10进制显示,后跟空格+数字即执行进制转换\r\n\n");
USMART_PRINTF("runtime:1,开启函数运行计时;0,关闭函数运行计时;\r\n\n");
USMART_PRINTF("请按照程序编写格式输入函数名及参数并以回车键结束.\r\n");
USMART_PRINTF("--------------------------正点原子------------------------- \r\n");
#else
USMART_PRINTF("------------------------USMART V3.5------------------------ \r\n\n");
USMART_PRINTF("USMART There are 7 system commands (must be lowercase):\r\n\n");
USMART_PRINTF("?: Get help information\r\n\n");
USMART_PRINTF("help: Get help information\r\n\n");
USMART_PRINTF("list: List of available functions\r\n\n");
USMART_PRINTF("id: List of IDs for available functions\r\n\n");
USMART_PRINTF("hex: The parameter is displayed in hexadecimal format\r\n\n");
USMART_PRINTF("dec: Parameter displayed in decimal format\r\n\n");
USMART_PRINTF("runtime:1/0,Enable/Disable function running timer\r\n\n");
USMART_PRINTF("----------------------------END---------------------------- \r\n");
#endif
#else
USMART_PRINTF("指令失效\r\n");
#endif
break;
case 2: /* 查询指令 */
USMART_PRINTF("\r\n");
USMART_PRINTF("-------------------------Function List--------------------------- \r\n");
for (i = 0; i < usmart_dev.fnum; i++)USMART_PRINTF("%s\r\n", usmart_dev.funs[i].name);
USMART_PRINTF("\r\n");
break;
case 3: /* 查询ID */
USMART_PRINTF("\r\n");
USMART_PRINTF("-------------------------Function ID --------------------------- \r\n");
for (i = 0; i < usmart_dev.fnum; i++)
{
usmart_get_fname((char *)usmart_dev.funs[i].name, sfname, &pnum, &rval); /* 得到本地函数名 */
USMART_PRINTF("%s id is:\r\n0X%08X\r\n", sfname, (unsigned int)usmart_dev.funs[i].func); /* 显示ID */
}
USMART_PRINTF("\r\n");
break;
case 4: /* hex指令 */
USMART_PRINTF("\r\n");
usmart_get_aparm(str, sfname, &i);
/* 参数正常 */
if (i == 0)
{
i = usmart_str2num(sfname, &res); /* 记录该参数 */
/* 进制转换功能 */
if (i == 0)
{
USMART_PRINTF("HEX:0X%lX\r\n", res); /* 转为16进制 */
}
else if (i != 4)return USMART_PARMERR; /* 参数错误. */
/* 参数显示设定功能 */
else
{
USMART_PRINTF("16 Binary parameter display!\r\n");
usmart_dev.sptype = SP_TYPE_HEX;
}
}
else return USMART_PARMERR; /* 参数错误. */
USMART_PRINTF("\r\n");
break;
case 5: /* dec指令 */
USMART_PRINTF("\r\n");
usmart_get_aparm(str, sfname, &i);
/* 参数正常 */
if (i == 0)
{
i = usmart_str2num(sfname, &res); /* 记录该参数 */
/* 进制转换功能 */
if (i == 0)
{
USMART_PRINTF("DEC:%lu\r\n", (unsigned long)res); /* 转为10进制 */
}
else if (i != 4)
{
return USMART_PARMERR; /* 参数错误. */
}
/* 参数显示设定功能 */
else
{
USMART_PRINTF("10 Binary parameter display!\r\n");
usmart_dev.sptype = SP_TYPE_DEC;
}
}
else
{
return USMART_PARMERR; /* 参数错误. */
}
USMART_PRINTF("\r\n");
break;
case 6: /* runtime指令,设置是否显示函数执行时间 */
USMART_PRINTF("\r\n");
usmart_get_aparm(str, sfname, &i);
/* 参数正常 */
if (i == 0)
{
i = usmart_str2num(sfname, &res); /* 记录该参数 */
/* 读取指定地址数据功能 */
if (i == 0)
{
if (USMART_ENTIMX_SCAN == 0)
{
USMART_PRINTF("\r\nError! \r\nTo EN RunTime function,Please set USMART_ENTIMX_SCAN = 1 first!\r\n");/* 报错 */
}
else
{
usmart_dev.runtimeflag = res;
if (usmart_dev.runtimeflag)
{
USMART_PRINTF("Run Time Calculation ON\r\n");
}
else
{
USMART_PRINTF("Run Time Calculation OFF\r\n");
}
}
}
else
{
return USMART_PARMERR; /* 未带参数,或者参数错误 */
}
}
else
{
return USMART_PARMERR; /* 参数错误. */
}
USMART_PRINTF("\r\n");
break;
default:/* 非法指令 */
return USMART_FUNCERR;
}
return 0;
}
/**
* @brief 初始化USMART
* @param tclk: 定时器的工作频率(单位:Mhz)
* @retval 无
*/
void usmart_init(uint16_t tclk)
{
#if USMART_ENTIMX_SCAN == 1
usmart_timx_init(1000, tclk * 100 - 1);
#endif
usmart_dev.sptype = 1; /* 十六进制显示参数 */
}
/**
* @brief 从str中获取函数名,id,及参数信息
* @param str: 字符串指针.
* @retval 0,识别成功;其他,错误代码.
*/
uint8_t usmart_cmd_rec(char *str)
{
uint8_t sta, i, rval; /* 状态 */
uint8_t rpnum, spnum;
char rfname[MAX_FNAME_LEN]; /* 暂存空间,用于存放接收到的函数名 */
char sfname[MAX_FNAME_LEN]; /* 存放本地函数名 */
sta = usmart_get_fname(str, rfname, &rpnum, &rval); /* 得到接收到的数据的函数名及参数个数 */
if (sta)return sta; /* 错误 */
for (i = 0; i < usmart_dev.fnum; i++)
{
sta = usmart_get_fname((char *)usmart_dev.funs[i].name, sfname, &spnum, &rval); /* 得到本地函数名及参数个数 */
/* 本地解析有误 */
if (sta)return sta;
if (usmart_strcmp(sfname, rfname) == 0) /* 相等 */
{
if (spnum > rpnum)return USMART_PARMERR;/* 参数错误(输入参数比源函数参数少) */
usmart_dev.id = i; /* 记录函数ID. */
break; /* 跳出. */
}
}
if (i == usmart_dev.fnum)return USMART_NOFUNCFIND; /* 未找到匹配的函数 */
sta = usmart_get_fparam(str, &i); /* 得到函数参数个数 */
if (sta)return sta; /* 返回错误 */
usmart_dev.pnum = i; /* 参数个数记录 */
return USMART_OK;
}
/**
* @brief USMART执行函数
* @note
* 该函数用于最终执行从串口收到的有效函数.
* 最多支持10个参数的函数,更多的参数支持也很容易实现.不过用的很少.一般5个左右的参数的函数已经很少见了.
* 该函数会在串口打印执行情况.以:"函数名(参数1,参数2...参数N)=返回值".的形式打印.
* 当所执行的函数没有返回值的时候,所打印的返回值是一个无意义的数据.
*
* @param 无
* @retval 无
*/
void usmart_exe(void)
{
uint8_t id, i;
uint32_t res;
uint32_t temp[MAX_PARM]; /* 参数转换,使之支持了字符串 */
char sfname[MAX_FNAME_LEN]; /* 存放本地函数名 */
uint8_t pnum, rval;
id = usmart_dev.id;
if (id >= usmart_dev.fnum)return; /* 不执行. */
usmart_get_fname((char *)usmart_dev.funs[id].name, sfname, &pnum, &rval); /* 得到本地函数名,及参数个数 */
USMART_PRINTF("\r\n%s(", sfname); /* 输出正要执行的函数名 */
for (i = 0; i < pnum; i++) /* 输出参数 */
{
/* 参数是字符串 */
if (usmart_dev.parmtype & (1 << i))
{
USMART_PRINTF("%c", '"');
USMART_PRINTF("%s", usmart_dev.parm + usmart_get_parmpos(i));
USMART_PRINTF("%c", '"');
temp[i] = (uint32_t) & (usmart_dev.parm[usmart_get_parmpos(i)]);
}
/* 参数是数字 */
else
{
temp[i] = *(uint32_t *)(usmart_dev.parm + usmart_get_parmpos(i));
if (usmart_dev.sptype == SP_TYPE_DEC)
{
USMART_PRINTF("%ld", (long)temp[i]); /* 10进制参数显示 */
}
else
{
USMART_PRINTF("0X%lX", temp[i]); /* 16进制参数显示 */
}
}
if (i != pnum - 1)USMART_PRINTF(",");
}
USMART_PRINTF(")");
#if USMART_ENTIMX_SCAN==1
/* 计时器清零,开始计时 */
usmart_timx_reset_time();
#endif
switch (usmart_dev.pnum)
{
case 0: /* 无参数(void类型) */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)();
break;
case 1: /* 有1个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0]);
break;
case 2: /* 有2个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1]);
break;
case 3: /* 有3个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2]);
break;
case 4: /* 有4个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3]);
break;
case 5: /* 有5个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4]);
break;
case 6: /* 有6个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4], \
temp[5]);
break;
case 7: /* 有7个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4], \
temp[5], temp[6]);
break;
case 8: /* 有8个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4], \
temp[5], temp[6], temp[7]);
break;
case 9: /* 有9个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4], \
temp[5], temp[6], temp[7], temp[8]);
break;
case 10:/* 有10个参数 */
res = (*(uint32_t(*)())usmart_dev.funs[id].func)(temp[0], temp[1], temp[2], temp[3], temp[4], \
temp[5], temp[6], temp[7], temp[8], temp[9]);
break;
}
#if USMART_ENTIMX_SCAN==1
usmart_timx_get_time(); /* 获取函数执行时间 */
#endif
if (rval == 1) /* 需要返回值. */
{
if (usmart_dev.sptype == SP_TYPE_DEC)USMART_PRINTF("=%lu;\r\n", (unsigned long)res); /* 输出执行结果(10进制参数显示) */
else USMART_PRINTF("=0X%lX;\r\n", res); /* 输出执行结果(16进制参数显示) */
}
else USMART_PRINTF(";\r\n"); /* 不需要返回值,直接输出结束 */
if (usmart_dev.runtimeflag) /* 需要显示函数执行时间 */
{
USMART_PRINTF("Function Run Time:%ld.%1ldms\r\n", usmart_dev.runtime / 10, usmart_dev.runtime % 10); /* 打印函数执行时间 */
}
}
/**
* @brief USMART扫描函数
* @note
* 通过调用该函数,实现USMART的各个控制.该函数需要每隔一定时间被调用一次
* 以及时执行从串口发过来的各个函数.
* 本函数可以在中断里面调用,从而实现自动管理.
* 如果非正点原子用户,则USART_RX_STA和USART_RX_BUF[]需要用户自己实现
*
* @param 无
* @retval 无
*/
void usmart_scan(void)
{
uint8_t sta, len;
char *pbuf = 0;
pbuf = usmart_get_input_string(); /* 获取数据数据流 */
if (pbuf == 0) return ; /* 数据流空, 直接返回 */
sta = usmart_dev.cmd_rec(pbuf); /* 得到函数各个信息 */
if (sta == 0)
{
usmart_dev.exe(); /* 执行函数 */
}
else
{
len = usmart_sys_cmd_exe(pbuf);
if (len != USMART_FUNCERR)sta = len;
if (sta)
{
switch (sta)
{
case USMART_FUNCERR:
USMART_PRINTF("Function error!\r\n");
break;
case USMART_PARMERR:
USMART_PRINTF("Function error!\r\n");
break;
case USMART_PARMOVER:
USMART_PRINTF("Too many parameters!\r\n");
break;
case USMART_NOFUNCFIND:
USMART_PRINTF("No matching function found!\r\n");
break;
}
}
}
}
#if USMART_USE_WRFUNS == 1 /* 如果使能了读写操作 */
/**
* @brief 读取指定地址的值
* @param 无
* @retval 无
*/
uint32_t read_addr(uint32_t addr)
{
return *(uint32_t *)addr;
}
/**
* @brief 在指定地址写入指定的值
* @param 无
* @retval 无
*/
void write_addr(uint32_t addr, uint32_t val)
{
*(uint32_t *)addr = val;
}
#endif
4.usmart_str.h
c
/**
****************************************************************************************************
* @file usmart_str.h
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 串口调试组件
*
* USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
* 序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)
* 、字符串、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.
* V2.1版本以后新增hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进
* 制转换,例如:
* 输入"hex 100" 会在串口调试助手上看到 HEX 0X64.
* 输入"dec 0X64" 会在串口调试助手上看到 DEC 100.
* @note
* USMART资源占用情况@MDK 3.80A@2.0版本:
* FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
* SRAM:72字节(最少的情况下)
* SRAM计算公式: SRAM=PARM_LEN+72-4 其中PARM_LEN必须大于等于4.
* 应该保证堆栈不小于100个字节.
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
*
****************************************************************************************************
*/
#ifndef __USMART_STR_H
#define __USMART_STR_H
#include "usmart_port.h"
uint8_t usmart_get_parmpos(uint8_t num); /* 得到某个参数在参数列里面的起始位置 */
uint8_t usmart_strcmp(char *str1, char *str2); /* 对比两个字符串是否相等 */
uint32_t usmart_pow(uint8_t m, uint8_t n); /* M^N次方 */
uint8_t usmart_str2num(char *str, uint32_t *res); /* 字符串转为数字 */
uint8_t usmart_get_cmdname(char *str, char *cmdname, uint8_t *nlen, uint8_t maxlen); /* 从str中得到指令名,并返回指令长度 */
uint8_t usmart_get_fname(char *str, char *fname, uint8_t *pnum, uint8_t *rval); /* 从str中得到函数名 */
uint8_t usmart_get_aparm(char *str, char *fparm, uint8_t *ptype); /* 从str中得到一个函数参数 */
uint8_t usmart_get_fparam(char *str, uint8_t *parn); /* 得到str中所有的函数参数. */
#endif
5.usmart_str.c
c
/**
****************************************************************************************************
* @file usmart_str.c
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 串口调试组件
*
* USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
* 序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)
* 、字符串、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.
* V2.1版本以后新增hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进
* 制转换,例如:
* 输入"hex 100" 会在串口调试助手上看到 HEX 0X64.
* 输入"dec 0X64" 会在串口调试助手上看到 DEC 100.
* @note
* USMART资源占用情况@MDK 3.80A@2.0版本:
* FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
* SRAM:72字节(最少的情况下)
* SRAM计算公式: SRAM=PARM_LEN+72-4 其中PARM_LEN必须大于等于4.
* 应该保证堆栈不小于100个字节.
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
*
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
*
****************************************************************************************************
*/
#include "usmart.h"
#include "usmart_str.h"
/**
* @brief 对比字符串str1和str2
* @param str1: 字符串1首地址(指针)
* @param str2: 字符串2首地址(指针)
* @retval 0,相等; 1,不相等;
*/
uint8_t usmart_strcmp(char *str1, char *str2)
{
while (1)
{
if (*str1 != *str2)return 1; /* 不相等 */
if (*str1 == '\0')break; /* 对比完成了. */
str1++;
str2++;
}
return 0; /* 两个字符串相等 */
}
/**
* @brief 把src的内容copy到dst
* @param src: 源地址
* @param dst: 目的地址
* @retval 0,相等; 1,不相等;
*/
void usmart_strcopy(char *src, char *dst)
{
while (1)
{
*dst = *src; /* 拷贝 */
if (*src == '\0')break; /* 拷贝完成了. */
src++;
dst++;
}
}
/**
* @brief 得到字符串的长度(字节)
* @param str: 字符串指针
* @retval 字符串的长度
*/
uint8_t usmart_strlen(char *str)
{
uint8_t len = 0;
while (1)
{
if (*str == '\0')break; /* 拷贝完成了. */
len++;
str++;
}
return len;
}
/**
* @brief 平方函数, m^n
* @param m: 底数
* @param n: 指数
* @retval m的n次方
*/
uint32_t usmart_pow(uint8_t m, uint8_t n)
{
uint32_t result = 1;
while (n--)result *= m;
return result;
}
/**
* @brief 把字符串转为数字
* @note
* 支持16进制转换,但是16进制字母必须是大写的,且格式为以0X开头的.
* 支持负数
* @param str: 字符串指针
* @param res: 转换完的结果存放地址.
* @retval 结果如下:
* @arg 0, 成功转换完成
* @arg 1, 数据格式错误
* @arg 2, 16进制位数为0
* @arg 3, 起始格式错误
* @arg 4, 十进制位数为0
*/
uint8_t usmart_str2num(char *str, uint32_t *res)
{
uint32_t t;
int tnum;
uint8_t bnum = 0; /* 数字的位数 */
char *p;
uint8_t hexdec = 10;/* 默认为十进制数据 */
uint8_t flag = 0; /* 0,没有符号标记;1,表示正数;2,表示负数. */
p = str;
*res = 0; /* 清零. */
while (1)
{
/* 参数合法性检测 */
if ((*p <= '9' && *p >= '0') || ((*str == '-' || *str == '+') && bnum == 0) || (*p <= 'F' && *p >= 'A') || (*p == 'X' && bnum == 1))
{
if (*p >= 'A')hexdec = 16; /* 字符串中存在字母,为16进制格式. */
if (*str == '-')
{
flag = 2; /* 偏移掉符号 */
str += 1;
}
else if (*str == '+')
{
flag = 1; /* 偏移掉符号 */
str += 1;
}
else
{
bnum++; /* 位数增加. */
}
}
else if (*p == '\0')
{
break; /* 碰到结束符,退出 */
}
else
{
return 1; /* 不全是十进制或者16进制数据. */
}
p++;
}
p = str; /* 重新定位到字符串开始的地址. */
if (hexdec == 16) /* 16进制数据 */
{
if (bnum < 3)return 2; /* 位数小于3,直接退出.因为0X就占了2个,如果0X后面不跟数据,则该数据非法. */
if (*p == '0' && (*(p + 1) == 'X')) /* 必须以'0X'开头. */
{
p += 2; /* 偏移到数据起始地址. */
bnum -= 2; /* 减去偏移量 */
}
else
{
return 3; /* 起始头的格式不对 */
}
}
else if (bnum == 0)
{
return 4; /* 位数为0,直接退出. */
}
while (1)
{
if (bnum)bnum--;
if (*p <= '9' && *p >= '0')t = *p - '0'; /* 得到数字的值 */
else t = *p - 'A' + 10; /* 得到A~F对应的值 */
*res += t * usmart_pow(hexdec, bnum);
p++;
if (*p == '\0')break; /* 数据都查完了. */
}
if (flag == 2) /* 是负数? */
{
tnum = -*res;
*res = tnum;
}
return 0; /* 成功转换 */
}
/**
* @brief 得到指令名
* @param str : 源字符串
* @param cmdname : 指令名
* @param nlen : 指令名长度
* @param maxlen : 最大长度(做限制,指令不可能太长的)
* @retval 0,成功;其他,失败.
*/
uint8_t usmart_get_cmdname(char *str, char *cmdname, uint8_t *nlen, uint8_t maxlen)
{
*nlen = 0;
while (*str != ' ' && *str != '\0') /* 找到空格或者结束符则认为结束了 */
{
*cmdname = *str;
str++;
cmdname++;
(*nlen)++; /* 统计命令长度 */
if (*nlen >= maxlen)return 1; /* 错误的指令 */
}
*cmdname = '\0'; /* 加入结束符 */
return 0; /* 正常返回 */
}
/**
* @brief 获取下一个字符(当中间有很多空格的时候,此函数直接忽略空格,找到空格之后的第一个字符)
* @param str : 字符串指针
* @retval 下一个字符
*/
uint8_t usmart_search_nextc(char *str)
{
str++;
while (*str == ' ' && str != 0)str++;
return *str;
}
/**
* @brief 从str中得到函数名
* @param str : 源字符串指针
* @param fname : 获取到的函数名字指针
* @param pnum : 函数的参数个数
* @param rval : 是否需要显示返回值(0,不需要;1,需要)
* @retval 0,成功;其他,错误代码.
*/
uint8_t usmart_get_fname(char *str, char *fname, uint8_t *pnum, uint8_t *rval)
{
uint8_t res;
uint8_t fover = 0; /* 括号深度 */
char *strtemp;
uint8_t offset = 0;
uint8_t parmnum = 0;
uint8_t temp = 1;
char fpname[6]; /* void+X+'/0' */
uint8_t fplcnt = 0; /* 第一个参数的长度计数器 */
uint8_t pcnt = 0; /* 参数计数器 */
uint8_t nchar;
/* 判断函数是否有返回值 */
strtemp = str;
while (*strtemp != '\0')/* 没有结束 */
{
if (*strtemp != ' ' && (pcnt & 0X7F) < 5) /* 最多记录5个字符 */
{
if (pcnt == 0)pcnt |= 0X80; /* 置位最高位,标记开始接收返回值类型 */
if (((pcnt & 0x7f) == 4) && (*strtemp != '*'))break; /* 最后一个字符,必须是* */
fpname[pcnt & 0x7f] = *strtemp; /* 记录函数的返回值类型 */
pcnt++;
}
else if (pcnt == 0X85)
{
break;
}
strtemp++;
}
if (pcnt) /* 接收完了 */
{
fpname[pcnt & 0x7f] = '\0'; /* 加入结束符 */
if (usmart_strcmp(fpname, "void") == 0)
{
*rval = 0; /* 不需要返回值 */
}
else
{
*rval = 1; /* 需要返回值 */
}
pcnt = 0;
}
res = 0;
strtemp = str;
while (*strtemp != '(' && *strtemp != '\0') /* 此代码找到函数名的真正起始位置 */
{
strtemp++;
res++;
if (*strtemp == ' ' || *strtemp == '*')
{
nchar = usmart_search_nextc(strtemp); /* 获取下一个字符 */
if (nchar != '(' && nchar != '*')offset = res; /* 跳过空格和*号 */
}
}
strtemp = str;
if (offset)strtemp += offset + 1; /* 跳到函数名开始的地方 */
res = 0;
nchar = 0; /* 是否正在字符串里面的标志,0,不在字符串;1,在字符串; */
while (1)
{
if (*strtemp == 0)
{
res = USMART_FUNCERR; /* 函数错误 */
break;
}
else if (*strtemp == '(' && nchar == 0)
{
fover++; /* 括号深度增加一级 */
}
else if (*strtemp == ')' && nchar == 0)
{
if (fover)
{
fover--;
}
else
{
res = USMART_FUNCERR; /* 错误结束,没收到'(' */
}
if (fover == 0)break; /* 到末尾了,退出 */
}
else if (*strtemp == '"')
{
nchar = !nchar;
}
if (fover == 0) /* 函数名还没接收完 */
{
if (*strtemp != ' ') /* 空格不属于函数名 */
{
*fname = *strtemp; /* 得到函数名 */
fname++;
}
}
else /* 已经接受完了函数名了. */
{
if (*strtemp == ',')
{
temp = 1; /* 使能增加一个参数 */
pcnt++;
}
else if (*strtemp != ' ' && *strtemp != '(')
{
if (pcnt == 0 && fplcnt < 5) /* 当第一个参数来时,为了避免统计void类型的参数,必须做判断. */
{
fpname[fplcnt] = *strtemp; /* 记录参数特征. */
fplcnt++;
}
temp++; /* 得到有效参数(非空格) */
}
if (fover == 1 && temp == 2)
{
temp++; /* 防止重复增加 */
parmnum++; /* 参数增加一个 */
}
}
strtemp++;
}
if (parmnum == 1) /* 只有1个参数. */
{
fpname[fplcnt] = '\0'; /* 加入结束符 */
if (usmart_strcmp(fpname, "void") == 0)parmnum = 0; /* 参数为void,表示没有参数. */
}
*pnum = parmnum;/* 记录参数个数 */
*fname = '\0'; /* 加入结束符 */
return res; /* 返回执行结果 */
}
/**
* @brief 从str中得到一个函数的参数
* @param str : 源字符串指针
* @param fparm : 参数字符串指针
* @param ptype : 参数类型
* @arg 0 ,数字
* @arg 1 ,字符串
* @arg 0XFF,参数错误
* @retval
* @arg 0, 已经无参数了
* @arg 其他,下一个参数的偏移量.
*/
uint8_t usmart_get_aparm(char *str, char *fparm, uint8_t *ptype)
{
uint8_t i = 0;
uint8_t enout = 0;
uint8_t type = 0; /* 默认是数字 */
uint8_t string = 0; /* 标记str是否正在读 */
while (1)
{
if (*str == ',' && string == 0)enout = 1; /* 暂缓立即退出,目的是寻找下一个参数的起始地址 */
if ((*str == ')' || *str == '\0') && string == 0)break; /* 立即退出标识符 */
if (type == 0) /* 默认是数字的 */
{
/* 数字串检测 */
if ((*str >= '0' && *str <= '9') || *str == '-' || *str == '+' || (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F') || *str == 'X' || *str == 'x')
{
if (enout)break; /* 找到了下一个参数,直接退出. */
if (*str >= 'a')
{
*fparm = *str - 0X20; /* 小写转换为大写 */
}
else
{
*fparm = *str; /* 小写或者数字保持不变 */
}
fparm++;
}
else if (*str == '"') /* 找到字符串的开始标志 */
{
if (enout)break; /* 找到,后才找到",认为结束了. */
type = 1;
string = 1; /* 登记STRING 正在读了 */
}
else if (*str != ' ' && *str != ',') /* 发现非法字符,参数错误 */
{
type = 0XFF;
break;
}
}
else /* string类 */
{
if (*str == '"')string = 0;
if (enout)break; /* 找到了下一个参数,直接退出. */
if (string) /* 字符串正在读 */
{
if (*str == '\\') /* 遇到转义符(不复制转义符) */
{
str++; /* 偏移到转义符后面的字符,不管什么字符,直接COPY */
i++;
}
*fparm = *str; /* 小写或者数字保持不变 */
fparm++;
}
}
i++; /* 偏移量增加 */
str++;
}
*fparm = '\0'; /* 加入结束符 */
*ptype = type; /* 返回参数类型 */
return i; /* 返回参数长度 */
}
/**
* @brief 得到指定参数的起始地址
* @param num : 第num个参数,范围0~9.
* @retval 该参数的起始地址
*/
uint8_t usmart_get_parmpos(uint8_t num)
{
uint8_t temp = 0;
uint8_t i;
for (i = 0; i < num; i++)
{
temp += usmart_dev.plentbl[i];
}
return temp;
}
/**
* @brief 从str中得到函数参数
* @param str : 源字符串
* @param parn : 参数的多少.0表示无参数 void类型
* @retval 0,成功;其他,错误代码.
*/
uint8_t usmart_get_fparam(char *str, uint8_t *parn)
{
uint8_t i, type;
uint32_t res;
uint8_t n = 0;
uint8_t len;
char tstr[PARM_LEN + 1]; /* 字节长度的缓存,最多可以存放PARM_LEN个字符的字符串 */
for (i = 0; i < MAX_PARM; i++)
{
usmart_dev.plentbl[i] = 0; /* 清空参数长度表 */
}
while (*str != '(') /* 偏移到参数开始的地方 */
{
str++;
if (*str == '\0')return USMART_FUNCERR; /* 遇到结束符了 */
}
str++; /* 偏移到"("之后的第一个字节 */
while (1)
{
i = usmart_get_aparm(str, tstr, &type); /* 得到第一个参数 */
str += i; /* 偏移 */
switch (type)
{
case 0: /* 数字 */
if (tstr[0] != '\0') /* 接收到的参数有效 */
{
i = usmart_str2num(tstr, &res); /* 记录该参数 */
if (i)return USMART_PARMERR; /* 参数错误. */
*(uint32_t *)(usmart_dev.parm + usmart_get_parmpos(n)) = res; /* 记录转换成功的结果. */
usmart_dev.parmtype &= ~(1 << n); /* 标记数字 */
usmart_dev.plentbl[n] = 4; /* 该参数的长度为4 */
n++; /* 参数增加 */
if (n > MAX_PARM)return USMART_PARMOVER; /* 参数太多 */
}
break;
case 1:/* 字符串 */
len = usmart_strlen(tstr) + 1; /* 包含了结束符'\0' */
usmart_strcopy(tstr, (char *)&usmart_dev.parm[usmart_get_parmpos(n)]); /* 拷贝tstr数据到usmart_dev.parm[n] */
usmart_dev.parmtype |= 1 << n; /* 标记字符串 */
usmart_dev.plentbl[n] = len; /* 该参数的长度为len */
n++;
if (n > MAX_PARM)return USMART_PARMOVER; /* 参数太多 */
break;
case 0XFF:/* 错误 */
return USMART_PARMERR; /* 参数错误 */
}
if (*str == ')' || *str == '\0')break; /* 查到结束标志了. */
}
*parn = n; /* 记录参数的个数 */
return USMART_OK; /* 正确得到了参数 */
}
6.usmart_port.h
c
/**
****************************************************************************************************
* @file usmart_port.h
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 串口调试组件
*
* USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
* 序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)
* 、字符串、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.
* V2.1版本以后新增hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进
* 制转换,例如:
* 输入"hex 100" 会在串口调试助手上看到 HEX 0X64.
* 输入"dec 0X64" 会在串口调试助手上看到 DEC 100.
* @note
* USMART资源占用情况@MDK 3.80A@2.0版本:
* FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
* SRAM:72字节(最少的情况下)
* SRAM计算公式: SRAM=PARM_LEN+72-4 其中PARM_LEN必须大于等于4.
* 应该保证堆栈不小于100个字节.
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
*
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
*
****************************************************************************************************
*/
#ifndef __USMART_PORT_H
#define __USMART_PORT_H
#include <stdio.h>
#include "stm32f4xx.h"
/*******************************************************************************************************************************/
/* 用户配置参数 */
#define MAX_FNAME_LEN 30 /* 函数名最大长度,应该设置为不小于最长函数名的长度。 */
#define MAX_PARM 10 /* 最大为10个参数 ,修改此参数,必须修改usmart_exe与之对应. */
#define PARM_LEN 200 /* 所有参数之和的长度不超过PARM_LEN个字节,注意串口接收部分要与之对应(不小于PARM_LEN) */
#define USMART_ENTIMX_SCAN 1 /* 使用TIM的定时中断来扫描SCAN函数,如果设置为0,需要自己实现隔一段时间扫描一次scan函数.
* 注意:如果要用runtime统计功能,必须设置USMART_ENTIMX_SCAN为1!!!!
*/
#define USMART_USE_HELP 1 /* 使用帮助,该值设为0,可以节省近700个字节,但是将导致无法显示帮助信息。 */
#define USMART_USE_WRFUNS 1 /* 使用读写函数,使能这里,可以读取任何地址的值,还可以写寄存器的值. */
#define USMART_PRINTF printf /* 定义printf输出 */
/*******************************************************************************************************************************/
/* 引脚 和 串口 定义
* 默认是针对USART1的.
* 注意: 通过修改这12个宏定义,可以支持USART1~UART7任意一个串口.
*/
#define USART_TX_GPIO_PORT GPIOA
#define USART_TX_GPIO_PIN GPIO_PIN_9
#define USART_TX_GPIO_AF GPIO_AF7_USART1
#define USART_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* 发送引脚时钟使能 */
#define USART_RX_GPIO_PORT GPIOA
#define USART_RX_GPIO_PIN GPIO_PIN_10
#define USART_RX_GPIO_AF GPIO_AF7_USART1
#define USART_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* 接收引脚时钟使能 */
#define USART_UX USART1
#define USART_UX_IRQn USART1_IRQn
#define USART_UX_IRQHandler USART1_IRQHandler
#define USART_UX_CLK_ENABLE() do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0) /* USART1 时钟使能 */
#define RXBUFFERSIZE 1 /* 缓存大小 */
#define USART_EN_RX 1 /* 使能(1)/禁止(0)串口1接收 */
#define USART_REC_LEN 200 /* 定义最大接收字节数 200 */
/*******************************************************************************************************************************/
/* USMART定时器 定义 */
# if USMART_ENTIMX_SCAN == 1 /* 开启了使能定时器扫描,则需要如下定义 */
/* TIMX 中断定义
* 用于定时调用usmart.scan函数扫描串口数据,并执行相关操作
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM17任意一个定时器.
*/
#define USMART_TIMX TIM4
#define USMART_TIMX_IRQn TIM4_IRQn
#define USMART_TIMX_IRQHandler TIM4_IRQHandler
#define USMART_TIMX_CLK_ENABLE() do{ __HAL_RCC_TIM4_CLK_ENABLE(); }while(0) /* TIMX 时钟使能 */
#endif
/*******************************************************************************************************************************/
/* 如果没有定义uint32_t,则定义 */
#define TYPE_EN 0
#if TYPE_EN
#ifndef uint32_t
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
#endif
#endif
extern void usart_init(uint32_t baudrate); /* 串口初始化 */
extern char * usmart_get_input_string(void); /* 获取输入数据流 */
extern void usmart_timx_reset_time(void); /* 复位运行时间 */
extern uint32_t usmart_timx_get_time(void); /* 获取运行时间 */
extern void usmart_timx_init(uint16_t arr, uint16_t psc); /* 初始化定时器 */
#endif
7.usmart_port.c
c
/**
****************************************************************************************************
* @file usmart_port.c
* @author 正点原子团队(ALIENTEK)
* @version V3.5
* @date 2020-12-20
* @brief USMART 移植文件
*
* 通过修改该文件,可以方便的将USMART移植到其他工程
* 当:USMART_ENTIMX_SCAN == 0时,仅需要实现: usmart_get_input_string函数.
* 当:USMART_ENTIMX_SCAN == 1时,需要多实现4个函数:
* usmart_timx_reset_time
* usmart_timx_get_time
* usmart_timx_init
* USMART_TIMX_IRQHandler
*
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
*
* V3.4之前版本详细修改说明见USMART文件夹下的:readme.txt
*
* V3.4 20200324
* 1, 新增usmart_port.c和usmart_port.h,用于管理USMART的移植,方便修改
* 2, 修改变量命名方式为: uint8_t, uint16_t, uint32_t
* 3, 修改usmart_reset_runtime为usmart_timx_reset_time
* 4, 修改usmart_get_runtime为usmart_timx_get_time
* 5, 修改usmart_scan函数实现方式,改成由usmart_get_input_string获取数据流
* 6, 修改printf函数为USMART_PRINTF宏定义
* 7, 修改定时扫描相关函数,改用宏定义方式,方便移植
*
* V3.5 20201220
* 1,修改部分代码以支持AC6编译器
*
****************************************************************************************************
*/
#include "usmart.h"
#include "usmart_port.h"
/******************************************************************************************************/
/* 接收状态
* bit15, 接收完成标志
* bit14, 接收到0x0d
* bit13~0, 接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;
UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
TIM_HandleTypeDef g_timx_usmart_handle; /* 定时器句柄 */
uint8_t g_rx_buffer[RXBUFFERSIZE]; /* HAL库使用的串口接收缓冲 */
uint8_t g_usart_rx_buf[USART_REC_LEN]; /* 接收缓冲, 最大USART_REC_LEN个字节. */
/**
* @brief 串口1中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
#if SYS_SUPPORT_OS /* 使用OS */
OSIntEnter();
#endif
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
#if SYS_SUPPORT_OS /* 使用OS */
OSIntExit();
#endif
}
/**
* @brief Rx传输回调函数
* @param huart: UART句柄类型指针
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART_UX) /* 如果是串口1 */
{
if((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */
{
if(g_usart_rx_sta & 0x4000) /* 接收到了0x0d */
{
if(g_rx_buffer[0] != 0x0a)
{
g_usart_rx_sta = 0; /* 接收错误,重新开始 */
}
else
{
g_usart_rx_sta |= 0x8000; /* 接收完成了 */
}
}
else /* 还没收到0X0D */
{
if(g_rx_buffer[0] == 0x0d)
{
g_usart_rx_sta |= 0x4000;
}
else
{
g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0] ;
g_usart_rx_sta++;
if(g_usart_rx_sta > (USART_REC_LEN - 1))
{
g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */
}
}
}
}
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if(huart->Instance == USART_UX) /* 如果是串口1,进行串口1 MSP初始化 */
{
USART_UX_CLK_ENABLE(); /* USART1 时钟使能 */
USART_TX_GPIO_CLK_ENABLE(); /* 发送引脚时钟使能 */
USART_RX_GPIO_CLK_ENABLE(); /* 接收引脚时钟使能 */
gpio_init_struct.Pin = USART_TX_GPIO_PIN; /* TX引脚 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = USART_TX_GPIO_AF; /* 复用为USART1 */
HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct); /* 初始化发送引脚 */
gpio_init_struct.Pin = USART_RX_GPIO_PIN; /* RX引脚 */
gpio_init_struct.Alternate = USART_RX_GPIO_AF; /* 复用为USART1 */
HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct); /* 初始化接收引脚 */
#if USART_EN_RX
HAL_NVIC_EnableIRQ(USART_UX_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3); /* 抢占优先级3,子优先级3 */
#endif
}
}
/**
* @brief 串口X初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
* 这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
* @retval 无
*/
void usart_init(uint32_t baudrate)
{
g_uart1_handle.Instance = USART_UX; /* USART1 */
g_uart1_handle.Init.BaudRate = baudrate; /* 波特率 */
g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
g_uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
g_uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
g_uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&g_uart1_handle); /* HAL_UART_Init()会使能UART1 */
/* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
/**
* @brief 获取输入数据流(字符串)
* @note USMART通过解析该函数返回的字符串以获取函数名及参数等信息
* @param 无
* @retval
* @arg 0, 没有接收到数据
* @arg 其他,数据流首地址(不能是0)
*/
char *usmart_get_input_string(void)
{
uint8_t len;
char *pbuf = 0;
if (g_usart_rx_sta & 0x8000) /* 串口接收完成? */
{
len = g_usart_rx_sta & 0x3fff; /* 得到此次接收到的数据长度 */
g_usart_rx_buf[len] = '\0'; /* 在末尾加入结束符. */
pbuf = (char*)g_usart_rx_buf;
g_usart_rx_sta = 0; /* 开启下一次接收 */
}
return pbuf;
}
/******************************************************************************************************/
/******************************************************************************************************/
/* 如果使能了定时器扫描, 则需要定义如下函数 */
#if USMART_ENTIMX_SCAN == 1
/**
* 移植注意:本例是以stm32为例,如果要移植到其他mcu,请做相应修改.
*
* usmart_reset_runtime,清除函数运行时间,连同定时器的计数寄存器以及标志位一起清零.并设置重装载值为最大,以最
* 大限度的延长计时时间.
*
* usmart_get_runtime,获取函数运行时间,通过读取CNT值获取,由于usmart是通过中断调用的函数,所以定时器中断不再
* 有效,此时最大限度只能统计2次CNT的值,也就是清零后+溢出一次,当溢出超过2次,没法处理,所以最大延时,控制在:2*计
* 数器CNT*0.1ms.对STM32来说,是:13.1s左右
*
* 其他的:USMART_TIMX_IRQHandler和Timer4_Init,需要根据MCU特点自行修改.确保计数器计数频率为:10Khz即可.另外,
* 定时器不要开启自动重装载功能!!
*/
/**
* @brief 复位runtime
* @note 需要根据所移植到的MCU的定时器参数进行修改
* @param 无
* @retval 无
*/
void usmart_timx_reset_time(void)
{
__HAL_TIM_CLEAR_FLAG(&g_timx_usmart_handle, TIM_FLAG_UPDATE); /* 清除中断标志位 */
__HAL_TIM_SET_AUTORELOAD(&g_timx_usmart_handle, 0XFFFF); /* 将重装载值设置到最大 */
__HAL_TIM_SET_COUNTER(&g_timx_usmart_handle, 0); /* 清空定时器的CNT */
usmart_dev.runtime = 0;
}
/**
* @brief 获得runtime时间
* @note 需要根据所移植到的MCU的定时器参数进行修改
* @param 无
* @retval 执行时间,单位:0.1ms,最大延时时间为定时器CNT值的2倍*0.1ms
*/
uint32_t usmart_timx_get_time(void)
{
if (__HAL_TIM_GET_FLAG(&g_timx_usmart_handle, TIM_FLAG_UPDATE) == SET) /* 在运行期间,产生了定时器溢出 */
{
usmart_dev.runtime += 0XFFFF;
}
usmart_dev.runtime += __HAL_TIM_GET_COUNTER(&g_timx_usmart_handle);
return usmart_dev.runtime; /* 返回计数值 */
}
/**
* @brief 定时器初始化函数
* @param arr:自动重装载值
* psc:定时器分频系数
* @retval 无
*/
void usmart_timx_init(uint16_t arr, uint16_t psc)
{
USMART_TIMX_CLK_ENABLE();
g_timx_usmart_handle.Instance = USMART_TIMX; /* 通用定时器4 */
g_timx_usmart_handle.Init.Prescaler = psc; /* 分频系数 */
g_timx_usmart_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
g_timx_usmart_handle.Init.Period = arr; /* 自动装载值 */
g_timx_usmart_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&g_timx_usmart_handle);
HAL_TIM_Base_Start_IT(&g_timx_usmart_handle); /* 使能定时器和定时器中断 */
HAL_NVIC_SetPriority(USMART_TIMX_IRQn,3,3); /* 设置中断优先级,抢占优先级3,子优先级3 */
HAL_NVIC_EnableIRQ(USMART_TIMX_IRQn); /* 开启ITM中断 */
}
/**
* @brief USMART定时器中断服务函数
* @param 无
* @retval 无
*/
void USMART_TIMX_IRQHandler(void)
{
if(__HAL_TIM_GET_IT_SOURCE(&g_timx_usmart_handle,TIM_IT_UPDATE)==SET) /* 溢出中断 */
{
usmart_dev.scan(); /* 执行usmart扫描 */
__HAL_TIM_SET_COUNTER(&g_timx_usmart_handle, 0);; /* 清空定时器的CNT */
__HAL_TIM_SET_AUTORELOAD(&g_timx_usmart_handle, 100); /* 恢复原来的设置 */
}
__HAL_TIM_CLEAR_IT(&g_timx_usmart_handle, TIM_IT_UPDATE); /* 清除中断标志位 */
}
/******************************************************************************************************/
#endif
8.usmart_config.c
c
#include "usmart.h"
#include "usmart_str.h"
/******************************************************************************************/
/* 用户配置区
* 这下面要包含所用到的函数所申明的头文件(用户自己添加)
*/
/* 函数名列表初始化(用户自己添加)
* 用户直接在这里输入要执行的函数名及其查找串
*/
struct _m_usmart_nametab usmart_nametab[] =
{
#if USMART_USE_WRFUNS == 1 /* 如果使能了读写操作 */
{(void *)read_addr, "uint32_t read_addr(uint32_t addr)"},
{(void *)write_addr, "void write_addr(uint32_t addr, uint32_t val)"},
#endif
};
/******************************************************************************************/
/* 函数控制管理器初始化
* 得到各个受控函数的名字
* 得到函数总数量
*/
struct _m_usmart_dev usmart_dev =
{
usmart_nametab,
usmart_init,
usmart_cmd_rec,
usmart_exe,
usmart_scan,
sizeof(usmart_nametab) / sizeof(struct _m_usmart_nametab), /* 函数数量 */
0, /* 参数数量 */
0, /* 函数ID */
1, /* 参数显示类型,0,10进制;1,16进制 */
0, /* 参数类型.bitx:,0,数字;1,字符串 */
{}, /* 每个参数的长度暂存表,需要MAX_PARM个0初始化 */
{}, /* 函数的参数,需要PARM_LEN个0初始化 */
0, /* runtimeflag */
0 /* runtime */
};
五、USAMRT使用

USMART是一个串口调试组件,通过串口与嵌入式系统 进行交互。以下是USMART的使用说明:
1.输入函数名和参数:
- 按照程序编写格式输入函数名及参数,并以回车键结束。
- 例如:function_name arg1 arg2 ...
2.系统命令:
- ? 或 help: 获取帮助信息。
- list: 获取可用的函数列表。
- id: 获取可用函数的ID列表。
- hex: 将参数以16进制显示,后跟空格+数字即执行进制转换。
- dec: 将参数以10进制显示,后跟空格+数字即执行进制转换。
- runtime 1: 开启函数运行计时。
- runtime 0: 关闭函数运行计时。
3.示例:
- 输入 ? 或 help 可以获取帮助信息。
- 输入 list 可以查看可用的函数列表。
- 输入 function_name arg1 arg2 ... 执行特定函数,并根据需要输入参数。
- 输入 hex value 或 dec value 可以将值转换为16进制或10进制。
- 输入 runtime 1 开启函数运行计时,runtime 0 则关闭。

六、总结
