USMART调试组件学习

USMART调试组件学习日记

写于2024/9/24日晚

1. 简介

USMART 是由正点原子开发的一个灵巧的串口调试互交组件,通过它你可以通过串口助手调用程序里面的任何函数,并执行。因此,你可以随意更改函数的输入参数(支持数字(10/16进制,支持负数)、字符串、函数入口地址等作为参数),单个函数最多支持 10 个输入参数,并支持函数返回值显示。

实现效果

image-20240924204852707

USMART 的特点如下:

  1. 可以调用绝大部分用户直接编写的函数。

  2. 资源占用极少(最少情况:FLASH:4K;SRAM:72B)。

  3. 支持参数类型多(数字(包含 10/16 进制,支持负数)、字符串、函数指针等)。

  4. 支持函数返回值显示。

  5. 支持参数及返回值格式设置。

  6. 支持函数执行时间计算(V3.1 及以后的版本新特性)。

  7. 使用方便。

2. 调试组件组成

USMART 文件说明
usmart.cUSMART 核心文件,用于处理命令以及对外交互
usmart.hUSMART 核心文件头文件,定义结构体,宏定义、函数声明等
usmart_str.cUSMART 字符串处理文件,用于字符串转换、参数获取等
usmart_str.hUSMART 字符串处理头文件,用于函数声明
usmart_port.cUSMART 移植文件,用于 USMART 移植
usmart_port.hUSMART 移植头文件,定义用户配置参数、宏定义、函数声明等
usmart_config.cUSMART 函数管理文件,用于添加用户需要 USMART 管理的函数

3.程序流程图

image-20240924205619957

我们拿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. 实验效果

微信图片_20240924211408

image-20240924211451299

image-20240924211504079

发送led_set(0x0),led亮起,实验完成。

,并usart_init(115200)初始化串口

5. 实验效果

微信图片_20240924211408

[外链图片转存中…(img-B8ACaJDK-1727183958931)]

[外链图片转存中…(img-HEwCgvSW-1727183958931)]

发送led_set(0x0),led亮起,实验完成。

微信图片_20240924211624

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值