This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP430FR5964:控制台 printf#39;s 似乎跳闸了 EUSCI 中断处理

Guru**** 2381040 points
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1169852/msp430fr5964-console-printf-s-appear-to-trip-up-eusci-interrupt-handling

器件型号:MSP430FR5964

我一直在使用嵌入式 printf 来尝试跟踪几个令人困扰的问题、但我怀疑 printf 可能会导致他们自己的某些问题! 我遇到的问题是 UART 溢出、仅 当 printf 处于活动状态时才会出现溢出。

UART 使用接收 ISR 来捕获和缓冲任何传入的流量(它的位非确定性)。 当用户请求数据时、该数据将从该缓冲区中被处理、或在数据可用之前被挂起(比这更复杂一点、但这是通用的 gist)。 由于我已经开始插入一些 printf (全部处于用户模式),我看到接收 ISR 有一些偶发的超支。  

我通过将用户数据请求分为两部分来设计这些故障-第一部分请求消息的一部分、并且一旦将此数据控制返回到用户模式、就会向控制台执行 printf。 同时、在后台、接收 ISR 仍将(或应该)接收到数据消息的其余部分、但它将立即失败并出现溢出错误。 如果我移除 printf、一切都正常。

我可以看到唯一能够解释这一点的机制是、如果 printf 本身抑制了 EUSCI (或通用)中断、以至于 UART 正在丢失数据。 这听起来是否合理、或者我是否应该调查其他一些方向? 我相信我已经阅读过一些先前的评论、这些评论指出 printf 实现使用一些 UART 功能通过 JTAG 管理流量、这可能会让人有一定的可信度、但这对我来说都是一个黑盒。

衷心地收到任何建议、想法或评论。 控制台打印功能很好、但在 UART 处于活动状态时可能不可用?

Andrew

BTW、我使用的是 GCC 工具链 CCS V11。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现在、我将其隔离了一点、我有了更多的发挥。

    首先、 将正在使用的 EUSCI 从0更改为3 -无变化。

    然后尝试改变 printf 消息的大小。 单个非\n 字符可以正常工作、但我怀疑在发送下一个\n 字符之前、这实际上不是通过 JTAG 传输的。 单个\n 就足以引起问题。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Andrew,

    您是否直接使用了库 printf 函数?

    您是否添加了一些剩余代码?

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    你(们)好,约翰逊

    最初、这种方法中有一些额外的代码、但现在我将其完全简化为原始 printf 调用。 我还对标准完全膨胀的 printf 和-mTina-printf 选项进行了讨论、虽然这些选项的运行方式不同、但我认为这只是因为微型选项中的缓冲更有限。  

    对于标准 printf、如果我在不终止输出字符串(a \n)的情况下调用此函数、则问题不会发生、大概是因为在随后发送线路终结符之前、格式化的 printf 字符串不会实际传输到控制台。 您可以在控制台输出中很清楚地看到这一点-在成功接收到所有 UART 流量后、我打印一个终端器之前不会出现任何情况。 使用微型选项构建后执行相同的逻辑、未终止的字符串会立即传输到控制台、溢出错误会被 UART 拾取。

    我在微型选项中看到的奇怪之处是、如果我只打印一 个非终结符、则不会向控制台传输任何内容。 是否有单个终端器?

    一切似乎都指向实际向控制台传输的数据以触发问题、而不是之前执行的任何格式化或缓冲 printf 。 我不确定 在 gcc 工具链中的什么位置实现了这一点、除了我想它必须是 CCS 套件的一部分、而不是纯 mspgcc。 在 TI 案例中 、看起来_TI_printfi 执行实际传输、这是由_TI_file_lock 和 unlock 进行的插槽、用于管理对 stdout 的访问。 我还没有(!) 在 GCC 工具链中找到任何等效项。

    Andrew

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    我不熟悉默认 printf 函数、但您可以尝试以下 printf 示例:

    #include <stdio.h>
    #include <string.h>
    
    #define UART_PRINTF
    
    #ifdef UART_PRINTF
    int fputc(int _c, register FILE *_fp);
    int fputs(const char *_ptr, register FILE *_fp);
    #endif
    
    Main()
    {
             ……………
    }
    
    #ifdef UART_PRINTF
    int fputc(int _c, register FILE *_fp)
    {
            while(!(UCA0IFG&UCTXIFG));
            UCA0TXBUF = (unsigned char) _c;
    
            return((unsigned char)_c);
    }
    
    int fputs(const char *_ptr, register FILE *_fp)
    {
            unsigned int i, len;
    
            len = strlen(_ptr);
    
            for(i=0 ; i<len ; i++)
            {
                    while(!(UCA0IFG&UCTXIFG));
                    UCA0TXBUF = (unsigned char) _ptr[i];
            }
    
            return len;
    }
    #endif
    

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Johnson:

    感谢您的建议。 如果我正确理解了您的意图、那么我们的想法是从 printf 下劫持默认的 PUT 和 putc、并将它们引导至不会造成任何损坏的位置。 然后、我需要 UART 外部的其他一些独立记录器来捕获和报告这些记录。 并不像通过 JTAG 将这些内容引导至控制台那样巧妙、但如果我无法实现这一目标、这肯定是一个可接受的替代方案。

    如果我在这里遗漏了一些东西、请告诉我! 我通常只将这些用作黑盒、而不会在机罩下面进行脱开。

    Andrew

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    您能为此编写代码吗?

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我已经传回了很多代码、但在接下来的几天里、我将把它减少到一个简单的东西、这将(希望!) 演示问题。  

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    在减少代码以演示此问题后、请帮助在此处上传代码、我可以在我的一侧调试您的代码。

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    最后发现了一些时间将其减少到一些简单的东西、问题仍然存在(我有一种可怕的感觉、它可能与时间更相关、更简单的代码可以正常工作!)。

    // Bit of logic preceding this to set up the MSP
    
        GPIO_setAsPeripheralModuleFunctionOutputPin (
                GPIO_PORT_P2,
                (BIT0 | BIT1),
                GPIO_SECONDARY_MODULE_FUNCTION);
    
        // Configure UART for 9600 baud
        EUSCI_A_UART_initParam param = {0};
        param.selectClockSource = EUSCI_A_UART_CLOCKSOURCE_ACLK;
        param.clockPrescalar = 3;
        param.firstModReg = 0;
        param.secondModReg = 146;
        param.parity = EUSCI_A_UART_NO_PARITY;
        param.msborLsbFirst = EUSCI_A_UART_LSB_FIRST;
        param.numberofStopBits = EUSCI_A_UART_ONE_STOP_BIT;
        param.uartMode = EUSCI_A_UART_MODE;
        param.overSampling = 0;
    
        // Configure the port
        status = EUSCI_A_UART_init(EUSCI_A0_BASE, &param);
        EUSCI_A_UART_enable(EUSCI_A0_BASE);
        EUSCI_A_UART_enableInterrupt(EUSCI_A0_BASE,
                                     EUSCI_A_UART_RECEIVE_INTERRUPT);
    
        context.count = 0;
        context.uartStatus = 0;
        loop = true;
    
        printf ("Started\n");
        while (loop) {
            // Wait for something to happen
            _low_power_mode_0 ();
            if (context.uartStatus != 0) {        // Overrun error
                printf ("Overrun!\n");
            } else if (context.count == 3) {    // First 3 bytes successfully received
                // This is the critical printf - if it's included, the message is
                // output, but the uart immediately overruns. If its commented out
                // it all works fine
                printf ("First 3 bytes received\n");
            } else {                            // 10 bytes received
                printf ("%d bytes successfully received - exiting\n", context.count);
                loop = false;
            }
        }
    
        while (1) {
            catch = context.count;      // Somewhere to breakpoint
        }        // Loop forever
    
        return (0);
    }
    
    void __attribute__ ((interrupt (EUSCI_A0_VECTOR))) EUSCI_A0_ServiceRoutine (void) {
        uint8_t nextByte;
        bool activateMainline;
    
        activateMainline = false;
        nextByte = HWREG8 (EUSCI_A0_BASE + OFS_UCAxRXBUF);
    
        // Check for any errors
        context.uartStatus = HWREG8 (EUSCI_A0_BASE + OFS_UCAxSTATW)
                             & (UCFE | UCOE | UCPE | UCBRK);
        if (context.uartStatus) {
            activateMainline = true;
        } else {
            context.count++;
            context.byte[context.count] = nextByte;
            if (context.count == 3 || context.count == 10) {
                activateMainline = true;
            }
        }
    
        if (activateMainline) {
            __low_power_mode_off_on_exit();
        }
    
        return;
    }

    我在5964上使用的是 EUSCI A0、但从之前对完整代码库的讨论来看、实际使用哪个 USCI 似乎无关紧要。 UART 配置为9600波特、MSP 为16MHz。 我没有使用同一个器件来生成输入流、因为问题可能也会影响 TX 侧、并且它抑制了这种情况、它可能永远不会在 RX 上显示溢出。 在我使用的特定板上、EUSCI A0连接到串行蓝牙模块、因此我使用它来注入输入流。

    代码非常简单。

    中断处理程序只是将传入字节收集到缓冲区(context.byte[])和正在进行的计数中。 在接收到前3个字节后、它会重新激活主线(这保证它位于传入字节流的中间)。 如果主线此时执行 printf、中断处理程序将看到的下一个问题是溢出。 如果主线不执行此 printf、则一切都正常、下一个主线激活将通知传入流的末尾(仲裁设置为10字节)。

    不过、这并不能完全运行。 如果中断处理程序检测到溢出、它还应该重新激活主线并打印溢出消息、但这不会发生! 我可以按照溢出检测操作一直到 __LOW_POWER_MODE_OFF_ON_EXIT ();,但这绝不会重新激活主线。 不知道原因、也没有尝试深入探究(我有怀疑)。

    Andrew

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    更多的幕后调查;

    首先、缺少对溢出的报告是完全有意义的-这是在 printf 仍在进行的过程中生成的、因此主线激活只会返回到该状态、完成并将 MSP 置于睡眠状态。 如果溢出的测试和报告立即完成、则问题 printf 完成、此时已检测到溢出。

    为了尝试了解幕后发生的情况、我设置了一个以连续模式运行的定时器块、并记录了每次激活 RX 处理程序时的节拍数。 9600波特率应约为每 ms 一个字节、因此、将定时器块设置为拉取 ACLK /8、这样每周期大约244us。 我预计每字节大约4到5个节拍、但我希望在执行 printf 时看到一次重大跳转、 并且 检测到溢出(任何超过8个节拍的值都应将我们推到溢出的区域)-这是基于我的期望、即 printf 以某种方式抑制了 EUSCI 中断。

    由于流的中间没有 printf、这些节拍几乎达到了预期的效果;

    4、5、4、4、5、4、4、4  -大约为1 MS/byte (按预期)

    但是、预计不会出现 printf 处于活动状态的情况。 这为前3个字节提供了以下内容、之后是溢出(我们仅测量间隔、因此前2个是1-2和2-3间隔、第三个是3-溢出)

    4、5、0

    那么、这里提供了什么! 似乎定时器已停止、或者'overoveroveroveroveroveroveroverl'立即发生 printf 开始。 计时器只是在后台维护计时器块、并且应该完全独立于 printf 可能处理的任何中断。 通过查看溢出发生时的定时器寄存器、它仍然看起来完全有效(MC 仍然被设置为连续-我不相信有任何其他的方法来停止它?)。

    下一步是仔细查看正在记录的传入数据-因为这是来自已知源和结构的数据、因此它完全是确定性的且可重复的。 即使我之前忽略了它、UART 应该已经捕获并正确缓冲了第4个字节、而溢出则由传入的第5个字节触发(RX 缓冲区已满且未读时、EUSCI 没有任何地方可以写入第5个字节、因此溢出也是如此)。

    预期的字节序列(以及由 no-printf 逻辑捕获的内容)为;

    255、2、30、1、32、 185、...

    在 printf 就位的情况下、捕获的数据是

    255、2、30、3

    更多头部刮擦。 溢出(并且已经验证了它设置的 OE 标志)并不真正有意义-我本来希望 RX 缓冲器将1缓冲。 但是、如果定时器仍然处于活动状态、并且正确的话、即使是第4个字节也没有足够的时间进行传输、更不用说第5个字节来触发溢出。 不知道缓冲3来自哪里、但它是可重复的。

    此时、我不确定我还能检查什么。 对于后续版本、我将在此处包含测试逻辑的最新变体;

    Andrew

        GPIO_setAsPeripheralModuleFunctionOutputPin (
                GPIO_PORT_P2,
                (BIT0 | BIT1),
                GPIO_SECONDARY_MODULE_FUNCTION);
    
        // Configure UART for 9600 baud
        EUSCI_A_UART_initParam param = {0};
        param.selectClockSource = EUSCI_A_UART_CLOCKSOURCE_ACLK;
        param.clockPrescalar = 3;
        param.firstModReg = 0;
        param.secondModReg = 146;
        param.parity = EUSCI_A_UART_NO_PARITY;
        param.msborLsbFirst = EUSCI_A_UART_LSB_FIRST;
        param.numberofStopBits = EUSCI_A_UART_ONE_STOP_BIT;
        param.uartMode = EUSCI_A_UART_MODE;
        param.overSampling = 0;
    
        // Configure the port using DriverLib. It returns true on failure.
        status = EUSCI_A_UART_init(EUSCI_A0_BASE, &param);
        EUSCI_A_UART_enable(EUSCI_A0_BASE);
        EUSCI_A_UART_enableInterrupt(EUSCI_A0_BASE,
                                     EUSCI_A_UART_RECEIVE_INTERRUPT);
    
        // Set up TIMERA to run continuously, at a tick interval of approx 244 uS
        Timer_A_initContinuousModeParam paramT = {0};
        paramT.clockSource = TIMER_A_CLOCKSOURCE_ACLK;
        paramT.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_8;
        paramT.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;
        paramT.timerClear = TIMER_A_DO_CLEAR;
        paramT.startTimer = true;
        Timer_A_initContinuousMode (TA0_BASE, &paramT);
        Timer_A_startCounter(TA0_BASE, TIMER_A_CONTINUOUS_MODE);
    
        context.count = 0;
        context.uartStatus = 0;
        loop = true;
    
        printf ("Started\n");
        while (loop) {
            // Wait for something to happen
            _low_power_mode_0 ();
            if (context.uartStatus != 0) {        // Overrun error
                printf ("Overrun!\n");
                loop = false;
            } else if (context.count == 3) {    // First 3 bytes successfully received
                // This is the critical printf - if it's included, the message is
                // output, but the uart immediately overruns. If its commented out
                // it all works fine
                printf ("First 3 bytes received\n");
                if (context.uartStatus != 0) {        // Overrun error while doing printf
                    printf ("Overrun!\n");
                    loop = false;
                }
            } else {                            // 10 bytes received
                printf ("%d bytes successfully received - exiting\n", context.count);
                loop = false;
            }
        }
    
        while (1) {
        }        // Loop forever
    
        return (0);
    }
    
    void __attribute__ ((interrupt (EUSCI_A0_VECTOR))) EUSCI_A0_ServiceRoutine (void) {
        uint8_t nextByte;
    
        context.bytes[context.count] = HWREG8 (EUSCI_A0_BASE + OFS_UCAxRXBUF);
        context.timerTicks[context.count] = Timer_A_getCounterValue (TA0_BASE);
    
        // Check for any errors
        context.uartStatus = HWREG16 (EUSCI_A0_BASE + OFS_UCAxSTATW)
                             & (UCFE | UCOE | UCPE | UCBRK);
        if (context.uartStatus) {
            __low_power_mode_off_on_exit();
        } else {
            context.count++;
           if (context.count == 3 || context.count == 10) {
               __low_power_mode_off_on_exit();
            }
        }
    
        return;
    }
    
    

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    让我尝试了解您的调试信息和结果。

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我在 https://43oh.com/2015/05/how-to-add-printf-support-for-the-msp432-launchpad-serial-port/上发现了以下 内容、如果属实、将有助于解释所发生的情况;

    如果您在代码中使用 printf()而没有将其设置为通过 UART 传送它,则可以在 IDE 的调试控制台上查看这些字符串。 这种工作方式是编译器/调试器在代码转换字符串完成后添加一个断点, 然后通过 JTAG 将字符串拉出存储器、并将其显示在控制台上。

    如果整个机制取决于 在从存储器中检索控制台消息时在后台执行的断点、则肯定会停止处理器时钟、这将影响我的后台计时器(将被停止)以及任何 ISR 触发的能力。 实际上、任何具有时序限制的内容都可能在执行 printf 的检索部分时受到影响。

    真遗憾、我希望这个 printf 允许我让代码在某些时序关键部分中执行、但仍保留跟踪以供后续调试。 这让我回想起您之前的建议- revector put()通过 UART 输出,并将其从外部捕获到 IDE。不是很方便,但应该允许目标处理器在不中断的情况下继续运行。

    这是我找到的唯一注释、它提供了 printf 实现的任何线索。 我没有理由对其有效性提出质疑,除了它似乎解释 了我所看到的情况。 还有人对 printf 实现有任何见解?

    Andrew

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    现在我已经知道了我要搜索的内容、我已经找到了其他 TI 对使用幕后断点的 CIO 函数的引用、例如 slau157as 提供的断点(v10.x 的 CCS 用户指南)

    控制台 I/O (CIO)函数(如 printf)需要使用断点。 如果在中编译了这些函数、但您不希望使用断点、请通过更改 Project→Properties→Debug→Program/Memory Load Options→Program/Memory Load Options→Enable CIO function use (需要设置断点)中的选项来禁用 CIO 功能。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    很抱歉、对于此主题、我本周正在出差、我可以在下周回到办公室时尝试这个问题、

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Andrew:

    我同意以下观点:

    [引用 userid="265677" URL"~/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1169852/msp430fr5964-console-printf-s-appear-to-trip-up-eusci-interrupt-handling/4416390 #4416390"]\n 如果您在代码中使用 printf()而不通过 UART 进行传递,则可以在 IDE 的调试控制台上查看字符串。 这样做的方式是编译器/调试器在代码转换字符串完成后添加断点, 然后通过 JTAG 将字符串拉出存储器、并将其显示在控制台上。

     如果使用此 printf 函数、可能会在代码中添加一些断点。

    谢谢!

    此致

    Johnson

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    是的、虽然我可能不知道 printf 的所有内部信息、但我认为我已经了解了 printf 的工作原理、从而理解了它的局限性。 虽然使用 printf 将调试信息直接输出到控制台是很巧妙的、但如果代码正在执行任何时间关键型操作、则在后台插入的断点可能会造成严重破坏并导致我看到的症状类型。 按照建议将底层的 puts()函数修改为 UART,但需要一个独立的日志记录应用程序(Putty 或类似的应用程序)。

    Johnson、感谢您为此提供的所有帮助。

    Andrew