小识堂 | 重新定向 printf 输出到串口 ,你知多少??? - 创龙小识堂 - 嵌入式开发者社区 - 51ele.net
设为首页收藏本站

嵌入式开发者社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 5132|回复: 0

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

[复制链接]

61

主题

61

帖子

677

积分

创龙

Rank: 8Rank: 8

积分
677
发表于 2017-4-14 16:59:58 | 显示全部楼层 |阅读模式
重新定向 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 <stdio.h>;
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、printf 输出到 CCS Console
     
1.   #include <stdio.h>
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(;;) { } 循环是为了避免程序退出。如果程序退出会出现如下的提示,注意这个是提示,既不是警告更不是错误。
    1.png
       该提示是说找不到当前执行语句(图中是 /tmp/TI_MKLIBps1kJb/SRC/exit.c 这个文件,一般出现在 DSP 程序执行完成后也就是 main 函数返回后)所对应的源码(在这里,它只是一个 for 无限循环而已)。可以按下图操作找到源码。
点击定位文件...(Locate file...)按钮
    2.png
       在弹出的对话框定位到编译工具链的安装路径,图示路径为 D:\Project\Ti\c6000_7.4.16\lib\src。
   3.png
        可以看到 DSP 程序运行在 _CODE_ACCESS void abort(void) 函数中的    for (;;);   /* CURRENTLY, THIS SPINS FOREVER */ 语句。
   4.png



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 <file.h>
int open(const char * path , unsigned flags , int file_descriptor);

  
关闭文件 I/O
语法
   
#include <file.h>
int close (int file_descriptor );
  
从文件读取字符
语法
   
#include <file.h>
关闭文件 I/O
语法
#include <file.h>
int close (int file_descriptor );

  
写字符到文件
语法
   
#include <file.h>
int write (int file_descriptor , const char * buffer , unsigned count );
  
设置文件指针位置
语法
   
#include <file.h>
off_t lseek (int file_descriptor , off_t offset , int origin );
  
删除文件
语法
   
#include <file.h>
    int unlink (const char * path );
  
重命名文件
语法
   
#include <file.h>
    int rename (const char * old_name , const char * new_name );



     
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 被输出到了串口。
   5.png
        如果直接通过文件 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 这种存储类型的外设。     

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|嵌入式开发者社区 ( 粤ICP备15055271号

GMT+8, 2024-4-19 06:26 , Processed in 0.048611 second(s), 30 queries .

Powered by Discuz! X3.2

© 2001-2015 Comsenz Inc.

快速回复 返回顶部 返回列表