当前位置:   article > 正文

RT_Thread 调试笔记:串口打印、MSH控制台 相关_rtthread nano打印内存

rtthread nano打印内存

说明:记录日常使用 RT_Thread 开发时做的笔记。

持续更新中,欢迎收藏。

目录

1.打印相关

1.打印宏定义,可以打印打印所在文件,函数,行数。

2. rt thread 3.1.5  nano版本中添加 rt_kprintf() 函数功能

3. printf 重定向

 4.串口打印函数

1.0 RT官方文档对print函数介绍

1.1 串口打印函数,可以直接打印字符串,或者变量值。(以下两种方式)

1.2 printf 打印函数参数格式

1.3 按固位数输出

1.4 串口打印2,串口输出2,如通过串口打印输出一个字符数组。(非 rt_kprintf )

1.5 RT THREAD 串口发送数据(非 rt_kprintf 函数)

1.6 打印输出数据按格式对齐

1.7 rt_kprintf 打印输出数据按格式对齐

1.8 调试信息、编译信息打印,如:编译路径,时间,日期,文件名字等等

1.9 串口底层直接发送数据

1.10 串口函数 rt_kprintf 打印字符串数组

1.11 关于 %*.*s , %.*s

1.12 打印宏定义内容

1.13 仿 rt_kprintf()函数,用在调试串口外的串口,更方便发送数据

1.14 仿 rt_kprintf()函数,方便打印调试信息的开关

2. MSH 控制台功能

1. rt thread 3.1.5  nano版本中添加 MSH 控制台功能

.1 在 keil 软件包中勾选 shell 功能

.2 在 finsh_port.c 中看提示开启宏和串口接收函数。

.3 方式1:在 rt_hw_console_getchar()中添加串口接收函数

.4 方式2:定义一个接收函数,放到 rt_hw_console_getchar 中调用

.5 修改后的 rt_hw_console_getchar()参考

3.调试笔记:

1.rt thread  内存申请失败调试笔记

2. rt-thread 3.1.5 内存堆大小设置

.1 修改位置

.2 官方介绍: RT-Thread 堆区大小设置


1.打印相关

1.打印宏定义,可以打印打印所在文件,函数,行数。

#define PRINT_TRACE() printf("-------%s:%s:%d------\r\n", __FILE__, __FUNCTION__, __LINE__);
  1. __FILE__:正在编译文件的路径及文件名
  2. __LINE__:正在编译文件的行号
  3. __DATE__:编译时刻的日期字符串 如“July 19 2019
  4. __TIME__:编译时刻的时间字符串 如”22:00:00
  5. __FUNCTION__:函数名,类型为:字符常量指针
  6. __VER__:IDE版本信息,类型为:整型

2. rt thread 3.1.5  nano版本中添加 rt_kprintf() 函数功能

.1 rtconfig.h头文件中开启控制台相关宏  #define RT_USING_CONSOLE
.2 在 board.c 中 添加串口初始化函数 和  rt_kprintf() 串口输出调用的函数 rt_hw_console_output()

  1. #ifdef RT_USING_CONSOLE
  2. static int uart_init(void)
  3. {
  4. //#error "TODO 2: Enable the hardware uart and config baudrate."
  5. USART1_Init();
  6. return 0;
  7. }
  8. INIT_BOARD_EXPORT(uart_init);
  9. void rt_hw_console_output(const char *str)
  10. {
  11. //#error "TODO 3: Output the string 'str' through the uart."
  12. /* empty console output */
  13. rt_enter_critical();
  14. while (*str != '\0')
  15. {
  16. HAL_UART_Transmit(&Uart1Handle, (uint8_t *) (str++), 1, 1000);
  17. }
  18. rt_exit_critical();
  19. }
  20. #endif

3. printf 重定向

可通过搜索功能查找到函数 fputc(),修改函数体中的串口数据发送函数。

  1. /* printf 重定向 */
  2. int fputc(int ch, FILE *f)
  3. {
  4. #if PRINTF_PORT_JLINK
  5. SEGGER_RTT_PutChar(0, ch);
  6. #else
  7. HAL_UART_Transmit(&Uart1Handle, (uint8_t *)&ch, 1, 1000);
  8. #endif
  9. return (ch);
  10. }
  11. int fgetc(FILE *f)
  12. {
  13. int ch;
  14. HAL_UART_Receive(&Uart1Handle, (uint8_t *)&ch, 1, 1000);
  15. return (ch);
  16. }

 4.串口打印函数

1.0 RT官方文档对print函数介绍

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/libc/compiler

标准输出函数 (printf 家族)

printf 函数

在 Keil 和 IAR 编译平台下,用户可以直接使用 printf 函数;在 GCC 下,需要额外使能 RT_USING_POSIX_FS 和 RT_USING_POSIX_STDIO 宏才能使用 printf 函数。

rt_kprintf 函数 与 printf 函数的使用选择

        如果不是特殊需求,建议使用 rt_kprintf 函数,因为 printf 是由编译平台内部提供的,其空间占用、以及内存使用情况我们无从得知,printf 函数要比 rt_kprintf 函数的 ROM 占用大很多。

        无论是 rt_kprintf 函数以及 printf 函数都是非线程函数,在多线程同时使用的情况下,会出现交叉打印的现象,该问题是正常现象,因为根据 C 标准的要求,printf 函数就是非线程安全的。

        原生 rt_kprintf 函数是经过优化的,占用空间要比 printf 函数小很多。但是 rt_kprintf 函数并不支持浮点类型的输出。因此:

  1. 如需 rt_kprintf 函数支持浮点类型的输出,可以安装 rt_vsnprintf_full 软件包
  2. 如需 rt_kprintf 函数支持线程安全输出,可以安装 rt_kprintf_threadsafe 软件包
  3. 上述两个软件包可以同时安装使用,以同时让 rt_kprintf 支持线程安全和浮点类型输出的能力。

        其他字符串格式化输出函数 (例如 snprintf 等)

        其他字符串格式化输出函数(例如 snprintf 等)都是可以直接使用的。

        强烈建议用户使用 rt_snprintf 等 RT-Thread 这侧的函数来代替 snprintf 等函数以降低资源消耗。尤其是在 GCC 编译链下,Newlib(GCC 工具链内部默认的 C 库)内置提供的 snprintf 函数是非线程安全的,在多线程无保护输出浮点数的情况下可能会引发死机(snprintf 函数非线程安全是不正常的)。

        同理,原生 rt_snprintf 等函数不支持浮点输出,用户可以通过安装 rt_vsnprintf_full 软件包 来支持浮点数输出。

1.1 串口打印函数,可以直接打印字符串,或者变量值。(以下两种方式)

 rt_kprintf("led_test");

打印某个变量的数据,如打印 count 变量值

  1. while (1)
  2. {
  3. rt_kprintf("thread1 count: %d\n", count ++);
  4. rt_thread_mdelay(500);
  5. }

1.2 printf 打印函数参数格式

描述:
        C 库函数 int printf(const char *format, ...) 发送格式化输出到标准输出 stdout。
printf()函数的调用格式为:

printf("<格式化字符串>", <参量表>);

声明:下面是 printf() 函数的声明。

printf("<格式化字符串>", <参量表>);

参数:
        format -- 这是字符串,包含了要被写入到标准输出 stdout 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:

格式字符

意义

d

以十进制形式输出带符号整数(正数不输出符号)

o

以八进制形式输出无符号整数(不输出前缀0)

x,X

以十六进制形式输出无符号整数(不输出前缀Ox)

u

以十进制形式输出无符号整数

f

以小数形式输出单、双精度实数

e,E

以指数形式输出单、双精度实数

g,G

以%f或%e中较短的输出宽度输出单、双精度实数

c

输出单个字符

s

输出字符串

p

输出指针地址

lu

32位无符号整数

llu

64位无符号整数

1.3 按固位数输出

        按固位数输出,比如4位 xxxx,6位 xxxxxx ,以下用 %x举例,其他格式也是相同用法。
应用说明,如果16进制(%x)输出 0x061E, 实际打印出的结果是 61E ,开始的0被省略掉。
如果需要按固定4位打印,则需要把 %x 改为 %04x,这样就能输出061E。

rt_kprintf("CpuID[0] = %08x ,CpuID[1] = %08x , CpuID[2] = %08x \r\n",CpuID[0],CpuID[1],CpuID[2]);

1.4 串口打印2,串口输出2,如通过串口打印输出一个字符数组。(非 rt_kprintf )

  1. static rt_device_t serial; /* 串口设备句柄 */
  2. static char str[] = "中国加油,武汉加油,全世界加油!\r\n"; /* 需要发送的数据 */
  3. //中间省略了串口配置的部分
  4. rt_device_write(serial, 0, str, (sizeof(str)-1));
  5. //打印输出,str是数组名可直接作为数组首地址的地址指针,sizeof(srt)计算数组的大小,这个语句可以自动全部打印输出str数组

1.5 RT THREAD 串口发送数据(非 rt_kprintf 函数)

向串口中写入数据,可以通过如下函数完成:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);

参数

描述

dev

设备句柄

pos

写入数据偏移量,此参数串口设备未使用

buffer

内存缓冲区指针,放置要写入的数据

size

写入数据的大小

返回

——

写入数据的实际大小

如果是字符设备,返回大小以字节为单位;

0

需要读取当前线程的 errno 来判断错误状态

        调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的大小是 size。
向串口写入数据示例程序如下所示:

  1. #define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
  2. static rt_device_t serial; /* 串口设备句柄 */
  3. char str[] = "hello RT-Thread!\r\n";
  4. struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置参数 */
  5. /* 查找串口设备 */
  6. serial = rt_device_find(SAMPLE_UART_NAME);
  7. /* 以中断接收及轮询发送模式打开串口设备 */
  8. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
  9. /* 发送字符串 */
  10. rt_device_write(serial, 0, str, (sizeof(str) - 1));

1.6 打印输出数据按格式对齐

        比如输出数据1和11对齐,即输出01,11,这样有助于通过串口输出的数据整齐,方便后期数据处理;如下图,时分秒按2位显示对齐,只有各位数的,在十位补空格。这样让整个数据格式整齐。

        具体输出函数如下,在需要显示指定位数的地方 %2d ,表示按2位数据格式输出10进制数据。
经过测试发现,如果限制数据为 %2d 但是实际数据是3位,则输出任然按3位输出。

  1. rt_kprintf("%d.%d.%d/%2d:%2d:%2d:AHT10 温度:%d;SHT30 湿度:%d;SHT30 温度:%d;SHT30 湿度:%d;\n",\
  2. rtc_data_hex[6],rtc_data_hex[5],rtc_data_hex[4],rtc_data_hex[2],rtc_data_hex[1],rtc_data_hex[0],\
  3. AHT10_temperature,AHT10_humidity,SHT30_temperature,SHT30_humidity);

1.7 rt_kprintf 打印输出数据按格式对齐

        使用 rt_kprintf 函数打印小数、浮点数,

rt_kprintf("表头 PA7 电压 = %d.%03d ,   传感器 PB0 电压 = %d.%03d\n", vol_bt / 1000, vol_bt % 1000, vol_sig / 1000, vol_sig % 1000);

1.8 调试信息、编译信息打印,如:编译路径,时间,日期,文件名字等等

  1. __FILE__:正在编译文件的路径及文件名
  2. __LINE__:正在编译文件的行号
  3. __DATE__:编译时刻的日期字符串 如“July 19 2019
  4. __TIME__:编译时刻的时间字符串 如”22:00:00
  5. __FUNCTION__:函数名,类型为:字符常量指针
  6. __VER__:IDE版本信息,类型为:整型
  7. char BuildFile[] = __FILE__;
  8. int BuildLine = __LINE__;
  9. char BuildDate[] = __DATE__;
  10. char BuildTime[] = __TIME__;
  11. /* 为打印调试提供文件,行数,函数名等附加信息:*/
  12. rt_kprintf("编译文件路径:%s\n", BuildFile);
  13. rt_kprintf("编译代码所在行:%d\n", TestLine);
  14. rt_kprintf("编译日期:%s\n", BuildDate);
  15. rt_kprintf("编译时间:%s\n", BuildTime);

打印效果

1.9 串口底层直接发送数据

  1. for( i = 0 ; i < Send_Count; i++) //循环发送,直到发送完毕
  2. {
  3. while((USART1->SR&0X40)==0);//判断串口数据是否发送完
  4. USART1->DR = DataScope_OutPut_Buffer[i]; //从串口丢一个字节数据出去
  5. }

1.10 串口函数 rt_kprintf 打印字符串数组

  1. /*定义一个字符串数组*/
  2. u_char Adc_Channel_Name[CH_NUM][12] = \
  3. {
  4. {"ch1_vh2_r "},\
  5. {"ch1_vh1_ra"},\
  6. {"ch1_vh1_la"},\
  7. {"ch1_vh2_l "},\
  8. {"ch1_vh1_rb"},\
  9. {"ch1_vh1_lb"},\
  10. {"ch2_vh2_r "},\
  11. {"ch2_vh1_ra"},\
  12. {"ch2_vh1_la"},\
  13. {"ch2_vh2_l "},\
  14. {"ch2_vh1_rb"},\
  15. {"ch2_vh1_lb"},\
  16. {"ch1_th1 "},\
  17. {"ch2_th1 "},\
  18. {"24v_th1 "},\
  19. {"3v3_th1 "},\
  20. };/*通道名称*/
  21. ---------------------
  22. /*打印输出*/
  23. if (argc != 3 )
  24. {
  25. rt_kprintf("校准命令:请输入校准命令 和 校准目标值,单位mV,输入如: vcf ch1_vh2_r 14000 \n");
  26. rt_kprintf("当前采样电压 \n");
  27. for (uint8_t var = 0; var < CH_NUM; ++var)
  28. {
  29. rt_kprintf("%s = %d \n",&Adc_Channel_Name[var][0],Adc_Channel_InVol[var]);
  30. }
  31. return;
  32. }

输出效果

1.11 关于 %*.*s , %.*s

        小数点.后“*”表示输出位数,具体的数据来自参数表 printf 格式字符串中,与宽度控制和精度控制有关的常量都可以换成变量,方法就是使用一个“*”代替那个常量,然后在后面提供变量给“*”。同样,小数点.前也可以添加*,也要用户输入一个位宽值来代替,表示输出的字符所占位宽。也就是说,前面定义输出总宽度,后面定义输出字符个数。

举例:

  1. #include <cstdio>
  2. #include <iostream>
  3. int main()
  4. {
  5. char *s = "this is test example";
  6. int a,b;
  7. printf("%.*s\n", 10, s);//这里的常量10就是给*号的,你也可以用一个变量来控制宽度
  8. printf("%*.*s\n", 20, 10, s);//常量20控制输出所占位宽,也可以用一个变量控制
  9. std::cin>>a>>b; //输入15 10
  10. printf("%*.*s\n", a, b, s);//输出为:-----this is te 前面定义输出总宽度,后面定义输出字符个数
  11. std::cin.get();
  12. std::cin.ignore();//暂停程序执行
  13. }
  1. 输出结果为:
  2. this is te
  3. ----------this is te//-代表空格
  4. 15  10 //输入
  5. -----this is te
  6. 转载于:https://www.cnblogs.com/ph829/p/5576832.html

1.12 打印宏定义内容

  1. #define def_buffer_swver_data "SW-MCU-V1.3-20210720" /* 软件版本号 */
  2. /* 软件版本号打印 */
  3. rt_kprintf("软件版本号: ");
  4. rt_kprintf("%s",def_buffer_swver_data);
  5. rt_kprintf("\r\n");

1.13 仿 rt_kprintf()函数,用在调试串口外的串口,更方便发送数据

 修改代码中 rt_device_write() 串口句柄参数,就可以像 rt_kprintf()一样使用自定义的串口了。

  1. /**
  2. * This function will print a formatted string on system console
  3. * 本函数基于 系统的 rt_kprintf 改动而来,主要改动了 指向串口硬件的变量 yl_uart_device
  4. * @param fmt the format
  5. */
  6. void yl_kprintf(const char *fmt, ...)
  7. {
  8. rt_device_t yl_uart_device = serial_uart3; /* 定义一个本函数用的串口硬件句柄 */
  9. va_list args;
  10. rt_size_t length;
  11. static char rt_log_buf[RT_CONSOLEBUF_SIZE];
  12. va_start(args, fmt);
  13. /* the return value of vsnprintf is the number of bytes that would be
  14. * written to buffer had if the size of the buffer been sufficiently
  15. * large excluding the terminating null byte. If the output string
  16. * would be larger than the rt_log_buf, we have to adjust the output
  17. * length. */
  18. length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
  19. if (length > RT_CONSOLEBUF_SIZE - 1)
  20. length = RT_CONSOLEBUF_SIZE - 1;
  21. if (yl_uart_device == RT_NULL)
  22. {
  23. rt_hw_console_output(rt_log_buf);
  24. }
  25. else
  26. {
  27. rt_uint16_t old_flag = yl_uart_device->open_flag;
  28. yl_uart_device->open_flag |= RT_DEVICE_FLAG_STREAM;
  29. //rt_device_write(serial_uart3, 0, rt_log_buf, length); /* 串口对应 RS232 */
  30. yl_rt_device_write(serial_uart3, 0, rt_log_buf, length);/* 串口对应硬件是RS485接口 */
  31. yl_uart_device->open_flag = old_flag;
  32. }
  33. va_end(args);
  34. }
  35. RTM_EXPORT(yl_kprintf);

1.14 仿 rt_kprintf()函数,方便打印调试信息的开关

通过宏定义来统一管理调试信息输出的开关

  1. 以下代码通过仿 LOG_D("Hello RT-Thread!"); 改变而得。
  2. /**************************以下内容放到 .h 文件中**************************************/
  3. #define DEBUG_PRINTF_ONOFF 0 /* 调试信息打印开关 */
  4. /* 调试信息打印函数 */
  5. #define debug_printf(fmt, ...) \
  6. do \
  7. { \
  8. if(DEBUG_PRINTF_ONOFF)rt_kprintf(fmt, ##__VA_ARGS__); \
  9. } \
  10. while (0)
  11. /************************** .c 文件中的调用 **************************************/
  12. for (uint8_t var = 0; var < max; ++var)
  13. {
  14. debug_printf("函数回调功能触发 = %d \r\n",var);
  15. rt_thread_mdelay(100);
  16. }

2. MSH 控制台功能

1. rt thread 3.1.5  nano版本中添加 MSH 控制台功能

.1 在 keil 软件包中勾选 shell 功能

.2 在 finsh_port.c 中看提示开启宏和串口接收函数。

添加 shell 功能后编译 在 finsh_port.c 文件中会有2个错误的提示。

  1. /* 在 rtconfig.h 中开启 #include "finsh_config.h 宏 */
  2. #ifndef RT_USING_FINSH
  3. #error Please uncomment the line <#include "finsh_config.h"> in the rtconfig.h
  4. #endif
  5. #ifdef RT_USING_FINSH
  6. /* 定义串口接收功能 */
  7. RT_WEAK char rt_hw_console_getchar(void)
  8. {
  9. /* Note: the initial value of ch must < 0 */
  10. int ch = -1;
  11. //#error "TODO 4: Read a char from the uart and assign it to 'ch'."
  12. return ch;
  13. }
  14. #endif /* RT_USING_FINSH */

.3 方式1:在 rt_hw_console_getchar()中添加串口接收函数

注意:在串口调试软件中需要输入回车,系统才会正确接收控制台命令。

 HAL_UART_Receive(&Printf_UartHandle, (uint8_t *)&ch, 1, 1000);

.4 方式2:定义一个接收函数,放到 rt_hw_console_getchar 中调用

前提:串口已经实现了对串口数据的接收,接收的数据存储在了接收缓存中,缓存的前2个字节用于存储 shell 取了第几个字节数据,第3个字节开始存储串口收到的数据。

  1. void read_uart1_ch(int *ch)
  2. {
  3. uint16_t *count = NULL;
  4. count = (uint16_t *)usart1_rx_buf;
  5. *ch = usart1_rx_buf[*count];
  6. if (*ch == 0)
  7. {
  8. *ch = -1;
  9. }
  10. else
  11. {
  12. *count = *count +1;
  13. }
  14. }

.5 修改后的 rt_hw_console_getchar()参考

  1. //添加RTOS控制台的接收代码
  2. char rt_hw_console_getchar(void)
  3. {
  4. /* Note: the initial value of ch must < 0 */
  5. int ch = -1;
  6. #if PRINTF_PORT_JLINK /* j-link rtt 模式*/
  7. ch = SEGGER_RTT_GetKey();
  8. #else /* 串口 模式*/
  9. HAL_UART_Receive(&Printf_UartHandle, (uint8_t *)&ch, 1, 1000); //方式1
  10. ch = read_uart1_ch();//方式2
  11. #endif
  12. return ch;
  13. }

3.调试笔记:

1.rt thread  内存申请失败调试笔记

问题描述:
1.在线程中调用了一个函数A,该函数会申请内存函数结束前会再释放。测试中发现这个函数在线程中调用几次后就提示异常,申请不到内存。
2:请教:内存的释放 是否是在 rt_free 后就完成?
3:大概逻辑

  1. 线程()
  2. {
  3. while(n)
  4. {
  5. 调用函数:A(申请内存,执行相关工作,释放内存);
  6. 延时;
  7. 调用函数:A(申请内存,执行相关工作,释放内存);
  8. 延时;
  9. }
  10. }

 问题已解决自己回答:
1:调试发现内存堆分配了15K,关掉发现问题的线程后,发现系统跑起来其他线程都运行后,内存最大已经使用了13K多,这就反应了为什么前几次能申请到内存后面就申请不到了,因为这个线程运行的比较早开始时内存充足可以申请到,后面问题线程延时时系统调度启动了别的线程,导致内存堆空间减少,延时结束继续申请内存时就出现了内存不足。
2:增大内存堆后问题解决。

2. rt-thread 3.1.5 内存堆大小设置

.1 修改位置

在 board.c 文件中修改  #define RT_HEAP_SIZE (30*1024)
  1. #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
  2. /*
  3. * Please modify RT_HEAP_SIZE if you enable RT_USING_HEAP
  4. * the RT_HEAP_SIZE max value = (sram size - ZI size), 1024 means 1024 bytes
  5. */
  6. #define RT_HEAP_SIZE (30*1024)
  7. static rt_uint8_t rt_heap[RT_HEAP_SIZE];

.2 官方介绍: RT-Thread 堆区大小设置

RT-Thread 堆区大小设置

链接:https://www.cnblogs.com/jzcn/p/16427067.html

介绍了 RT-THREAD 内存堆的设置,STM32内存分布情况。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/779919
推荐阅读
相关标签
  

闽ICP备14008679号