55、软件追踪技术:从 QS 到 QP - nano 的深度解析

软件追踪技术:从 QS 到 QP - nano 的深度解析

在软件开发过程中,尤其是嵌入式系统开发,测试和调试往往占据了大量的时间和精力。软件追踪技术为我们提供了一种在不停止应用程序的情况下获取诊断信息的有效方法,特别是与事件驱动范式结合时,它能让我们深入了解系统的运行情况。下面将详细介绍相关的技术和应用。

1. 平台特定的 QS 回调函数

对于 QK/DOS 平台,QS 追踪使用标准的 16550 UART(COM1 - COM4)以 115200 波特率进行数据输出。以下是相关的回调函数代码:

#include "qp_port.h"
#include "dpp.h"
#include "bsp.h"
. . .
/* Local-scope objects --------------------------------------------------*/
#ifdef Q_SPY
static uint16_t l_uart_base;
/* QS data uplink UART base address */
. . .
#define UART_16550_TXFIFO_DEPTH 16
#endif
. . .
/*......................................................................*/
void BSP_init(int argc, char *argv[]) {
    char const *com = "COM1";
    uint8_t n;
    if (argc > 1) {
        l_delay = atol(argv[1]);
        /* set the delay counter for busy delay */
    }
    if (argc > 2) {
        com = argv[2];
        (void)com;
        /* avoid compiler warning if Q_SPY not defined */
    }
    if (!QS_INIT(com)) {
        /* initialize QS */
        Q_ERROR();
    }
  . . .
}
/*......................................................................*/
void QK_onIdle(void) {
    #ifdef Q_SPY
    if ((inportb(l_uart_base + 5) & (1 << 5)) != 0) {
        /* Tx FIFO empty? */
        uint16_t fifo = UART_16550_TXFIFO_DEPTH;
        /* 16550 Tx FIFO depth */
        uint8_t const *block;
        QF_INT_LOCK(dummy);
        block = QS_getBlock(&fifo);
        /* try to get next block to transmit */
        QF_INT_UNLOCK(dummy);
        while (fifo-- != 0) {
            /* any bytes in the block? */
            outportb(l_uart_base + 0, *block++);
        }
    }
    #endif
}
. . .
/*----------------------------------------------------------------------*/
#ifdef Q_SPY
/* define QS callbacks */
/*.....................................................................*/
static uint8_t UART_config(char const *comName, uint32_t baud) {
    switch (comName[3]) {
        /* Set the base address of the COMx port */
        case ’1’: l_uart_base = (uint16_t)0x03F8; break;
        /* COM1 */
        case ’2’: l_uart_base = (uint16_t)0x02F8; break;
        /* COM2 */
        case ’3’: l_uart_base = (uint16_t)0x03E8; break;
        /* COM3 */
        case ’4’: l_uart_base = (uint16_t)0x02E8; break;
        /* COM4 */
        default: return (uint8_t)0;
        /* COM port out of range failure */
    }
    baud = (uint16_t)(115200UL / baud);
    /* divisor for baud rate */
    outportb(l_uart_base + 3, (1 << 7));
    /* Set divisor access bit (DLAB) */
    outportb(l_uart_base + 0, (uint8_t)baud);
    /* Load divisor */
    outportb(l_uart_base + 1, (uint8_t)(baud >> 8));
    outportb(l_uart_base + 3, (1 << 1) | (1 << 0)); /* LCR:8-bits,no p,1stop */
    outportb(l_uart_base + 4, (1 << 3) | (1 << 1) | (1 << 0)); /*DTR,RTS,Out2*/
    outportb(l_uart_base + 1, 0);
    /* Put UART into the polling FIFO mode */
    outportb(l_uart_base + 2, (1 << 2) | (1 << 0)); /* FCR: enable, TX clear */
    return (uint8_t)1;
    /* success */
}
/*.....................................................................*/
uint8_t QS_onStartup(void const *arg) {
    static uint8_t qsBuf[2*1024];
    /* buffer for Quantum Spy */
    QS_initBuf(qsBuf, sizeof(qsBuf));
    return UART_config((char const *)arg, 115200UL);
}
/*.....................................................................*/
void QS_onCleanup(void) {
}
/*.....................................................................*/
void QS_onFlush(void) {
    uint16_t fifo = UART_16550_TXFIFO_DEPTH;
    /* 16550 Tx FIFO depth */
    uint8_t const *block;
    QF_INT_LOCK(dummy);
    while ((block = QS_getBlock(&fifo)) != (uint8_t *)0) {
        QF_INT_UNLOCK(dummy);
        /* busy-wait until TX FIFO empty */
        while ((inportb(l_uart_base + 5) & (1 << 5)) == 0) {
        }
        while (fifo-- != 0) {
            /* any bytes in the block? */
            outportb(l_uart_base + 0, *block++);
        }
        fifo = UART_16550_TXFIFO_DEPTH;
        /* re-load 16550 Tx FIFO depth */
        QF_INT_LOCK(dummy);
    }
    QF_INT_UNLOCK(dummy);
}
/*.....................................................................*/
QSTimeCtr QS_onGetTime(void) { 
    /* see Listing 11.18 */
  . . .
}
#endif
/* Q_SPY */

这些回调函数的作用如下:
- BSP_init :初始化 QS 组件,如果初始化失败会调用 Q_ERROR
- QK_onIdle :在空闲循环中执行,用于输出追踪数据。
- UART_config :配置 UART 端口。
- QS_onStartup :初始化 QS 组件并配置 UART。
- QS_onCleanup :进行 QS 的清理工作,此处为空。
- QS_onFlush :将整个追踪缓冲区的数据刷新到主机。
- QS_onGetTime :提供 QS 追踪记录的时间戳。

2. 生成 QS 时间戳

大多数 QS 追踪记录都带有时间戳,将所有记录关联到一个共同的时间轴上。在标准 PC 中,可以从 8284 定时器/计数器获取时间。以下是相关代码:

/* Local-scope objects --------------------------------------------------*/
#ifdef Q_SPY
static QSTimeCtr l_tickTime;
/* keeps timestamp at tick */
static uint32_t l_lastTime;
/* last timestamp */
#endif
. . .
void interrupt ISR_tmr(void) {
    uint8_t pin;
    #ifdef Q_SPY
    l_tickTime += 0x10000;
    /* add 16-bit rollover */
    #endif
    QK_ISR_ENTRY(pin, TMR_ISR_PRIO);
    /* inform QK about entering the ISR */
    QF_tick();
    /* call QF_tick() outside of critical section */
    QK_ISR_EXIT(pin);
    /* inform QK about exiting the ISR */
}
/*.....................................................................*/
#ifdef Q_SPY
/* define QS callbacks */
. . .
QSTimeCtr QS_onGetTime(void) {
    /* invoked with interrupts locked */
    uint32_t now;
    uint16_t count16;
    /* 16-bit count from the 8254 */
    if (l_tickTime != 0) {
        /* time tick has started? */
        outportb(0x43, 0);
        /* latch the 8254’s counter-0 count */
        count16 = (uint16_t)inportb(0x40);/* read the low byte of counter-0 */
        count16 += ((uint16_t)inportb(0x40) << 8);
        /* add on the hi byte */
        now = l_tickTime + (0x10000 - count16);
        if (l_lastTime > now) {
            /* are we going "back" in time? */
            now += 0x10000;
            /* assume that there was one rollover */
        }
        l_lastTime = now;
    }
    else {
        now = 0;
    }
    return (QSTimeCtr)now;
}
#endif
/* Q_SPY */

时间戳生成的步骤如下:
1. 系统时钟滴答中断会将 l_tickTime 增加 0x10000 以处理 16 位计数器的溢出。
2. QS_onGetTime 函数在临界区被调用,获取当前时间戳。
3. 读取 8254 计数器的值并计算完整的 32 位时间戳。
4. 检查时间是否“倒流”,如果是则进行修正。

3. 从活动对象生成 QS 字典记录

全局层面的字典记录只能提供有限的符号信息,而封装的应用组件(如 Philosopher 和 Table 活动对象)也需要在追踪中提供符号信息。以下是相关代码:

// Listing 11.19 Generating dictionary trace records from the Table active object
static Table l_table;
/* the single instance of the Table active object */
. . .
QState Table_initial(Table *me, QEvent const *e) {
    (void)e;
    /* suppress the compiler warning about unused parameter */
    QS_OBJ_DICTIONARY(&l_table);
    QS_FUN_DICTIONARY(&QHsm_top);
    QS_FUN_DICTIONARY(&Table_initial);
    QS_FUN_DICTIONARY(&Table_serving);
    QS_SIG_DICTIONARY(DONE_SIG, 0);
    /* global signals */
    QS_SIG_DICTIONARY(EAT_SIG, 0);
    QS_SIG_DICTIONARY(TERMINATE_SIG, 0);
    QS_SIG_DICTIONARY(HUNGRY_SIG, me);
    /* signal just for Table */
    /* signal HUNGRY_SIG is posted directly */
    QActive_subscribe((QActive *)me, DONE_SIG);
    QActive_subscribe((QActive *)me, TERMINATE_SIG);
    return Q_TRAN(&Table_serving);
}

// Listing 11.20 Generating dictionary trace records from the Philosopher active objects
static Philo l_philo[N_PHILO];
/* storage for all Philos */
. . .
QState Philo_initial(Philo *me, QEvent const *e) {
    static uint8_t registered;
    /* starts off with 0, per C-standard */
    if (!registered) {
        registered = (uint8_t)1;
        QS_OBJ_DICTIONARY(&l_philo[0]);
        QS_OBJ_DICTIONARY(&l_philo[0].timeEvt);
        QS_OBJ_DICTIONARY(&l_philo[1]);
        QS_OBJ_DICTIONARY(&l_philo[1].timeEvt);
        QS_OBJ_DICTIONARY(&l_philo[2]);
        QS_OBJ_DICTIONARY(&l_philo[2].timeEvt);
        QS_OBJ_DICTIONARY(&l_philo[3]);
        QS_OBJ_DICTIONARY(&l_philo[3].timeEvt);
        QS_OBJ_DICTIONARY(&l_philo[4]);
        QS_OBJ_DICTIONARY(&l_philo[4].timeEvt);
        QS_FUN_DICTIONARY(&Philo_initial);
        QS_FUN_DICTIONARY(&Philo_thinking);
        QS_FUN_DICTIONARY(&Philo_hungry);
        QS_FUN_DICTIONARY(&Philo_eating);
        randomize();
        /* initialize the random number generator just once */
    }
    QS_SIG_DICTIONARY(HUNGRY_SIG, me);
    /* signal for each Philos */
    QS_SIG_DICTIONARY(TIMEOUT_SIG, me);
    /* signal for each Philos */
    QActive_subscribe((QActive *)me, EAT_SIG);
    return Q_TRAN(&Philo_thinking);
    /* top-most initial transition */
}

生成字典记录的要点如下:
- 对于 Table 活动对象,在 Table_initial 函数中生成对象、函数和信号的字典记录。
- 对于 Philosopher 活动对象,在 Philo_initial 函数中确保某些代码只执行一次,生成对象、函数和信号的字典记录。

4. 添加应用特定的追踪记录

QS 允许在追踪中插入应用特定的记录。以下是一个示例:

#ifdef Q_SPY
. . .
enum AppRecords {
    /* application-specific trace records */
    PHILO_STAT = QS_USER
};
#endif
. . .
void BSP_displyPhilStat(uint8_t n, char const *stat) {
    Video_printStrAt(17, 12 + n, VIDEO_FGND_YELLOW, stat);
    QS_BEGIN(PHILO_STAT, AO_Philo[n]) /* application-specific record begin */
    QS_U8(1, n);
    /* Philosopher number */
    QS_STR(stat);
    /* Philosopher status */
    QS_END()
}

添加应用特定追踪记录的步骤如下:
1. 枚举应用特定的追踪记录类型。
2. 使用 QS_BEGIN 开始记录,指定记录类型和对象指针。
3. 使用 QS_U8 QS_STR 输出数据。
4. 使用 QS_END 结束记录。

5. QSPY 参考手册

可以从网站获取“QSPY 参考手册”,它包含命令行选项、人类可读格式以及 qspy.m 脚本生成的所有 MATLAB 矩阵的描述。该手册是“QP 参考手册”的一部分,有 HTML 和 CHM - Help 两种格式。

6. QP - nano:极致精简的事件驱动架构

QP - nano 是为低端 8 位和 16 位单芯片微控制器设计的精简版事件驱动基础设施。与传统的 QP 应用相比,QP - nano 具有极小的内存占用,一个最小的 QP - nano 应用只需 100 字节的 RAM 和 2KB 的 ROM。这使得它非常适合高产量、对成本敏感的事件驱动应用,如电机控制、照明控制、电容式触摸感应等。同时,由于事件驱动范式的特性,QP - nano 还适用于超低功耗应用,如无线传感器网络和植入式医疗设备。

综上所述,软件追踪技术和 QP - nano 为嵌入式系统开发提供了强大的工具和解决方案,能帮助开发者更高效地开发和调试应用程序。

软件追踪技术:从 QS 到 QP - nano 的深度解析

7. 软件追踪技术与 QP - nano 的优势总结

软件追踪技术在嵌入式系统开发中具有显著优势,尤其是与事件驱动范式结合时。以下是其主要优势总结:
|优势|描述|
| ---- | ---- |
|实时诊断|无需停止应用程序即可获取系统的诊断信息,能在系统运行过程中及时发现问题。|
|事件驱动洞察|由于事件驱动架构中所有重要系统交互都通过事件驱动基础设施,软件追踪可以深入了解系统的运行情况。|
|问题发现与解决|帮助发现和记录由并发组件之间微妙交互引起的难以捉摸的间歇性错误,便于进行针对性的调试。|
|测试与验证|支持可重复的单元和集成测试,确保系统长时间可靠运行并获得最佳处理器性能。|

QP - nano 作为精简版的事件驱动基础设施,也有其独特的优势:
|优势|描述|
| ---- | ---- |
|极小的内存占用|只需 100 字节的 RAM 和 2KB 的 ROM,适合资源受限的系统。|
|成本效益高|对于高产量、对成本敏感的应用,QP - nano 能有效降低成本。|
|低功耗特性|事件驱动范式可使 CPU 在不处理事件时进入低功耗睡眠模式,适合超低功耗应用。|

8. 应用场景分析

软件追踪技术和 QP - nano 在多个领域都有广泛的应用,以下是一些具体的应用场景:
- 工业控制 :在电机控制、照明控制等工业应用中,软件追踪技术可以实时监测系统的运行状态,及时发现故障并进行修复。QP - nano 由于其小内存占用和低功耗特性,适合用于工业现场的小型控制器。
- 消费电子 :如电容式触摸感应、小型家电等产品,QP - nano 可以在有限的资源下实现复杂的功能。软件追踪技术则有助于优化产品的性能和稳定性。
- 医疗设备 :对于植入式医疗设备和无线传感器网络,QP - nano 的低功耗特性至关重要。软件追踪技术可以确保设备的可靠性和安全性。

9. 技术实现流程总结

为了更清晰地展示软件追踪技术和 QP - nano 的实现过程,下面给出一个 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(初始化 QS 组件):::process
    B --> C{QS 初始化是否成功?}:::decision
    C -->|是| D(系统运行):::process
    C -->|否| E(调用 Q_ERROR):::process
    D --> F(生成 QS 时间戳):::process
    F --> G(生成 QS 字典记录):::process
    G --> H(添加应用特定追踪记录):::process
    H --> I(使用 QSPY 进行追踪分析):::process
    I --> J{是否使用 QP - nano?}:::decision
    J -->|是| K(部署 QP - nano 应用):::process
    J -->|否| L(继续使用传统 QP 应用):::process
    K --> M([结束]):::startend
    L --> M
10. 未来发展趋势

随着嵌入式系统的不断发展,软件追踪技术和 QP - nano 也将面临新的挑战和机遇。以下是一些可能的发展趋势:
- 更高的性能要求 :随着系统复杂度的增加,对软件追踪技术的性能要求也会提高,例如更高的采样率和更精确的时间戳。
- 更广泛的应用领域 :QP - nano 可能会在更多领域得到应用,如物联网、智能家居等。
- 与其他技术的融合 :软件追踪技术可能会与人工智能、机器学习等技术融合,实现更智能的故障诊断和预测。

总之,软件追踪技术和 QP - nano 为嵌入式系统开发提供了强大的支持,未来它们将在更多领域发挥重要作用。开发者可以根据具体的应用需求选择合适的技术和工具,提高开发效率和系统的可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值