STM32 printf 方法重定向到串口UART
在嵌入式系统中调试代码是很麻烦的一件事, 如果能方便地输出调试信息(与调试者交互), 能使极大加快问题排查的过程. 串口在嵌入式领域是一个比较重要的通讯接口. 因为没有显示设备, 在单片机的程序里调用printf()打印内容是不可见的,但我们可以利用它的外设来实现printf(),比如串口, 串口基本上大多数单片机都有, 通常用串口来打印内容. 通过重写fputc()函数来实现. fputc()是printf()的底层函数, 通过它把要打印的数据发送到串口上去.
Keil ARM 编译环境
不使用 MicroLib的普通方式
- 禁用半主机模式, 禁用了半主机模式才能使用标准库函数printf()打印信息到串口
说明: 半主机模式是ARM单片机的一种调试机制,跟串口调试不一样,它需要通过仿真器来连接电脑,并调用相应的指令来实现单片机向电脑显示器打印信息(或者从电脑键盘读取输入)。这种方法比串口调试更复杂, 需要用仿真器实现. - include头文件 #include "stdio.h"
- 重写 fputc方法
- 重新定义
__FILE, __stdout, __stdin这三个变量, 以及重写_sys_exit()
#pragma import(__use_no_semihosting_swi)
// Change it if you use different USART port
#define USARTx USART1
struct __FILE { int handle; };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f) {
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET){}
USART_SendData(USARTx, ch);
return(ch);
}
int fgetc(FILE *f) {
char ch;
while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET){}
ch = USART_ReceiveData(USARTx);
return((int)ch);
}
int ferror(FILE *f) {
return EOF;
}
void _ttywrch(int ch) {
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}
USART_SendData(USARTx, ch);
}
void _sys_exit(int return_code) {
while (1); /* endless loop */
}
使用 MicroLib (micro-library)
如果使用了keil uvsion开发环境, 可以用microlib简化这一过程. MicroLib是一个定制(精简)的stdio替代库, 提供无缓冲的stdin, stdout 和 stderr,当使用微库时,就默认关闭了半主机模式, 不需要#pragma注释. 使用MicroLib之后, 只需要修改fputc()使其重定向
1. 在KEIL-MDK中开启 Use MicroLIB 选项
打开配置面板, 定位到Target标签页, 勾选Use MicroLIB.
2. 将 fputc 方法的输出重定向
在 MicroLib 的 stdio.h 头文件中, fputc() 方法的prototype为
int fputc(int ch, FILE* stream)
这个方法原本是将ch输出到strem这个文件类型指针指向的文件, 现在将其替换为串口1
#include <stdio.h>
int fputc(int ch, FILE* stream)
{
USART_SendChar(USART1, (uint8_t)ch);
return ch;
}
3. 重写fgetc方法
同样的
/*
** Rewrite fgetc function and make scanf function work
**/
int fgetc(FILE* file)
{
while((USART1->ISR & UART_IT_RXNE) == RESET);
return USART1->RDR;
}
注意要include stdio.h, 否则会报FILE类型未定义.
ARM GCC 环境 gcc-arm-none-eabi
重写 __io_putchar
在对printf重定向之前,不能调用printf
在main函数之前加上如下代码对串口进行重定向,当然,串口一定要初始化之后再用printf,否则程序虽然不会飞,但是printf也不会有结果
下面代码公共使用的宏判断, 注意引用头文件#include "stdio.h"
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
下面的三种实现根据使用的库选择即可
SPL库实现
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART1, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{}
return ch;
}
HAL库实现
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
寄存器实现
PUTCHAR_PROTOTYPE
{
//具体哪个串口可以更改USART1为其它串口
while ((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
USART1->DR = (uint8_t) ch;
return ch;
}
重写 _write
SPL库实现
void USART_Put(uint8_t ch)
{
#if (DEBUG == DEBUG_UART1)
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
#elif (DEBUG == DEBUG_UART2)
USART_SendData(USART2, (uint8_t)ch);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
#elif (DEBUG == DEBUG_UART3)
USART_SendData(USART3, (uint8_t)ch);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
#endif
}
#if defined(__GNUC__)
int _write(int file, char *ptr, int len) {
int n = len;
switch (file) {
case STDOUT_FILENO: /* stdout */
case STDERR_FILENO: /* stderr */
while (len--) {
USART_Put(*ptr++ & (uint16_t)0x01FF);
}
break;
default:
errno = EBADF;
return -1;
}
return n;
}
#elif defined (__ICCARM__)
#include "LowLevelIOInterface.h"
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
int len = size;
while (len--) {
USART_Put(*buffer++ & (uint16_t)0x01FF);
}
return size;
}
#elif defined (__CC_ARM)
int fputc(int ch, FILE *f)
{
rturn USART_Put(ch);
}
#endif
HAL库实现
#if defined(__GNUC__)
int _write(int fd, char * ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
return len;
}
#elif defined (__ICCARM__)
#include "LowLevelIOInterface.h"
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
HAL_UART_Transmit(&huart1, (uint8_t *) buffer, size, HAL_MAX_DELAY);
return size;
}
#elif defined (__CC_ARM)
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
#endif
在代码中启用UART
根据可用的pin脚, USART1可以使用PA9, PA10组合, 或者PB6, PB7组合.
stm32f103
void UARTmain_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开GPIO和USART时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 将USART1 Tx@PA9的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 将USART1 Rx@PA10的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置USART1参数
波特率 = 115200
数据长度 = 8
停止位 = 1
校验位 = No
禁止硬件流控(即禁止RTS和CTS)
使能接收和发送
*/
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能 USART1
USART_Cmd(USART1, ENABLE);
}
stm32f401
void UARTmain_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
/**
* Enable clock for GPIOB
* Enable clock for USART1 peripheral
*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/**
* 串口1对应引脚复用映射
* STM32F4xx USART1 为PA9/PB6对应USART1的TX, PA10/PB7对应USART1的RX
* Tell pins PB6 and PB7 which alternating function you will use
* @important Make sure, these lines are before pins configuration!
*/
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
// Initialize pins as alternating function
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
/**
* Set Baudrate to value you pass to function
* Disable Hardware Flow control
* Set Mode To TX and RX, so USART will work in full-duplex mode
* Disable parity bit
* Set 1 stop bit
* Set Data bits to 8
*
* Initialize USART2
* Activate USART2
*/
USART_InitStruct.USART_BaudRate=115200;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_Parity=USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
参考
- https://www.programmersought.com/article/47483173289/
- http://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/
- https://community.arm.com/developer/tools-software/tools/f/keil-forum/34791/how-printf-to-specific-usart
STM32 printf 方法重定向到串口UART的更多相关文章
- STM32 使用 printf 发送数据配置方法 -- 串口 UART, JTAG SWO, JLINK RTT
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK) http://home.eeworld.com.cn/my/space-uid-338727-blogid-47 ...
- STM32 printf()函数和scanf()函数重定向到串口
STM32 printf()函数和scanf()函数重定向到串口 printf()函数和scanf()函数重定向 在学习STM32的时候,常常需要用串口来测试代码的正确与否,这时候就要要用到print ...
- stm32 printf重定向
printf函数调用fputc int fputc(int ch, FILE *p) { USART_SendData(USART1, ch); //重定向到串口 while(USART_GetFla ...
- 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现. 串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说 ...
- 如何在User版本开启串口(Uart),抓取上层Log,开启输入控制台
[原][FAQ03891] 如何在User版本开启串口(Uart),抓取上层Log,开启输入控制台 2014-11-26阅读1369 评论0 FAQ Content [Description]如何在U ...
- STM32 printf函数
/******************** (C) COPYRIGHT 2012 WildFire Team *************************** * 文件名 :usart1.c * ...
- 第011课_串口(UART)的使用
from: 第011课_串口(UART)的使用 第001节_辅线1_硬件知识_UART硬件介绍 1. 串口的硬件介绍 UART的全称是 Universal Asynchronous Receiver ...
- STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085和串口只发送数据不能接收数据问题
STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085的问题讨论:http://www.rt-thr ...
- java中printf()方法简单用法
%n 换行 相当于 \n %c 单个字符 %d 十进制整数 %u 无符号十进制数 %f 十进制浮点数 %o 八进制数 %x 十六进制数 %s 字符串 %% 输出百分号 > 在printf()方法 ...
- 通过response对象的sendRedirect方法重定向网页
通过response对象的sendRedirect方法重定向网页 制作人:全心全意 使用response对象提供的sendRedirect()方法可以将网页重定向到另一个页面.重定向操作支持将地址重定 ...
随机推荐
- 03-Tcl数学表达式及expr命令
3 Tcl书写表达式及expr命令 Tcl提供了有效的数学运算和逻辑运算功能.通过expr可以实现对数学表达式的分析和计算. 3.1 数学与逻辑运算符 运算符 说明 - + ~ ! 一元减(取负).一 ...
- [转帖]聊聊字符串数据长度和nls_length_semantics参数
字符串是我们设计数据库经常用到的类型,从传统的ASCII格式到UTF-8格式,不同应用需求对应不同的字符类型和长度配置.针对Oracle而言,最常用的类型无外乎char和varchar2两个基本类型. ...
- TiKV 服务部署的注意事项
TiKV 服务部署的注意事项 背景 最近发现tikv总是会掉线 不知道是哪里触发了啥样子的bug. 所以想着使用systemd 管理一下, 至少在tikv宕机的时候能够拉起来服务. 二进制文件 pd- ...
- [转帖]高性能 -Nginx 多进程高并发、低时延、高可靠机制在百万级缓存 (redis、memcache) 代理中间件中的应用
https://xie.infoq.cn/article/2ee961483c66a146709e7e861 关于作者 前滴滴出行技术专家,现任 OPPO 文档数据库 mongodb 负责人,负责 o ...
- [转帖]yum 下载全量依赖 rpm 包及离线安装(终极解决方案)
简介 通常生产环境由于安全原因都无法访问互联网.此时就需要进行离线安装,主要有两种方式:源码编译.rpm包安装.源码编译耗费时间长且缺乏编译环境,所以一般都选择使用离线 rpm 包安装. 验证环境 C ...
- [转帖]Java 类加载器
类的生命周期和加载过程 在Java中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载.引用类型,Java 将其细分为四种:类.接口.数组类和泛型 ...
- 解决input标签自动填充内容的问题
autocomplete="new-password" 巧妙解决input标签自动填充问题 如果是同域名网站,并且曾经在该网站下登录过账号密码,并且选择了记住账号密码. chrom ...
- 【第4个渗透靶机项目】 Tr0ll
每天都要加油哦! 0x00 信息搜集 首先找到一下靶机的ip,并扫描端口. 靶机和kali都是桥接. 那么先看一下kali的ifconfig信息. nmap -sn 192.168.0.0/24 ...
- [LeetCode刷题记录]113 路径总和 II
题目描述 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径. 说明: 叶子节点是指没有子节点的节点. 难度 中等 题解 采用深度搜索优先,遍历每条从根节点到叶子节点的 ...
- Gorm实战,轻松掌握数据库增删改查技巧!
Gorm实战,轻松掌握数据库增删改查技巧! CRUD通常指数据库的增删改查操作,本文详细介绍了如何使用GORM实现创建.查询.更新和删除操作. 目录 Gorm实战,轻松掌握数据库增删改查技巧! 一.C ...