[技术手册] CW32L052串口应用之多个串口重定义实现printf输出

[复制链接]
 楼主| lulugl 发表于 2023-7-18 13:44 | 显示全部楼层 |阅读模式
本帖最后由 lulugl 于 2023-7-18 13:52 编辑

#申请原创# #有奖活动#@21小跑堂

一、前言
什么是重定向?重定向是指将fputc里面的输出指向目标设备。因printf函数调用了fputc,而fputc输出有默认指向的目标,且不同库中的fputc输出指向不同,所以需要重写fputc标准库实现重定向到串口
二、若需要printf输出到串口,则需要将fputc里面的输出指向串口,这一过程称为重定向。在我们的CW32L052_StandardPeripheralLib_V1.0的demo中官方给出了我们在mdk下面的串口重定向的示例,路径为\CW32L052_StandardPeripheralLib_V1.0\Utilities。其log.c中定义来设置了板载的UART2为printf输出串口。根据原理图其TXD与RXD分别为PD02与PC12。
f340d32524f2934b702d643f7f833a3f
在log.c中函数static void SerialInit(uint32_t BaudRate)为初始化指定的波特率的UART2,代码如下:
  1. static void SerialInit(uint32_t BaudRate)

  2. {

  3. uint32_t PCLK_Freq;

  4. GPIO_InitTypeDef GPIO_InitStructure = {0};

  5. UART_InitTypeDef UART_InitStructure = {0};

  6. PCLK_Freq = SystemCoreClock >> pow2_table[CW_SYSCTRL->CR0_f.HCLKPRS];

  7. PCLK_Freq >>= pow2_table[CW_SYSCTRL->CR0_f.PCLKPRS];

  8. // 调试串口使用UART2

  9. // PC12->TX

  10. // PD02<-RX

  11. // 时钟使能

  12. __RCC_GPIOD_CLK_ENABLE();

  13. __RCC_GPIOC_CLK_ENABLE();

  14. __RCC_UART2_CLK_ENABLE();

  15. // 先设置UART TX RX 复用,后设置GPIO的属性,避免口线上出现毛刺

  16. PD02_AFx_UART2RXD();

  17. PC12_AFx_UART2TXD();

  18. GPIO_InitStructure.Pins = GPIO_PIN_12;

  19. GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;

  20. GPIO_Init(CW_GPIOC, &GPIO_InitStructure);

  21. GPIO_InitStructure.Pins = GPIO_PIN_2;

  22. GPIO_InitStructure.Mode = GPIO_MODE_INPUT;

  23. GPIO_Init(CW_GPIOD, &GPIO_InitStructure);

  24. UART_InitStructure.UART_BaudRate = BaudRate;

  25. UART_InitStructure.UART_Over = UART_Over_16;

  26. UART_InitStructure.UART_Source = UART_Source_PCLK;

  27. UART_InitStructure.UART_UclkFreq = PCLK_Freq;

  28. UART_InitStructure.UART_StartBit = UART_StartBit_FE;

  29. UART_InitStructure.UART_StopBits = UART_StopBits_1;

  30. UART_InitStructure.UART_Parity = UART_Parity_No;

  31. UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;

  32. UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;

  33. UART_Init(CW_UART2, &UART_InitStructure);

  34. }

  35. 函数static void SerialSend(uint8_t Data)的功能为发送一个字节

  36. static void SerialSend(uint8_t Data)

  37. {

  38. UART_SendData_8bit(CW_UART2, Data);

  39. while (UART_GetFlagStatus(CW_UART2, UART_FLAG_TXE) == RESET);

  40. }

【串口重定向】在最后的fputc中重构了标准输出函数。
  1. int fputc(int ch, FILE *f)
  2. {
  3.     SerialSend(ch);
  4.     return ch;
  5. }

然后重写的pirntf(即write),其代码如下:
  1. size_t __write(int handle, const unsigned char * buffer, size_t size)
  2. {   
  3.     size_t nChars = 0;

  4.     if (buffer == 0)
  5.     {
  6.         /*
  7.          * This means that we should flush internal buffers.  Since we
  8.          * don't we just return.  (Remember, "handle" == -1 means that all
  9.          * handles should be flushed.)
  10.          */
  11.         return 0;
  12.     }


  13.     for (/* Empty */; size != 0; --size)
  14.     {
  15.         SerialSend(*buffer++);
  16.         ++nChars;
  17.     }

  18.     return nChars;
  19. }


【注意】
1、调用printf需要先导入stdio.h头文件。
2、在MDK的设置中需要勾选Target中的use MicroLIB选项
6e73c3862fbb019b22c18941c7867db9
如果我们在gcc的编译环境下,那printf的重定向函数需要修改为:
  1.    int _write (int fd, char *pBuffer, int size)  
  2.     {  
  3.     for (int i = 0; i < size; i++)  
  4.     {  
  5. SerialSend(*buffer++);
  6.     }  
  7.     return size;  
  8.     }



以上就是串口重定向固件的串口的实现。我们就可以实现用printf("%d%s",num,string)来实现打印。

三、如果我们需要同时操作一个,或者多个串口,比如同时使用UART2来做日志打印,UART1来与WIFI模块通讯,UART3用来与NB-IOT来通信,同时我也需要用printf来组装参数,那么就必需使用到多串口的重定向功能。下面介绍如何实现这个功能的方法:
1、为了实现功能,我们需要用到C标准库中的stdarg.h这个库。
stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。可变参数的函数通在参数列表的末尾是使用省略号(,...)定义的。
其中val_list 是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型。
  • void va_start(va_list ap, last_arg)
这个宏初始化 ap 变量,它与 va_arg 和 va_end 宏是一起使用的。last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数。
  • type va_arg(va_list ap, type)
这个宏检索函数参数列表中类型为 type 的下一个参数。
  • void va_end(va_list ap)
这个宏允许使用了 va_start 宏的带有可变参数的函数返回。如果在从函数返回之前没有调用 va_end,则结果为未定义。
2、先定义 需要用到串口的和发送字符串口组数:
  1. #define UART1_TXBUFF_SIZE 128
  2. __align(8) char Uart1_TxBuff[UART1_TXBUFF_SIZE];


3、新建u1_printf函数,流程为先创建一个valist 变量ap,然后获取可变参数地址 fmt地址赋给ap,使用参数列表发送格式化输出到字符串,函数功能将可变参数格式化输出到一个字符数组。最近通过指定的UART_SendString函数发送到指定的串口。代码如下:
  1. void u1_printf(char* fmt,...)
  2. {
  3.         va_list ap;
  4.        
  5.         //va_list 可变参数列表,存参数地址
  6.         va_start(ap, fmt); //获取可变参数地址 fmt地址赋给ap
  7.        
  8.         /*
  9.         * 使用参数列表发送格式化输出到字符串,函数功能将可变参数格式化输出到一个字符数组
  10.        
  11.         */
  12.         vsprintf(Uart1_TxBuff, fmt, ap);
  13.        
  14.         va_end(ap); //清空参数列表
  15.        
  16.         UART_SendString(CW_UART1, Uart1_TxBuff);

  17. }

这样,我们可以创建多个u1_prntf的函数来实现多个串口定向的功能了。
【测试】
我们在主程序中添加u1_printf("rcv:%s\r\n",uart1_rx_buff);编译下载后就可以实现基功能了:

65d9c32b61a962001561cd71534dc943



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

本版积分规则

180

主题

830

帖子

12

粉丝
快速回复 在线客服 返回列表 返回顶部

180

主题

830

帖子

12

粉丝
快速回复 在线客服 返回列表 返回顶部