单片机printf和scanf重定向和自定义打印函数的使用归纳

本文介绍了如何在Keil5的Version5和Version6中不使用useMicroLIB,以及使用useMicroLIB的情况下,通过重定义fputc和fgetc函数,以及自定义printf和scanf功能,实现在串口上进行数据打印和接收。同时提供了HAL库和标准库的代码示例。
摘要由CSDN通过智能技术生成

理解:使用printf函数打印数据,会跳转到fputc函数,每个字符都会跳转一次。

一.不需要use MicroLIB的方式:

1.Version5

 Keil 5 version5且不使用use MicroLIB的配置界面

Keil 5 version5的代码:
#include <stdio.h>

//需包含#include "stdio.h"
#if 1
#pragma import(__use_no_semihosting)                
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;  
FILE __stdin;   
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (uint8_t) ch;      
	return ch;
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
    uint8_t ch = 0;
	while((USART1->SR&0X20)==0);//循环发送,直到发送完毕   
	ch = (uint8_t) USART1->DR;   
    return ch;
}
#endif 

2. Version6

 Keil 5 version6且不使用use MicroLIB的配置界面

Keil 5 version6的代码:
#include <stdio.h>

//需包含#include "stdio.h"
#if 1            
struct FILE 
{ 
	int handle; 
}; 

FILE __stdout;  
FILE __stdin;   
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (uint8_t) ch;      
	return ch;
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
    uint8_t ch = 0;
	while((USART1->SR&0X20)==0);//循环发送,直到发送完毕   
	ch = (uint8_t) USART1->DR;   
    return ch;
}
#endif 

二.使用use MicroLIB的方式:

配置勾上Use MicroLIB

HAL库代码:
#include <stdio.h>

#if 1       
//需包含#include "stdio.h"
int fputc(int ch, FILE *f)
{
    uint8_t temp = ch;
    HAL_UART_Transmit(&huart1, &temp, 1, 0xffff);//huart1需要根据你的配置修改
    return ch;
}

int fgetc(FILE *f)
{
    uint8_t ch = 0;
    HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    return ch;
}
#endif 

标准库代码: 
#include <stdio.h>

#if 1
/// 重定向c库函数printf到USART1
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到USART1 */
		USART_SendData(USART1, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}
 
/// 重定向c库函数scanf到USART1
int fgetc(FILE *f)
{
		/* 等待串口1输入数据 */
		while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
 
		return (int)USART_ReceiveData(USART1);
}
#endif

三、自定义函数的printf打印Debug方式:

(这里不需要使用微库)

        实际项目中,当用到多个串口时,使用printf来Dedug打印数据非常不方便,这里可以自己定义每个串口的打印函数。

HAL库代码: 

这里只要修改

HAL_UART_Transmit(&huart1, (uint8_t *)Uart_buf, len, 0xf);

​​​​中的串口句柄(&huart1)即可打印到指定串口

#include "usart.h"
#include "stdarg.h"
#include "stdio.h"

#define CONSOLEBUF_SIZE 64

void PrintfDebug1(const char *fmt, ...)
{
    char Uart_buf[CONSOLEBUF_SIZE];  // 用于存储格式化后的字符串
    uint16_t len = 0;  // 存储格式化后的字符串长度

    va_list args;  // 声明一个可变参数列表
    va_start(args, fmt);  // 初始化可变参数列表,使其指向参数 fmt

    // 使用 vsprintf 函数将格式化的字符串放入 Uart_buf 中,并获取格式化后的字符串长度
    len = vsprintf(Uart_buf, fmt, args);

    va_end(args);  // 结束可变参数的使用

    // 使用 HAL_UART_Transmit 函数将格式化后的字符串通过串口发送出去
    HAL_UART_Transmit(&huart1, (uint8_t *)Uart_buf, len, 0xf);
}
标准库代码: 


USART_SendData(USART1, (uint8_t)ch);

while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

改变USART1即可打印到指定的串口

#include "stdarg.h"
#include "stm32f10x_conf.h"
#include "stdio.h"

#define CONSOLEBUF_SIZE 64

// 发送单个字符到 USART1,阻塞等待发送完毕
void Send_char(char ch) 
{
    // 使用 USART_SendData 函数发送单个字符到 USART1
    USART_SendData(USART1, (uint8_t)ch);

    // 等待发送完毕
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 发送字符串到 USART1
// 参数 str: 要发送的字符串
// 参数 length: 字符串的长度
void Send_string(const char *str, uint16_t length) 
{
    while (length > 0) 
    {
        Send_char(*str);
        str++;
        length--;
    }
}

// 格式化输出到 USART1
// 参数 fmt: 格式字符串
// ...: 可变参数列表
void PrintfDebug1(const char *fmt, ...) 
{
    char Uart_buf[CONSOLEBUF_SIZE];
    uint16_t len = 0;
    
    va_list args;
    va_start(args, fmt);
    
    // 使用 vsnprintf 函数将格式化的字符串放入 Uart_buf 中,并获取格式化后的字符串长度
    len = vsnprintf(Uart_buf, CONSOLEBUF_SIZE, fmt, args);
    
    va_end(args);

    // 将格式化后的字符串发送到 USART1
    Send_string(Uart_buf, len);
}

STM32中,可以通过重定向标准输入输出流的方式实现重定向scanf函数的功能。具体步骤如下: 1. 在工程中添加syscalls.c文件,并在其中编写重定向标准输入输出流的代码。示例代码如下: ```c #include <errno.h> #include <sys/stat.h> #include <sys/times.h> #include <sys/unistd.h> int _write(int file, char *ptr, int len) { // 重定向标准输出流,例如将数据发送到串口 // ... return len; } int _read(int file, char *ptr, int len) { // 重定向标准输入流,例如从串口接收数据 // ... return len; } int _fstat(int file, struct stat *st) { st->st_mode = S_IFCHR; return 0; } int _close(int file) { return -1; } int _isatty(int file) { return 1; } int _lseek(int file, int ptr, int dir) { return 0; } int _open(const char *name, int flags, int mode) { return -1; } int _wait(int *status) { errno = ECHILD; return -1; } int _unlink(char *name) { errno = ENOENT; return -1; } int _times(struct tms *buf) { return -1; } int _stat(const char *file, struct stat *st) { st->st_mode = S_IFCHR; return 0; } int _link(char *old, char *new) { errno = EMLINK; return -1; } int _fork(void) { errno = EAGAIN; return -1; } int _execve(char *name, char **argv, char **env) { errno = ENOMEM; return -1; } ``` 2. 在main函数中调用freopen函数将标准输入流重定向到其他输入流中。例如,将标准输入流重定向到串口输入流中: ```c int main(void) { // 将标准输入流重定向到串口输入流中 freopen("uart", "r", stdin); // 其他代码 return 0; } ``` 其中,"uart"是串口输入流的名称,可以根据实际情况进行修改。 3. 在需要使用scanf函数的地方,直接调用即可。例如: ```c int main(void) { // 将标准输入流重定向到串口输入流中 freopen("uart", "r", stdin); int num; scanf("%d", &num); // 从串口中获取一个整数 // 其他代码 return 0; } ``` 需要注意的是,重定向标准输入输出流可能会影响其他函数的使用,因此在进行重定向时需要谨慎。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值