是否可以在没有stdlibs的情况下写入控制台? C / C ++

时间:2022-12-05 20:49:45

I am programming on an arm microprocessor and am trying to debug using print statements via UART. I do not want to add stdlibs just for debugging. Is there a way to print to the console without stdio.h/iostream.h ? Is it possible for me to write my own printf()?

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

Alternatively I can do this using a DMA controller and writing to the UART directly. However I would like to avoid that is possible. Using the built in test function "echo" or "remote loop-back" I know I have the UART configured properly.

或者,我可以使用DMA控制器直接写入UART。但是,我想避免这是可能的。使用内置测试功能“echo”或“remote loop-back”我知道我已正确配置UART。

4 个解决方案

#1


9  

Short answer: Yes, it's entirely possible to do both of your solutions.

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

The printf function is quite complex if you want to support all of the data types and formats. But it's not that hard to write something that can output a string or an integer in a few different bases (most people only need decimal and hex, but octal probably only adds another 3-4 lines of code once you have decimal and hex).

如果要支持所有数据类型和格式,printf函数非常复杂。但是编写可以在几个不同的基础上输出字符串或整数的东西并不难(大多数人只需要十进制和十六进制,但是一旦你有十进制和十六进制,八进制可能只会添加另外3-4行代码)。

Typically, printf is written like this:

通常,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;
}

And then the do_xprintf() does all the hard work for all variants (printf, sprintf, fprintf, etc)

然后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;
 }                

Now, all you need to do is fill in a few bits of the above code and write an outputfunc() that outputs to your serial port.

现在,您需要做的就是填写上面代码的几个部分并写一个输出到您的串行端口的outputfunc()。

Note that this is rough sketch, and I'm sure there are some bugs in the code - and if you want to support floating point or "widths", you will have to work a bit more at it...

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

(Note on the extra parameter - for output to a FILE * that would be the filepointer, for sprintf, you can pass a structure for the buffer and position in the buffer, or something like that)

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

#2


2  

The concept of "console" does not have much meaning outside of the context of the specific system you are using. Generally in embedded program there is no real concept of a console.

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

What you are looking for is a way to get data out of your system. If you want to use the UART, and you are not using a high-level OS like GNU/linux, you will need to write your own I/O drivers. Generally this means first configuring the UART for the desired butrate/parity/flow control via register writes. For any type of robust IO you will want it to be interrupt driven, so you will need to write ISRs for tx and rx that utilize circular buffers.

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

After you have that done, you can write your own printf like Mats indicated.

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

#3


1  

I have found for background debugging, enqueuing characters into a circular buffer which is then drained by a polling routine on the uart transmit register, is my method of choice.

我找到了后台调试,将字符排入循环缓冲区,然后通过uart传输寄存器上的轮询程序排出,这是我选择的方法。

The enqueuing routines are based around a character, string and the variable size (to either hex or fixed width decimal). And a deluxe buffer routine could indicate an overflow with a reserved character.

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

The approach has the lowest overhead/impact on target operation, can be used (with care) in interrupt routine and the idea is easily transferable, so I have ignored the debugger on all the systems I have used.

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

#4


1  

Since printing out information through a serial port in an embedded system modifies the main program's timing, the best solution I've found is to send a small message encoded in 2 bytes (sometimes 1 byte works fine), and then using a program in the PC to decode those messages and provide the necessary information, which can include statistics and everything you may need. This way, I'm adding just a little bit of overhead to the main program, and letting the PC do the hard work to process the messages. Maybe something like this:

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

  • 1 byte message: bits 7:4 = module ID, bits 3:0 = debug info.

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

  • 2 bytes message: bits 15:12 = module ID, bits 11:8 = debug info, bits 7:0 = data.

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

Then, in the PC software, you have to declare a table with the messages that map to a given module ID/debug info pair, and use them to be printed on the screen.

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

Maybe it's not as flexible as the pseudo-printf function, since you need a fixed set of messages in the PC to decode, but it doesn't add too much overhead, as I mentioned before.

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

Hope it helps.

希望能帮助到你。

Fernando

#1


9  

Short answer: Yes, it's entirely possible to do both of your solutions.

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

The printf function is quite complex if you want to support all of the data types and formats. But it's not that hard to write something that can output a string or an integer in a few different bases (most people only need decimal and hex, but octal probably only adds another 3-4 lines of code once you have decimal and hex).

如果要支持所有数据类型和格式,printf函数非常复杂。但是编写可以在几个不同的基础上输出字符串或整数的东西并不难(大多数人只需要十进制和十六进制,但是一旦你有十进制和十六进制,八进制可能只会添加另外3-4行代码)。

Typically, printf is written like this:

通常,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;
}

And then the do_xprintf() does all the hard work for all variants (printf, sprintf, fprintf, etc)

然后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;
 }                

Now, all you need to do is fill in a few bits of the above code and write an outputfunc() that outputs to your serial port.

现在,您需要做的就是填写上面代码的几个部分并写一个输出到您的串行端口的outputfunc()。

Note that this is rough sketch, and I'm sure there are some bugs in the code - and if you want to support floating point or "widths", you will have to work a bit more at it...

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

(Note on the extra parameter - for output to a FILE * that would be the filepointer, for sprintf, you can pass a structure for the buffer and position in the buffer, or something like that)

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

#2


2  

The concept of "console" does not have much meaning outside of the context of the specific system you are using. Generally in embedded program there is no real concept of a console.

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

What you are looking for is a way to get data out of your system. If you want to use the UART, and you are not using a high-level OS like GNU/linux, you will need to write your own I/O drivers. Generally this means first configuring the UART for the desired butrate/parity/flow control via register writes. For any type of robust IO you will want it to be interrupt driven, so you will need to write ISRs for tx and rx that utilize circular buffers.

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

After you have that done, you can write your own printf like Mats indicated.

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

#3


1  

I have found for background debugging, enqueuing characters into a circular buffer which is then drained by a polling routine on the uart transmit register, is my method of choice.

我找到了后台调试,将字符排入循环缓冲区,然后通过uart传输寄存器上的轮询程序排出,这是我选择的方法。

The enqueuing routines are based around a character, string and the variable size (to either hex or fixed width decimal). And a deluxe buffer routine could indicate an overflow with a reserved character.

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

The approach has the lowest overhead/impact on target operation, can be used (with care) in interrupt routine and the idea is easily transferable, so I have ignored the debugger on all the systems I have used.

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

#4


1  

Since printing out information through a serial port in an embedded system modifies the main program's timing, the best solution I've found is to send a small message encoded in 2 bytes (sometimes 1 byte works fine), and then using a program in the PC to decode those messages and provide the necessary information, which can include statistics and everything you may need. This way, I'm adding just a little bit of overhead to the main program, and letting the PC do the hard work to process the messages. Maybe something like this:

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

  • 1 byte message: bits 7:4 = module ID, bits 3:0 = debug info.

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

  • 2 bytes message: bits 15:12 = module ID, bits 11:8 = debug info, bits 7:0 = data.

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

Then, in the PC software, you have to declare a table with the messages that map to a given module ID/debug info pair, and use them to be printed on the screen.

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

Maybe it's not as flexible as the pseudo-printf function, since you need a fixed set of messages in the PC to decode, but it doesn't add too much overhead, as I mentioned before.

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

Hope it helps.

希望能帮助到你。

Fernando