我正在手臂微处理器上编程,并试图通过 UART 使用打印语句进行调试。我不想添加stdlibs
只是为了调试。stdio.h
有没有办法在没有/的情况下打印到控制台iostream.h
?我可以自己写printf()
吗?
或者,我可以使用 DMA 控制器并直接写入 UART 来执行此操作。但是我想避免这种情况是可能的。使用内置的测试功能“echo”或“remote loop-back”我知道我已经正确配置了UART。
我正在手臂微处理器上编程,并试图通过 UART 使用打印语句进行调试。我不想添加stdlibs
只是为了调试。stdio.h
有没有办法在没有/的情况下打印到控制台iostream.h
?我可以自己写printf()
吗?
或者,我可以使用 DMA 控制器并直接写入 UART 来执行此操作。但是我想避免这种情况是可能的。使用内置的测试功能“echo”或“remote loop-back”我知道我已经正确配置了UART。
简短的回答:是的,完全有可能同时使用这两种解决方案。
如果要支持所有数据类型和格式, 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 *
文件指针的 a ,对于sprintf
,您可以传递缓冲区的结构和缓冲区中的位置,或类似的东西)
在您使用的特定系统的上下文之外,“控制台”的概念没有太多意义。通常在嵌入式程序中没有控制台的真正概念。
您正在寻找的是一种从系统中获取数据的方法。如果你想使用 UART,并且你没有使用像 GNU/linux 这样的高级操作系统,你将需要编写自己的 I/O 驱动程序。通常,这意味着首先通过寄存器写入将 UART 配置为所需的比特率/奇偶校验/流量控制。对于任何类型的稳健 IO,您都希望它是中断驱动的,因此您需要为使用循环缓冲区的 tx 和 rx 编写 ISR。
完成后,您可以编写自己的 printf,如 Mats 所示。
由于通过嵌入式系统中的串行端口打印出信息会修改主程序的时序,因此我发现的最佳解决方案是发送以 2 个字节编码的小消息(有时 1 个字节可以正常工作),然后使用PC 来解码这些消息并提供必要的信息,其中可能包括统计数据和您可能需要的一切。这样,我只在主程序中增加了一点点开销,让 PC 完成处理消息的繁重工作。也许是这样的:
1 字节消息:位 7:4 = 模块 ID,位 3:0 = 调试信息。
2 字节消息:位 15:12 = 模块 ID,位 11:8 = 调试信息,位 7:0 = 数据。
然后,在 PC 软件中,您必须声明一个表格,其中包含映射到给定模块 ID/调试信息对的消息,并使用它们打印在屏幕上。
也许它不如伪 printf 函数灵活,因为你需要在 PC 中解码一组固定的消息,但它不会增加太多开销,正如我之前提到的。
希望能帮助到你。
费尔南多
我发现对于后台调试,将字符排入循环缓冲区,然后由 uart 发送寄存器上的轮询例程排出,这是我选择的方法。
入队例程基于字符、字符串和可变大小(到十六进制或固定宽度的十进制)。一个豪华的缓冲区例程可以指示一个保留字符的溢出。
该方法对目标操作的开销/影响最低,可以(小心地)在中断例程中使用,并且这个想法很容易转移,所以我忽略了我使用过的所有系统上的调试器。