在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