USMART调试组件学习日记
写于2024/9/24日晚
文章目录
- USMART调试组件学习日记
- 1. 简介
- 2. 调试组件组成
- 3.程序流程图
- 4. 移植解析
- 5. 实验效果
- 5. 实验效果
1. 简介
USMART 是由正点原子开发的一个灵巧的串口调试互交组件,通过它你可以通过串口助手调用程序里面的任何函数,并执行。因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)、字符串、函数入口地址等作为参数),单个函数最多支持 10 个输入参数,并支持函数返回值显示。
实现效果
USMART 的特点如下:
-
可以调用绝大部分用户直接编写的函数。
-
资源占用极少(最少情况:FLASH:4K;SRAM:72B)。
-
支持参数类型多(数字(包含 10/16 进制,支持负数)、字符串、函数指针等)。
-
支持函数返回值显示。
-
支持参数及返回值格式设置。
-
支持函数执行时间计算(V3.1 及以后的版本新特性)。
-
使用方便。
2. 调试组件组成
USMART 文件 | 说明 |
---|---|
usmart.c | USMART 核心文件,用于处理命令以及对外交互 |
usmart.h | USMART 核心文件头文件,定义结构体,宏定义、函数声明等 |
usmart_str.c | USMART 字符串处理文件,用于字符串转换、参数获取等 |
usmart_str.h | USMART 字符串处理头文件,用于函数声明 |
usmart_port.c | USMART 移植文件,用于 USMART 移植 |
usmart_port.h | USMART 移植头文件,定义用户配置参数、宏定义、函数声明等 |
usmart_config.c | USMART 函数管理文件,用于添加用户需要 USMART 管理的函数 |
3.程序流程图
我们拿LED0翻转函数进行测试USMART
4. 移植解析
USMART 的移植非常简单,我们只需要修改usmart_port.c 里面的 5 个函数即可完成移植。至于usmart.c等核心代码不用详细解析。
/**
* @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;
}
该函数通过 SYSTEM 文件夹默认的串口接收来实现输入数据流获取。SYSTEM 文件夹里面的串口接收函数,最大可以一次接收 200 字节,用于从串口接收函数名和参数等。大家如果在其他平台移植,请参考 SYSTEM 文件夹串口接收的实现方式进行移植
第二个是 usmart_timx_init 函数,该函数的实现代码如下:
/**
* @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); /* 开启 TIM 中断 */
}
该函数只有在:USMART_ENTIMX_SCAN 值为 1 时,才需要实现,用于定时器初始化,利用定时器完成对 usmart_scan 函数的周期性调用,并实现函数运行时间计时(runtime)功能。
第三和第四个函数仅用于服务 USMART 的函数执行时间统计功能(串口指令:runtime 1),分别是:usmart_timx_reset_time 和 usmart_timx_get_time,这两个函数代码如下:
/**
* @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; /* 返回计数值 */
}
usmart_timx_reset_time 函数在每次 USMART 调用函数之前执行,清除定时器的计数器,然后在函数执行完之后,调用 usmart_timx_get_time 获取计数器值,从而得到整个函数的运行时间。由于 usmart 调用的函数,都是在中断里面执行的,所以我们不太方便再用定时器的中断功能来实现定时器溢出统计,因此,USMART 的函数执行时间统计功能,最多可以统计定时器溢出 1 次的时间,对 STM32F103 的 TIM4 来说,该定时器是 16 位的,最大计数是 65535,而由于我们定时器设置的是 0.1ms 一个计时周期(10Khz),所以最长计时时间是:65535 * 2 * 0.1ms = 13.1 秒
也就是说,如果函数执行时间超过 13.1 秒,那么计时将不准确。
最后一个是 USMART_TIMX_IRQHandler 函数,该函数的实现代码如下:
/**
* @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); /* 清除中断标志位 */
}
该函数是定时器 TIMX 的中断服务函数,也是一个宏定义函数,同样是在 usmart_port.h 里面定义,方便大家修改。该函数主要用于周期性调用 usmart 扫描函数(实际函数:usmart_scan),完成对输入数据流的处理。同时,清除定时器的 CNT 值,并设置自动重装载值。完成这几个函数的移植,就可以使用 USMART 了。不过,需要注意的是,usmart 同外部的互交,一般是通过 usmart_dev 结构体实现,所以 usmart_init 和 usmart_scan 的调用分别是通过:usmart_dev.init 和 usmart_dev.scan 实现的。
/* 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 */
};
此外我们还需要在 usmart_config.c 文件里面添加想要被 USMART 调用的函数。打开usmart_config.c 文件
#include "./USMART/usmart.h"
#include "./USMART/usmart_str.h"/******************************************************************************************/
/* 用户配置区* 这下面要包含所用到的函数所申明的头文件(用户自己添加)*/#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
//#include "./BSP/LCD/lcd.h"extern void led_set(uint8_t sta);
extern void test_fun(void(*ledset)(uint8_t), uint8_t sta);/* 函数名列表初始化(用户自己添加)* 用户直接在这里输入要执行的函数名及其查找串*/
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(void *)delay_ms, "void delay_ms(uint16_t nms)",(void *)delay_us, "void delay_us(uint32_t nus)",(void *)led_set, "void led_set(uint8_t sta)",};/******************************************************************************************/
/* 函数控制管理器初始化* 得到各个受控函数的名字* 得到函数总数量*/
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,字符串 */0, /* 每个参数的长度暂存表,需要MAX_PARM个0初始化 */0, /* 函数的参数,需要PARM_LEN个0初始化 */
};
这里的添加函数很简单,只要把函数所在头文件添加进来,并把函数名添加到usmart_nametab[]中
主程序初始化
/* LED状态设置函数 */
void led_set(uint8_t sta)
{LED1(sta);
}/* 函数参数调用测试函数 */
void test_fun(void(*ledset)(uint8_t), uint8_t sta)
{ledset(sta);
}int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */usmart_dev.init(72); /* 初始化USMART */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "USMART TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);while (1){LED0_TOGGLE(); /* LED0(RED) 闪烁 */delay_ms(500);}
}
usmart_dev.init(72)通过此函数进行USMART初始化,并usart_init(115200)初始化串口
5. 实验效果
发送led_set(0x0),led亮起,实验完成。
,并usart_init(115200)初始化串口
5. 实验效果
[外链图片转存中…(img-B8ACaJDK-1727183958931)]
[外链图片转存中…(img-HEwCgvSW-1727183958931)]
发送led_set(0x0),led亮起,实验完成。