小识堂 | 重新定向 printf 输出到串口 ,你知多少???

重新定向 printf 输出到串口 

概述

在调试 DSP 程序的时候,不可避免会用到 C 语言运行时提供的一些标准输入/输出函数来获取或输出一些调试信息。但是,在使用 CCS 集成开发环境时,这些调试信息往往是通过 CCS Console 窗口来输入输出的,当程序固化在 Flash 自启动时,这些调试输入输出就不能够使用了。如果,可以使这些标准输出从串口输出,这样不论调试还是固化之后都可以很方便的查看调试信息。

这里不得不要提到DSP 的 C++/C 语言运行时支持库(Runtime Support Library,即 RTS 库)。RTS 库都是类似这样的名称,rts6600_elf.lib / rts6740_elf.lib / rts6740e.lib 等等,其中 ELF 代表库二进制格式为 ELF,需要注意的是同一工程中链接的库必须是同一种格式的,COFF 或者 ELF,不能够混合使用,也不支持 COFF 格式与 ELF 格式库文件互相转换。rts6740e.lib 中的字母 e 代表大端字节序。库名中的数字代表 DSP CPU 架构,6400,6740,6600等等。

而 RTS 库中,很大一部分函数是 C I/O 函数。在 CCS 集成开发环境下 TI 指定了一套协议通过 C I/O 函数与运行着 CCS 的电脑进行通信。(请参阅 http://processors.wiki.ti.com/index.php/CIO_System_Call_Protocol 获取 C I/O 的详细协议说明)比如,可以通过 C 语言文件操作函数 fopen / fread / fwrite 等函数直接读取电脑上的文件进行处理或者使用 clock() 函数来测量时间等等。

1.         #include;

2.          

3.         void main()

4.         {

5.             FILE *fd;

6.          

7.             fd= fopen('C:/test.txt', 'w');

8.             fprintf(fd,'Hello, Tronlong!\n');

9.             fclose(fd);

10.      

11.         printf('Hello again, Tronlong!\n');

12.     }

在 CCS 下运行这段程序的效果是,在 C 盘根目录下新建 test.txt 文本文件,并在该文件中写入字符串“Hello, Tronlong!”,然后在 CCS Console 窗口输出 “Hello again, Tronlong!”。这就是 C I/O 函数的主要功能,提供一种便捷的方式与上位机交互。

但是,最常用的 C I/O 函数是类似 printf 这样可以输出调试文本的函数。

实现

1

1、printf 输出到 CCS Console

1.   #include

2.    

3.   int main(void)

4.   {

5.       printf('Hello Tronlong!');

6.    

7.       for(;;)

8.       {

9.    

10.      }

11.  }

这一段简单的 C 程序,很容易就可以知道输出结果,但是当在开发板上实际运行的时候,会发现根本不会输出“Hello Tronlong!”。为什么呢?C 语言标准输出(Standard Output,stdout)默认是缓冲的,而且是行缓冲(Line buffered)。只有当遇到行结束符('\n')的时候才会输出。当然,在缓冲区满或者程序退出的时候也会输出。

将第5行,修改为

printf('Hello Tronlong!\n');

即可正常输出。

此外,最后的 for(;;) { } 循环是为了避免程序退出。如果程序退出会出现如下的提示,注意这个是提示,既不是警告更不是错误。

该提示是说找不到当前执行语句(图中是 /tmp/TI_MKLIBps1kJb/SRC/exit.c 这个文件,一般出现在 DSP 程序执行完成后也就是 main 函数返回后)所对应的源码(在这里,它只是一个 for 无限循环而已)。可以按下图操作找到源码。

点击定位文件...(Locate file...)按钮

在弹出的对话框定位到编译工具链的安装路径,图示路径为 D:\Project\Ti\c6000_7.4.16\lib\src。

可以看到 DSP 程序运行在 _CODE_ACCESS void abort(void) 函数中的    for (;;);   /* CURRENTLY, THIS SPINS FOREVER */ 语句。

2

2、C I/O结构                         

C I/0 函数逻辑上分为三层,上层(High Level),底层(Low Level)和设备驱动层(Device-driver Level)。

上层函数是标准 C 库 I/O 流(printf, scanf, fopen, getchar等)。这些函数调用单个或者多个底层 I/O 函数来实现上层 I/O 请求。这些上层 I/O 以文件指针的方式操作,也被称为流(Stream)。使用上层 I/O 函数可以确保应用程序的可移植性,使用这些函数需要包含 stdio.h 头文件。

底层 I/O 函数由七个基本函数组成:open, read, write, close, lseek,

rename 和 unlink。这些函数提供上层函数与设备驱动函数之间的接口,而设备驱动函数提供在特定硬件的的 I/O 操作。

底层函数被设计为适合所有 I/O 操作,即使操作的对象并不是实际的磁盘文件。其实,所有的 I/O 操作都可以抽象为文件操作,比如,Linux 之类的类 Unix 系统就是用设备文件来操作硬件外设的。

打开文件 I/O

语法

#include

int open(const char * path , unsigned flags , int file_descriptor);

关闭文件 I/O

语法

#include

int close (int file_descriptor );

从文件读取字符

语法

#include

关闭文件 I/O

语法

#include

int close (int file_descriptor );

写字符到文件

语法

#include

int write (int file_descriptor , const char * buffer , unsigned count );

设置文件指针位置

语法

#include

off_t lseek (int file_descriptor , off_t offset , int origin );

删除文件

语法

#include

    int unlink (const char * path );

重命名文件

语法

#include

    int rename (const char * old_name , const char * new_name );

3

3、实现重定向                         

现在目标就很明确了,就是要实现串口操作的那7个底层 I/O 函数。RTS 库中提供了这样一个函数 add_device 用来添加设备,也就是把一个开发人员定义的设备注册到 I/O 设备表中。

_DECL _CODE_ACCESS int add_device(

    char     *name,                   

    unsigned  flags,

    int      (*dopen)(const char *path, unsigned flags, int llv_fd),

    int      (*dclose)(int dev_fd),

    int      (*dread)(int dev_fd, char *buf, unsigned count),

    int      (*dwrite)(int dev_fd, const char *buf, unsigned count),

    off_t    (*dlseek)(int dev_fd, off_t offset, int origin),

    int      (*dunlink)(const char *path),

int      (*drename)(const char *old_name, const char *new_name));

这个函数,除了前两个参数,剩下的参数就是那7个函数的函数指针,现在定义一个串口设备,其中 UART 即设备名称,在后面打开设备的时候要用到, _SSA 代表只能打开一个文件实例,如果实现支持对多个串口外设操作,这里可以改成 _MSA 以便支持多实例。

 1.   add_device('UART', _SSA, UART_open, UART_close, UART_read, UART_write, UART_lseek, UART_unlink, UART_rename);

下一步就需要编写这7个函数的源码,在这里以 TL665x-EasyEVM 为例,包括但不仅限于该平台,理论上支持 TI 现在全部的 DSP 平台。

1.   int UART_open(const char *path, unsigned flags, int fno)

2.   {

3.       BoardUART *uart0cfg = (BoardUART *)malloc(sizeof(BoardUART));

4.    

5.       uart0cfg->ID = BoardUART0;

6.       uart0cfg->BaudRate = BAUD_115200;

7.       uart0cfg->config = UART_WORDL_8BITS;

8.       uart0cfg->OverSampRate = UART_OVER_SAMP_RATE_16;

9.       UARTInit(uart0cfg);

10.   

11.      return (int)uart0cfg;

12.  }

13.   

14.  int UART_close(int fno)

15.  {

16.      if(fno)

17.      {

18.          free((void *)fno);

19.      }

20.   

21.      return 0;

22.  }

23.   

24.  int UART_read(int fno, char *buffer, unsigned count)

25.  {

26.   

27.      return 0;

28.  }

29.   

30.  int UART_write(int fno, const char *buffer, unsigned count)

31.  {

32.      UARTPuts((BoardUART *)fno, (char *)buffer, count);

33.   

34.      return 0;

35.  }

36.   

37.  off_t UART_lseek(int fno, off_t offset, int origin)

38.  {

39.   

40.      return 0;

41.  }

42.   

43.  int UART_unlink(const char *path)

44.  {

45.   

46.      return 0;

47.  }

48.   

49.  int UART_rename(const char *old_name, const char *new_name)

50.  {

51.   

52.      return 0;

53.  }

这一层即所谓的设备驱动层,需要实现具体的硬件外设控制,方法有很多,可以直接读写寄存器,可以使用 CSL 库等等。在这里使用 C665x 平台的 Tronlong.DSP.Driver.le66 库来操作串口外设的,不过7个底层 I/O 函数中 UART_lseek / UART_unlink / UART_rename 对于抽象为文件的硬件外设是没有太大意义的这里就没有实现。

最后一步操作,就是把 C 语言标准输出重定向到我们刚才新增加的 I/O 设备,UART:/uart0 冒号(:)后面代表具体的路径,可选参数,也可以只写成 UART:。如果底层函数支持多个串口外设,可用在后面增加路径,这个路径会作为字符串传递到 open 函数。

前文提到过标准输出是行缓冲的,为了可用马上在串口看到输出结果,这里使用 setvbuf 函数禁用缓冲。

1.       freopen('UART:/uart0', 'w', stdout);    // 重定向 stdout 到串口

2.       setvbuf(stdout, NULL, _IONBF, 0);       // 禁用 stdout 缓冲区

再次运行前面的例程,可用看到 printf 被输出到了串口。

如果直接通过文件 I/O 对实际硬件操作是怎么样的呢?下面以 LED 为例,虽然看起来比较奇怪,但确实是可行的方案。

1.   int main(void)

2.   {

3.       /* 基本外设初始化 */

4.       SystemInit();

5.    

6.       printf('Hello world!\r\n');

7.    

8.       FILE *led1, *led2, *leds;

9.    

10.      led1 = fopen('LED:/led1', 'w'); // 打开设备

11.      led2 = fopen('LED:/led2', 'w');

12.      leds = fopen('LED:/leds', 'w');

13.   

14.      setbuf(led1, 0);                // 禁用缓冲区

15.      setbuf(led2, 0);

16.      setbuf(leds, 0);

17.   

18.      // 主循环

19.      for(;;)

20.      {

21.          // 延时(非精确)

22.          Delay(0x00FFFFFF);

23.          fputc(0x00, led1);

24.          fputc(0x01, led2);

25.          fputc(0x18, leds);

26.   

27.          // 延时(非精确)

28.          Delay(0x00FFFFFF);

29.          fputc(0x01, led1);

30.          fputc(0x00, led2);

31.          fputc(0x14, leds);

32.   

33.          // 延时(非精确)

34.          Delay(0x00FFFFFF);

35.          fputc(0x0C, leds);

36.      }

37.   

38.  //  fclose(led);

39.  }

1.   int LED_open(const char *path, unsigned flags, int fno)

2.   {

3.       // 返回的文件描述符 0/1/2 系统保留

4.       // 如果使用会被自动顺延

5.    

6.       KickUnlock();

7.    

8.       if(!strcmp(path, '/led1'))

9.       {

10.          // 核心板 LED

11.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO26_UARTCTS1, GPIO_NORMAL_ENABLED);

12.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO26_UARTCTS1, GPIO_DIR_OUTPUT);

13.   

14.          return 0x00000011;

15.      }

16.      else if(!strcmp(path, '/led2'))

17.      {

18.          // 核心板 LED

19.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO27_UARTRTS1, GPIO_NORMAL_ENABLED);

20.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO27_UARTRTS1, GPIO_DIR_OUTPUT);

21.   

22.          return 0x00000012;

23.      }

24.      else if(!strcmp(path, '/led3'))

25.      {

26.          // 底板 LED

27.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO19_TIMO1,    GPIO_NORMAL_ENABLED);

28.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO19_TIMO1,    GPIO_DIR_OUTPUT);

29.   

30.          return 0x00000013;

31.      }

32.      else if(!strcmp(path, '/led4'))

33.      {

34.          // 底板 LED

35.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO22_UARTCTS0, GPIO_NORMAL_ENABLED);

36.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO22_UARTCTS0, GPIO_DIR_OUTPUT);

37.   

38.          return 0x00000014;

39.      }

40.      else if(!strcmp(path, '/led5'))

41.      {

42.          // 底板 LED

43.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO23_UARTRTS0, GPIO_NORMAL_ENABLED);

44.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO23_UARTRTS0, GPIO_DIR_OUTPUT);

45.   

46.          return 0x00000015;

47.      }

48.      else if(!strcmp(path, '/leds'))

49.      {

50.          // 核心板 LED

51.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO26_UARTCTS1, GPIO_NORMAL_ENABLED);

52.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO27_UARTRTS1, GPIO_NORMAL_ENABLED);

53.   

54.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO26_UARTCTS1, GPIO_DIR_OUTPUT);

55.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO27_UARTRTS1, GPIO_DIR_OUTPUT);

56.   

57.          // 底板 LED

58.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO19_TIMO1,    GPIO_NORMAL_ENABLED);

59.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO22_UARTCTS0, GPIO_NORMAL_ENABLED);

60.          GPIOPinMuxSet(SOC_DSC_BASE_REGS + SOC_DSC_PIN_CONTROL_0, GPIO23_UARTRTS0, GPIO_NORMAL_ENABLED);

61.   

62.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO19_TIMO1,    GPIO_DIR_OUTPUT);

63.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO22_UARTCTS0, GPIO_DIR_OUTPUT);

64.          GPIODirModeSet(SOC_GPIO_0_REGS, GPIO23_UARTRTS0, GPIO_DIR_OUTPUT);

65.   

66.          return 0x00000020;

67.      }

68.      else

69.      {

70.          return -1;

71.      }

72.  }

73.   

74.  int LED_close(int fno)

75.  {

76.   

77.      return 0;

78.  }

79.   

80.  int LED_read(int fno, char *buffer, unsigned count)

81.  {

82.   

83.      return 0;

84.  }

85.   

86.  int LED_write(int fno, const char *buffer, unsigned count)

87.  {

88.      switch(fno)

89.      {

90.          case 0x11 : *buffer ? GPIOPinWrite(SOC_GPIO_0_REGS, GPIO26_UARTCTS1, GPIO_PIN_HIGH) : \

91.                              GPIOPinWrite(SOC_GPIO_0_REGS, GPIO26_UARTCTS1, GPIO_PIN_LOW); break;

92.          case 0x12 : *buffer ? GPIOPinWrite(SOC_GPIO_0_REGS, GPIO27_UARTRTS1, GPIO_PIN_HIGH) : \

93.                              GPIOPinWrite(SOC_GPIO_0_REGS, GPIO27_UARTRTS1, GPIO_PIN_LOW); break;

94.   

95.          case 0x13 : *buffer ? GPIOPinWrite(SOC_GPIO_0_REGS, GPIO19_TIMO1, GPIO_PIN_HIGH) : \

96.                              GPIOPinWrite(SOC_GPIO_0_REGS, GPIO19_TIMO1, GPIO_PIN_LOW); break;

97.   

98.          case 0x14 : *buffer ? GPIOPinWrite(SOC_GPIO_0_REGS, GPIO22_UARTCTS0, GPIO_PIN_HIGH) : \

99.                              GPIOPinWrite(SOC_GPIO_0_REGS, GPIO22_UARTCTS0, GPIO_PIN_LOW); break;

100. 

101.        case 0x15 : *buffer ? GPIOPinWrite(SOC_GPIO_0_REGS, GPIO23_UARTRTS0, GPIO_PIN_HIGH) : \

102.                            GPIOPinWrite(SOC_GPIO_0_REGS, GPIO23_UARTRTS0, GPIO_PIN_LOW); break;

103. 

104.        case 0x20 : GPIOPinWrite(SOC_GPIO_0_REGS, GPIO26_UARTCTS1, (*buffer >> 0) & 0x01);

105.                   GPIOPinWrite(SOC_GPIO_0_REGS, GPIO27_UARTRTS1, (*buffer >> 1) & 0x01);

106.                   GPIOPinWrite(SOC_GPIO_0_REGS, GPIO19_TIMO1,    (*buffer >> 2) & 0x01);

107.                   GPIOPinWrite(SOC_GPIO_0_REGS, GPIO22_UARTCTS0, (*buffer >> 3) & 0x01);

108.                   GPIOPinWrite(SOC_GPIO_0_REGS, GPIO23_UARTRTS0, (*buffer >> 4) & 0x01); break;

109.    }

110. 

111.    return 0;

112.}

113. 

114.off_t LED_lseek(int fno, off_t offset, int origin)

115.{

116. 

117.    return 0;

118.}

119. 

120.int LED_unlink(const char *path)

121.{

122. 

123.    return 0;

124.}

125. 

126.int LED_rename(const char *old_name, const char *new_name)

127.{

128. 

129.    return 0;

130.}

可能通过这种方式操作硬件,最合适的外设就是 EEPROM / Flash 这种存储类型的外设。

广州创龙,你身边的主板定制专家!

每周一堂课,动动手指头

微信ID:tronlong
(0)

相关推荐

  • RT-Thread上设备SPI移植与实践

    作为通信协议的两大基础,IIC和SPI两者的应用都非常广泛,上一篇文章讲过了RTT上IIC的移植与实践. <一步到位!教你RT-Thread上设备IIC驱动移植> 讲完IIC,自然少不了S ...

  • STM32的cubemx生成的工程中头文件的包含关系

    举例说明: 上图是通过cubemx生成的工程,文件的包含关系如下: 1.  main.c文件开头就包含了main.h   iwdg.h  gpio.h, 由于 iwdg.h 和gpio.h中都对应声明 ...

  • 从这些知识点入手,学单片机就简单多了

    自从CubeMX等图像配置软件的出现,同学们往往点几下鼠标就解决了单片机的配置问题.对于追求开发速度的业务场景下,使用快速配置软件是合理的,高效的,但对于学生的学习场景下,更为重要的是知其然并知其所以 ...

  • 干货 | 在STM32F746-Disco 上跑 Basic 体验 AppleⅡ

    在STM32F746-Disco上跑Basic.就是直接在STM32F746-Disco开发板上运行MMBasic解释器,可以执行标准的Basic命令.函数计算.图形显示.GPIO控制.串口.SPI. ...

  • 【博文连载】STM32F407ZET6的RESET管脚与GND短路问题

    串口通信是一种设备间非常常用串行通信(数据交互)方式,因为它比较简单便捷,大部分电子设备支持串口通信方式,电子工工程师调试设备时常用的接口: 串行通信:占用I/O较少,速度上较慢点 并行通信:占用I/ ...

  • STM32的复用时钟何时开启呢?

    STM32的AFIO时钟真的是在开启引脚复用功能的时候开启吗?其实并不是~ 什么是复用? 我们知道,STM32有很多外设,这些外设的外部引脚都是与GPIO共用的.我们可以通过软件来配置引脚作为GPIO ...

  • 【Linux笔记】Pinctrl子系统与GPIO子系统

    前言 之前我们已经通过几篇笔记来学习点灯了: [Linux笔记]LED驱动程序 [Linux笔记]LED驱动实验(总线设备驱动模型) [Linux笔记]设备树实例分析 但之前的点灯实验我们都得去跟一些 ...

  • STM32端口复用和重映射(AFIO辅助功能时钟) | MCU加油站

    端口复用功能 端口复用的定义 STM32有许多的内置外设(如串口.ADC.DCA等等),这些外设的外部引脚都是和GPIO复用的.也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPI ...

  • STM8S103固件库安装

    注意这个固件的名字,STM8S的专属名字 众所周知,单片机的开发其实就是底层驱动的开发,就是控制寄存器的.随着MCU功能的强大,去配置每一个寄存器来开发的模式不太妙,毕竟不是每一款MCU的寄存器都像是 ...

  • 酒瓶今已作花瓶——南宋龙泉窑梅瓶小识 

    诗案自应留笔砚, 书窗谁不对梅瓶. 南宋 韩淲<上饶新刊巩宋齐六言寄赵晏叟者次韵上饶> 石几梅瓶添水活, 地炉茶鼎煮泉新. 宋 方逢振<风潭精舍月夜偶成> 调朱旋滴梅瓶水, 读 ...

  • 海昏侯墓竹简《诗经·小雅·我行其野》小识

    朱凤瀚先生在<海昏竹书〈诗〉初读>一文中公布了<小雅·我行其野>的释文,[1]读后随手作了一则笔记,今整理出来以飨同好. 先列个表把海昏竹简本和毛诗本略作对照: 海昏竹简本 毛 ...

  • “木得桂则枯”小识

    "木得桂则枯"小识 "木得桂则枯",见于诸多本草记载.究其由来,可追溯如下: 1.<尔雅>:梫,木桂也,言其侵害诸木. 2.<吕氏春秋> ...

  • 石头识堂开课啦!

    有许多同学曾向小编提问: 哎?地理书上那些石头的图片那么好看,怎么和我们平时在生活中看到的不一样呢? 今天小编就带着大家看看这些石头在野外长什么样,和手标本大小差不多的时候是什么样,在显微镜下看到的又 ...

  • 穴位后面和左右石墩环立,小明堂石墩石桩环围的奇妙地!

    穴位后面和左右石墩环立,小明堂石墩石桩环围的奇妙地!

  • 贾平凹《河南巷小识》原文及赏析

    河南巷小识 在我们西安,河南人占了三分之一,城内三个大区:莲湖,碑林,新城;新城几乎要成为河南的省城了.他们是20年代开始向这里移居的;半个世纪以来,黄河使他们得幸,也使他们受害,水的灾祸培养了他们开 ...

  • 贾平凹《陕西小吃小识录》原文及赏析

    陕西小吃小识录 序 世说,"南方人细致,北方人粗糙."而西北人粗之更甚.言语滞重,字多去声,膳馔保持食物原色,轻糖重盐,故男人少白脸,女人无细腰.此水土造化的缘故啊.今陕西省域,北 ...

  • BIM软件小技巧:Revit定向到视图的应用

    BIM软件小技巧:Revit定向到视图的应用        我们在三维进行浏览时,会经常需要单独去查看一些图元或者一层几层的图元.在Revit里面,有一个定向到视图的命令,可以快速的帮助到我们.   ...

  • 张小莲‖堂哥张兴贵

    我每次到西安,途经蓝田,望着路标上的"蓝田"二字,脑袋里立马涌出来一个身材魁梧,国字脸,五官端正,仪表堂堂,目光凌厉的汉子来.因为这个小城曾经住过我们村的精神领袖人物.我的堂哥张兴 ...