打印
[技术手册]

CW32L052串口应用之多个串口重定义实现printf输出

[复制链接]
336|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
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。

在log.c中函数static void SerialInit(uint32_t BaudRate)为初始化指定的波特率的UART2,代码如下:
static void SerialInit(uint32_t BaudRate)

{

uint32_t PCLK_Freq;

GPIO_InitTypeDef GPIO_InitStructure = {0};

UART_InitTypeDef UART_InitStructure = {0};

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

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

// 调试串口使用UART2

// PC12->TX

// PD02<-RX

// 时钟使能

__RCC_GPIOD_CLK_ENABLE();

__RCC_GPIOC_CLK_ENABLE();

__RCC_UART2_CLK_ENABLE();

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

PD02_AFx_UART2RXD();

PC12_AFx_UART2TXD();

GPIO_InitStructure.Pins = GPIO_PIN_12;

GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_Init(CW_GPIOC, &GPIO_InitStructure);

GPIO_InitStructure.Pins = GPIO_PIN_2;

GPIO_InitStructure.Mode = GPIO_MODE_INPUT;

GPIO_Init(CW_GPIOD, &GPIO_InitStructure);

UART_InitStructure.UART_BaudRate = BaudRate;

UART_InitStructure.UART_Over = UART_Over_16;

UART_InitStructure.UART_Source = UART_Source_PCLK;

UART_InitStructure.UART_UclkFreq = PCLK_Freq;

UART_InitStructure.UART_StartBit = UART_StartBit_FE;

UART_InitStructure.UART_StopBits = UART_StopBits_1;

UART_InitStructure.UART_Parity = UART_Parity_No;

UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;

UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;

UART_Init(CW_UART2, &UART_InitStructure);

}

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

static void SerialSend(uint8_t Data)

{

UART_SendData_8bit(CW_UART2, Data);

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

}

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

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

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


    for (/* Empty */; size != 0; --size)
    {
        SerialSend(*buffer++);
        ++nChars;
    }

    return nChars;
}


【注意】
1、调用printf需要先导入stdio.h头文件。
2、在MDK的设置中需要勾选Target中的use MicroLIB选项

如果我们在gcc的编译环境下,那printf的重定向函数需要修改为:
   int _write (int fd, char *pBuffer, int size)  
    {  
    for (int i = 0; i < size; i++)  
    {  
SerialSend(*buffer++);
    }  
    return size;  
    }



以上就是串口重定向固件的串口的实现。我们就可以实现用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、先定义 需要用到串口的和发送字符串口组数:
#define UART1_TXBUFF_SIZE 128
__align(8) char Uart1_TxBuff[UART1_TXBUFF_SIZE];


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

}

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





使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

144

主题

693

帖子

8

粉丝