Keil重定义fputc函数实现通过串口实现printf格式化输出

        printf的格式化输出可以输出一下几种格式化类型:

      %a(%A)     浮点数、十六进制数字和p-(P-)记数法(C99)
      %c             字符
      %d             有符号十进制整数
      %f              浮点数,.xf表示保留几位小数
      %e(%E)     浮点数指数输出[e-(E-)记数法]
      %g(%G)     浮点数不显示无意义的0
      %i              有符号十进制整数(%d)
      %u             无符号十进制整数
      %o             八进制整数 
      %x(%X)      十六进制整数
      %p             内存地址
      %s             字符串
      %%            "%"

       其中%#x等,即加入#号表示会有格式存在(单位),如打印16进制会自动加上0x...

        printf函数就是通过一次一次调用fputc函数完成对字符的格式化输出,调用一次就输出1字节ASCII码值。

        printf为默认屏幕输出,而sprintf为标准的输出流,sprintf(char * p,format[a,b,c,])可以选择格式化输出到哪个内存当中,再通过其他方式读取出来,如LCD\OLED屏幕显示格式化的实时数据。

本文章介绍两种方法:

1.1第一种:使用微库

        如下图所示,使用keil的微库。

                使用微库可直接重定义fputc即可,其中fputc可加入预编译条件来选择哪个串口来进行printf输出,实际需求按如下代码修改即可:

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((UART1->STATUS & (1<<1)) == 0);     /* 等待上一个字符发送完成 */
 
    UART1->TDR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}

1.2不使用微库:使用半主机模式

        不使用微库代码较为复杂,如下所示:

/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");   

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((UART1->STATUS & (1<<1)) == 0);     /* 等待上一个字符发送完成 */

    UART1->TDR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/******************************************************************************************/

        可加入条件编译实现通过宏定义定义使用哪一个串口作为printf的输出

<think>嗯,用户想知道如何在AT32F402芯片上通过串口实现printf功能。首先,我得回忆一下STM32上的类似实现,因为AT32可能和STM32的库函数类似。记得在STM32中,通常需要重定向fputc函数,这样printf才会输出串口。用户提到的参考引用里有个关于硬件IIC的部分,可能他们之前接触过STM32的相关知识,所以AT32F402可能使用类似的库结构。 首先,我需要确认AT32的库是否支持类似的重定向方法。一般来说,标准库中的printf函数会调用fputc,所以重写这个函数,将字符通过串口发送即可。用户需要初始化串口,设置正确的波特率、数据位、停止位等参数,然后确保USART的发送函数正确。比如,在STM32中,我们会用HAL_UART_Transmit,而AT32可能有自己的库函数,比如usart_data_transmit。 接下来要考虑的是是否启用微库(MicroLib),这在MDK-ARM中常见,可以减小代码体积。用户需要在IDE里设置,这部分可能需要指导他们如何勾选选项。另外,如果使用GCC或者其他编译器,可能需要不同的处理方式,比如使用__io_putchar函数。 然后,代码示例部分需要具体针对AT32F402,可能参考AT32的官方库示例。比如,初始化串口需要配置GPIO和USART的时钟,设置引脚复用,配置USART的参数。发送单个字符的函数需要被正确实现,并处理可能的传输完成等待,比如检查发送数据寄存器是否为空。 还要注意,用户是否需要使用查询方式或者中断方式,但printf通常使用阻塞式发送,所以查询方式更简单。在重定向fputc时,确保每次发送一个字符,并等待发送完成,避免数据覆盖。 可能的问题点包括:是否正确启用USART时钟,GPIO是否正确配置为复用功能,波特率计算是否正确,以及是否在工程设置中启用了微库。如果用户遇到printf没有输出,可能需要检查这些步骤,或者使用调试器查看是否程序运行到发送函数。 最后,生成的相关问题应该围绕这些潜在的问题点和扩展功能,比如重定向其他函数、提高效率的方法、波特率计算和中断发送等。确保用户能够进一步探索相关主题,解决可能遇到的困难。</think>在AT32F402芯片上通过串口实现`printf`功能,需通过重定向标准库函数实现。以下是具体步骤: --- ### 一、实现原理 1. **重定向`fputc`函数**:`printf`函数底层会调用`fputc`输出字符,通过重写该函数将字符发送至串口[^3]。 2. **初始化串口**:配置USART外设的GPIO、波特率、数据格式等参数。 --- ### 二、实现步骤 #### 1. 初始化串口(以USART1为例) ```c #include "at32f402_407.h" void USART1_Init(uint32_t baudrate) { // 开启GPIO和USART时钟 crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE); // 配置PA9(TX)为复用推挽输出 gpio_init_type gpio_init_struct; gpio_init_struct.gpio_pins = GPIO_PINS_9; gpio_init_struct.gpio_mode = GPIO_MODE_MUX; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init(GPIOA, &gpio_init_struct); // 配置USART参数 usart_init_type usart_init_struct; usart_default_para_init(&usart_init_struct); usart_init_struct.baudrate = baudrate; usart_init_struct.parity = USART_PARITY_NONE; usart_init_struct.word_length = USART_WORD_LENGTH_8B; usart_init_struct.stop_bits = USART_STOP_BITS_1; usart_init(USART1, &usart_init_struct); // 使能USART usart_enable(USART1, TRUE); } ``` #### 2. 重定向`fputc`函数 ```c #include <stdio.h> int fputc(int ch, FILE *f) { // 等待发送寄存器为空 while (usart_flag_get(USART1, USART_TDBE_FLAG) == RESET); // 发送字符 usart_data_transmit(USART1, (uint8_t)ch); return ch; } ``` #### 3. 启用MicroLib(仅限Keil MDK) - 在工程选项中勾选**Use MicroLIB**,以减小代码体积。 --- ### 三、使用示例 ```c int main(void) { USART1_Init(115200); // 初始化波特率为115200 printf("AT32F402 UART printf test\n"); // 通过串口输出 while (1); } ``` --- ### 四、注意事项 1. **波特率匹配**:确保PC端串口工具的波特率与代码设置一致。 2. **硬件连接**:检查TX引脚(PA9)与USB转串口模块的RX引脚连接。 3. **浮点数支持**:若需输出浮点数,需在工程设置中启用`Use float with printf`选项。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值