【问题标题】:is it possible write to console without stdlibs? c/c++是否可以在没有标准库的情况下写入控制台? c/c++
【发布时间】:2013-05-03 06:26:42
【问题描述】:

我在 arm 微处理器上编程,并尝试通过 UART 使用打印语句进行调试。我不想添加stdlibs 只是为了调试。有没有办法在没有 stdio.h/iostream.h 的情况下打印到控制台?我可以自己写printf()吗?

或者,我可以使用 DMA 控制器并直接写入 UART 来执行此操作。但是我想避免这种情况是可能的。使用内置测试功能“echo”或 “远程环回”我知道我的 UART 配置正确。

【问题讨论】:

  • 是的,这是可能的——你可以编写自己的输出例程,找到一个小的独立的部分 printf() 实现,或者编写必要的后端支持以从最小的嵌入式 libc 启用这些功能(可能包含在您的工具链中)在您的平台上运行。
  • 谢谢。我听说 newlib 作为嵌入式 libc 可以正常工作。不过,我会先寻找部分 printf()。
  • @ChrisStratton:它有效地依赖于操作系统。本机操作系统例程可能标准库。

标签: c++ c microcontroller uart


【解决方案1】:

简短回答:是的,完全有可能同时使用这两种解决方案。

如果你想支持所有的数据类型和格式,printf 函数是相当复杂的。但是编写可以以几种不同的基数输出字符串或整数的东西并不难(大多数人只需要十进制和十六进制,但八进制可能只需要在具有十进制和十六进制后再添加 3-4 行代码)。

通常,printf 是这样写的:

 int printf(const char *fmt, ...)
 {
     int ret;
     va_list args; 

     va_start(args, fmt)
     ret = do_xprintf(outputfunc, NULL, fmt, args); 
     va_end(args);
     return ret;
}

然后do_xprintf() 为所有变体(printf、sprintf、fprintf 等)做所有艰苦的工作

int do_xprintf(void (*outputfunc)(void *extra, char c), void *extra, const char *fmt, va_list args)
{
    char *ptr = fmt;
    while(1)
    {
       char c = *ptr++;

       if (c == '%')
       {
            c = *ptr++; // Get next character from format string. 
            switch(c)
            {
               case 's': 
                  char *str = va_arg(args, const char *);
                  while(*str)
                  {
                      count++;
                      outputfunc(extra, *str);
                      str++;
                  }
                  break; 
               case 'x': 
                  base = 16;
                  goto output_number;

               case 'd':
                  base = 10;
         output_number:
                  int i = va_arg(args, int);
                  // magical code to output 'i' in 'base'. 
                  break;

               default:
                  count++;
                  outputfunc(extra, c);
                  break;
         }
         else
             count++;
             outputfunc(extra, c);
     }
     return count;
 }                

现在,您需要做的就是填写上述代码的一些位,然后编写一个 outputfunc() 输出到您的串行端口。

请注意,这是粗略的草图,我确信代码中存在一些错误 - 如果你想支持浮点或“宽度”,你将不得不多做一些工作......

(关于额外参数的注意事项 - 对于将作为文件指针的FILE * 的输出,对于sprintf,您可以传递缓冲区的结构和缓冲区中的位置,或类似的东西)

【讨论】:

  • 非常感谢。我是少数只需要​​十进制/十六进制的人之一。我正在使用定点符号并想验证我的结果。这将使事情变得更容易。感谢您的帮助!
  • output_number 是 case 语句中的标签而不是普通函数的任何原因?
  • @Lundin:只是打字更短。除非编译器非常聪明并且意识到两个函数调用之间的唯一区别是输入参数,否则它也可能产生更短的代码。
【解决方案2】:

“控制台”的概念在您使用的特定系统的上下文之外没有太大意义。通常在嵌入式程序中没有真正的控制台概念。

您正在寻找一种从系统中获取数据的方法。如果你想使用 UART,并且你没有使用像 GNU/linux 这样的高级操作系统,你将需要编写自己的 I/O 驱动程序。通常,这意味着首先通过寄存器写入将 UART 配置为所需的比特率/奇偶校验/流量控制。对于任何类型的稳健 IO,您都希望它是中断驱动的,因此您需要为使用循环缓冲区的 tx 和 rx 编写 ISR。

完成后,您可以编写自己的 printf,如 Mats 所示。

【讨论】:

  • 谢谢!我意识到了这一点。我最初试图弄清楚如何解决这个问题,因为我遇到了问题。但我发现使用我的处理器,每次写入 tx 缓冲区后,我都必须访问状态寄存器。如果我不这样做,它就会停止。我现在正在处理波特率问题,但一旦解决了问题,我肯定会使用 Mats 的东西。感谢您的帮助
【解决方案3】:

由于通过嵌入式系统中的串行端口打印信息会修改主程序的时序,因此我发现的最佳解决方案是发送一条以 2 个字节编码的小消息(有时 1 个字节可以正常工作),然后使用PC 中的程序来解码这些消息并提供必要的信息,其中可能包括统计数据和您可能需要的一切。 这样,我只在主程序中增加了一点点开销,让 PC 完成处理消息的繁重工作。 也许是这样的:

  • 1 字节消息:位 7:4 = 模块 ID,位 3:0 = 调试信息。

  • 2 字节消息:位 15:12 = 模块 ID,位 11:8 = 调试信息,位 7:0 = 数据。

然后,在 PC 软件中,您必须声明一个表格,其中包含映射到给定模块 ID/调试信息对的消息,并使用它们打印在屏幕上。

也许它不如伪 printf 函数灵活,因为您需要在 PC 中解码一组固定的消息,但它不会像我之前提到的那样增加太多开销。

希望对你有帮助。

费尔南多

【讨论】:

    【解决方案4】:

    我发现对于后台调试,将字符排入循环缓冲区,然后由 uart 发送寄存器上的轮询例程排出,这是我选择的方法。

    入队例程基于字符、字符串和可变大小(十六进制或固定宽度十进制)。一个豪华的缓冲区例程可以指示一个保留字符的溢出。

    该方法对目标操作的开销/影响最低,可以(小心)在中断例程中使用,而且这个想法很容易转移,所以我忽略了我使用过的所有系统上的调试器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-13
      • 1970-01-01
      • 1970-01-01
      • 2015-01-23
      • 1970-01-01
      • 2021-12-29
      • 2012-06-17
      • 1970-01-01
      相关资源
      最近更新 更多