在CLion中使用printf函数来调试STM32代码

原因

因为Keil中使用的是MDK\ARM\ARMCC\include这个目录下的stdio.h,而CLion中是不会链接到这个文件,所以就会导致之前Keil工程中的printf无法正常输出。在Clion中链接的是GNU-Tools-ARM-Embedded\arm-none-eabi\include里面的stdio.h,如果仍然想使用printf函数功能,则需要进行如下操作。

使用效果

配置过程

参考了稚晖君的配置教程,配置CLion用于STM32开发【优雅の嵌入式开发】

  首先,在项目根目录新建一个文件夹为Bsp/UART,在UART文件夹下新建如下文件(retarget.h/c,uart.h/c)。
新建了文件夹、源文件肯定要配置CMakelists.txt文件了,需要加上我们刚刚的目录,如下:

//我们可以观察到,只需用空格后包含我们新的文件夹路径即可。
include_directories(Core/Inc Drivers/STM32F1xx_HAL_Driver/Inc/Legacy Bsp/UART)
//还有
file(GLOB_RECURSE SOURCES "startup/*.*" "Middlewares/*.*" "Drivers/*.*" "Core/*.*" "Bsp/*.*")

注意!!!可能写好后CMake会自动更新,会添加一些不必要的代码而产生报错,如下:

//此部分后面可能会添加上Bsp文件夹的源文件,原因可能是因为第一次更新CMake自动产生的,后面就没有了,不知道是bug,还是我的细节问题。
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/STM32F103C8Tx_FLASH.ld)

新建一个retarget.h文件内容如下:

#ifndef _RETARGET_H__
#define _RETARGET_H__

#include "stm32f1xx_hal.h"
#include <sys/stat.h>
#include <stdio.h>
#include "stm32f1xx_hal_uart.h"

void RetargetInit(UART_HandleTypeDef *huart);

int _isatty(int fd);

int _write(int fd, char *ptr, int len);

int _close(int fd);

int _lseek(int fd, int ptr, int dir);

int _read(int fd, char *ptr, int len);

int _fstat(int fd, struct stat *st);

#endif //#ifndef _RETARGET_H__

新建retarget.c文件如下:

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <retarget.h>
#include <stdint.h>

#include "uart.h"

#if !defined(OS_USE_SEMIHOSTING)

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2



void RetargetInit(UART_HandleTypeDef *huart)
{
    gHuart = huart;

    /* Disable I/O buffering for STDOUT stream, so that
     * chars are sent out as soon as they are printed. */
    setvbuf(stdout, NULL, _IONBF, 0);
}

int _isatty(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 1;

    errno = EBADF;
    return 0;
}

int _write(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;

    if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
    {
        hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return len;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}

int _close(int fd)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
        return 0;

    errno = EBADF;
    return -1;
}

int _lseek(int fd, int ptr, int dir)
{
    (void) fd;
    (void) ptr;
    (void) dir;

    errno = EBADF;
    return -1;
}

int _read(int fd, char *ptr, int len)
{
    HAL_StatusTypeDef hstatus;

    if (fd == STDIN_FILENO)
    {
        hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
        if (hstatus == HAL_OK)
            return 1;
        else
            return EIO;
    }
    errno = EBADF;
    return -1;
}

int _fstat(int fd, struct stat *st)
{
    if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    {
        st->st_mode = S_IFCHR;
        return 0;
    }

    errno = EBADF;
    return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

在此编译后,会出现上面几个函数多重定义的问题,注释掉另一个文件重复的函数即可

新建uart.c文件:

#include "uart.h"

UART_HandleTypeDef *gHuart;
UART_HandleTypeDef  uart_handle;


uint8_t  rx_buffer[1] = {0};
uint8_t  uart1_rx_flag = 0;


void uart_init(uint32_t bound)
{
    uart_handle.Instance = USART1;
    uart_handle.Init.BaudRate = bound;
    uart_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    uart_handle.Init.Mode = UART_MODE_TX_RX;
    //uart_handle.Init.OverSampling = ;  //过采样,不需要
    uart_handle.Init.Parity = UART_PARITY_NONE;//不需要奇偶校验
    uart_handle.Init.StopBits = UART_STOPBITS_1;
    uart_handle.Init.WordLength = UART_WORDLENGTH_8B;
    //初始化
    HAL_UART_Init(&uart_handle);
    //初始化串口1接收中断
    HAL_UART_Receive_IT(&uart_handle,rx_buffer,1);

}

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;
    if(huart->Instance == USART1)                /* 如果是串口1,进行串口1 MSP初始化 */
    {
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        gpio_init_struct.Pin = GPIO_PIN_9;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 推挽式复用输出 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            /* 初始化串口1的TX引脚 */

        gpio_init_struct.Pin = GPIO_PIN_10;
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;         /* 输入 */
        gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            /* 初始化串口1的RX引脚 */

        HAL_NVIC_SetPriority(USART1_IRQn, 3, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}
/* 串口1中断服务函数 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&uart_handle);

    HAL_UART_Receive_IT(&uart_handle, (uint8_t*)rx_buffer, 1);
}

/* 串口数据接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        uart1_rx_flag = 1;
    }
}

新建uart.h文件:

#ifndef CLOCK_UART_H
#define CLOCK_UART_H
#include "stm32f1xx.h"
//#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_uart.h"

extern UART_HandleTypeDef *gHuart;
extern UART_HandleTypeDef  uart_handle;
void uart_init(uint32_t bound);


#endif //CLOCK_UART_H

在main.c文件中重定向串口就可以实现printf函数了,如下:

void main()
{
	//init
	 uart_init(115200);
  	RetargetInit(&uart_handle);
  	printf("printf function test.\r\n");

}

以上没有问题了,就可以正常输出了.不过有一说一,这个printf函数可能链接的东西太多了吧,可是真的占用Flash啊!!!
使用printf时函数占用的空间:

不使用printf函数占用的空间:

可以看到差不多多出了40%!!!44.8KB – 19.2KB = 25.6KB,一个printf函数就占用了25.6KB!!!后面我又同时加入scanf函数,发现直接150%,空间直接溢出了。。。所以我们在调试阶段可以使用printf函数,在正式运行阶段就不要使用printf函数了。

作者:xinhard

物联沃分享整理
物联沃-IOTWORD物联网 » 在CLion中使用printf函数来调试STM32代码

发表评论