从0开始搭建基于ARM GCC的软件系统03——printf重定向

本文介绍如何在STM32上将printf语句重定向到指定UART端口,通过初始化LPUART1并配置GPIO及NVIC,重写写函数实现串口输出。提供Makefile链接参数及系统初始化代码,适用于AliOSThingsDeveloperKit。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如何让printf语句从指定的端口打印信息,本章节为你解锁……
 
一、直接上代码说明
我使用的AliOS Things Developer Kit,硬件上使用LPUART1(PB10/PB11)作为串口输出。
也可以根据具体情况配置其它UART。
 
UART_HandleTypeDef hlpuart1;

//初始化串口LPUART1
void MX_LPUART1_UART_Init(void)
{
    hlpuart1.Instance = LPUART1;
    hlpuart1.Init.BaudRate = 115200;
    hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
    hlpuart1.Init.StopBits = UART_STOPBITS_1;
    hlpuart1.Init.Parity = UART_PARITY_NONE;
    hlpuart1.Init.Mode = UART_MODE_TX_RX;
    hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    //hlpuart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    //hlpuart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&hlpuart1) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

//重写HAL_UART_MspInit
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if(uartHandle->Instance==LPUART1)
    {
        /* USER CODE BEGIN LPUART1_MspInit 0 */
        __HAL_RCC_GPIOB_CLK_ENABLE(); //特别注意,我参考的alios thing中代码,没有这句,导致串口没有输出,这里添加这句后就OK了

        /* USER CODE END LPUART1_MspInit 0 */
        /* LPUART1 clock enable */
        __HAL_RCC_LPUART1_CLK_ENABLE();

        /**LPUART1 GPIO Configuration    
        PB10     ------> LPUART1_RX
        PB11     ------> LPUART1_TX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* LPUART1 interrupt Init */
        HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(LPUART1_IRQn);
        /* USER CODE BEGIN LPUART1_MspInit 1 */

        /* USER CODE END LPUART1_MspInit 1 */
    }
}

// 重写write函数, printf时最终会调用这个函数。这里类似keil ARMCC中的fputc函数
int _write(int file, char *ptr, int len)
{
    int i=0;
    
    for (i = 0; i < len; i++)
    {
        while (!(LPUART1->ISR & USART_ISR_TXE))
        {
        }
        LPUART1->TDR = ptr[i];
    }
    return len;
}

// main函数实现
int main(void)
{

    HAL_Init();

    /* Configure the System clock to have a frequency of 80 MHz */
    SystemClock_Config();

    /* Add your application code here
     */
    MX_LPUART1_UART_Init();
    
    /* Infinite loop */
    while (1)
    {
        printf("Hello world!\n");
        fflush(stdout); //如果printf函数最后不添加"\n",信息不会打印出来,所以这里需要添加这一句

        /* Insert delay 500 ms */
        HAL_Delay(500);
    }
}

// 以下为系统初始化代码,保证系统能运行即可,与printf重定向无关,仅供参考
/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow :
  *            System Clock source            = PLL (MSI)
  *            SYSCLK(Hz)                     = 80000000
  *            HCLK(Hz)                       = 80000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 1
  *            APB2 Prescaler                 = 1
  *            MSI Frequency(Hz)              = 4000000
  *            PLL_M                          = 1
  *            PLL_N                          = 40
  *            PLL_R                          = 2
  *            PLL_P                          = 7
  *            PLL_Q                          = 4
  *            Flash Latency(WS)              = 4
  * @param  None
  * @retval None
  */
static void SystemClock_Config(void)
{

    RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_OscInitTypeDef RCC_OscInitStruct;

  /* MSI is enabled after System reset, activate PLL with MSI as source */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 40;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLP = 7;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    /* Initialization Error */
    while(1);
  }
  
  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
     clocks dividers */
  RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;  
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;  
  if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    /* Initialization Error */
    while(1);
  }
}

 

二、Makefile注意事项
需要用到一些lib。libc.a\libgcc.a等
在使用 arm gcc 编译链接的过程可能会出现错误undefined reference to  `_sbrk'   `_close'  `_fstat'  `_read' `_write' 。
添加以下链接参数:
-std=gun99
--specs=nano.specs //使用静态库 libc_nano.a
--specs=nosys.specs //使用静态库 libnosys.a
 
我使用的编译参数参考如下:
# 使用了编译优化和硬件浮点数
CFLAGS += -mcpu=$(CPU) -mthumb -Wall
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += -Os
CFLAGS += -ffunction-sections -fdata-sections -std=c99

LFLAGS += -mcpu=$(CPU) -mthumb
LFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
LFLAGS += -Wl,--gc-sections
# link libgcc.a libc.a libm.a
LFLAGS += -lgcc -lc -lm --specs=nosys.specs --specs=nano.specs -std=c99
# generate map file
LFLAGS += -Wl,-Map,$(TARGET).map
 
 
三、参考文档
stm32 基于ARM GCC Compliler(EmBitz IDE) print重定向到串口打印输出 com serial https://blog.youkuaiyun.com/daxiebao/article/details/52651891
STM32高级开发(12)-在GCC中使用printf打印串口数据 https://blog.youkuaiyun.com/zhengyangliu123/article/details/54966402
 
<think>好的,我现在需要回答用户关于在STM32中使用HAL库和GCC编译器实现printf重定向的问题。首先,我得回忆一下相关的知识。用户提到他们使用的是GCC编译器,比如arm-none-eabi-gcc,而之前参考了不同编译器的实现方法,比如Keil的MDK和CubeIDE。根据提供的引用资料,GCC和MDK在重定向printf时有不同的底层函数需要覆盖。例如,GCC可能需要使用_write函数,而MDK则使用fputc。 首先,我需要确认在GCC环境下,标准库中printf的输出依赖于哪个底层函数。根据引用[2],GCC使用_write函数来进行输出重定向。用户提供的引用[1]和[2]中的代码示例都是通过实现_write函数来完成的,而引用[3]中的MDK使用的是fputc。因此,正确的做法应该是覆盖_write函数。 接下来,步骤应该是:包含stdio.h头文件,然后实现_write函数,在该函数中调用HAL_UART_Transmit来发送数据到串口。需要注意的是,文件描述符fd在标准输出情况下通常是1,但为了保险起见,可以处理不同的fd或者只处理标准输出。不过根据示例代码,用户可能不需要处理其他fd,所以直接调用HAL库函数发送即可。 然后,需要确保链接时标准库能够链接到用户实现的_write函数,可能需要关闭某些优化或者设置相关的选项。但根据引用[1]和[2],似乎直接实现_write即可,不需要额外的配置,特别是在使用GCC时。 另外,用户可能还需要启用微库(newlib-nano)或者调整标准库的设置,但引用中没有提到这一点,可能默认情况下GCC已经配置好了。不过,有时在工程配置中需要链接特定的库,比如在CubeIDE中可能需要设置链接器参数。但用户使用的是Makefile,所以需要在Makefile中确保链接到正确的库。 另外,需要检查串口是否已经正确初始化,比如huart1是否已经配置好,并且在代码中正确声明了外部变量。例如,在main.c中声明extern UART_HandleTypeDef huart1;。 最后,测试代码是否正确,可以通过printf发送字符串到串口,用串口调试工具查看输出。如果出现卡死,可能需要检查HAL_UART_Transmit的超时时间是否正确,或者是否有硬件问题。 总结步骤: 1. 包含头文件stdio.h。 2. 实现_write函数,调用HAL_UART_Transmit发送数据。 3. 确保串口初始化正确。 4. 在代码中使用printf进行测试。 同时,用户的问题可能需要对比不同编译器的实现,比如GCC和MDK的区别,以及CubeIDE中的方法。例如,引用[4]提到CubeIDE中使用__io_putchar,这可能是因为CubeIDE使用了不同的标准库实现,或者不同的链接方式。但用户这里明确使用GCC,所以应遵循_write的实现方式。 可能需要注意的地方是,HAL_UART_Transmit的第三个参数是数据长度,而ptr指向的字符数组需要发送len字节。但每次调用_write时,ptr可能是一个字符数组,比如当printf输出多个字符时,len可能大于1。因此,HAL_UART_Transmit应该正确发送整个缓冲区的内容,而不仅仅是单个字符。例如,在引用[2]中,HAL_UART_Transmit的第三个参数是len,即发送整个缓冲区的内容。而在MDK的例子中,使用的是逐个字符发送的方式,可能因为fputc每次只处理一个字符。 此外,返回值的处理也很重要,需要返回实际发送的字节数,这里是len,因为HAL_UART_Transmit在超时时间内会发送所有字节,所以返回len即可。 最后,可能还需要在工程设置中启用浮点数支持,如果用户需要printf支持浮点的话,因为默认情况下GCC可能没有启用,需要添加-u _printf_float等链接选项。但这个问题可能超出当前问题的范围,用户可能需要注意这一点。</think>### 如何在STM32中使用HAL库和GCC实现`printf`重定向? 在STM32开发中,通过重定向`printf`函数可以将标准输出定向到串口,便于调试。以下是基于**HAL库**和**GCC编译器**(如`arm-none-eabi-gcc`)的实现步骤: --- #### **步骤1:添加头文件与函数声明** 在代码文件(如`main.c`)中引入标准输入输出库和HAL库: ```c #include <stdio.h> #include "stm32f1xx_hal.h" // 根据具体型号调整 extern UART_HandleTypeDef huart1; // 假设使用UART1 ``` --- #### **步骤2:重定向`_write`函数** GCC编译器通过`_write`函数实现底层输出,需重写该函数以调用HAL库的串口发送接口[^1][^2]: ```c int _write(int fd, char *ptr, int len) { // 仅处理标准输出(fd=1)和标准错误(fd=2) if (fd == 1 || fd == 2) { HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF); // 阻塞发送 } return len; // 返回实际发送的字节数 } ``` --- #### **步骤3:配置串口** 在STM32CubeMX中初始化UART,并确保: 1. 波特率、数据位、停止位等参数与接收端一致。 2. 生成代码后,检查`huart1`的实例化是否在对应文件中(如`usart.c`)。 --- #### **步骤4:测试输出** 在代码中使用`printf`发送数据: ```c printf("Hello, STM32!\n"); // 自动通过UART1输出 ``` --- #### **注意事项** 1. **浮点数支持**:若需`printf`支持浮点(如`%.2f`),需在编译时添加链接选项: ```makefile LDFLAGS += -u _printf_float # 在Makefile中添加 ``` 2. **超时处理**:`HAL_UART_Transmit`的最后一个参数为超时时间(单位ms),设为`0xFFFF`表示阻塞直到发送完成。 3. **多编译器差异**: - **Keil(ARM Compiler)**需重写`fputc`[^3]。 - **STM32CubeIDE(GCC)**需重写`__io_putchar`[^4]。 --- #### **示例代码对比** | 编译器 | 需重写的函数 | 发送方式 | |-----------------|---------------------------|-----------------------------| | GCC | `_write` | 直接发送缓冲区 | | Keil (ARM CC) | `fputc` | 逐字符发送 | | STM32CubeIDE | `__io_putchar` | 逐字符发送 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值