单片机实现 printf 打印输出,和电脑端一样用

2023-11-01

在学C语言时 printf 很好用,到了单片机/ARM时却不能用,那因为库中的 printf 是定向打印到显示屏的,所以我们把 printf 重新定向打印到串口就可以了,串口助手中就可以显示打印的内容! 这样我们在单片机/ARM中就可以 像电脑端一样使用 printf

串口初始化代码部分,以STM32为例,其他单片机也一样,只是修改成对应的单片机寄存器即可,整个逻辑是一样的
若只是实现printf打印到串口,串口不用开中断,也不用单独写串口接收发送函数,只要需要配置好串口四要素,再启动串口,并在下面核心代码去修改 串口状态接收标志 和 串口数据寄存器 就可以了, 若是STM32F4可以直接用下面代码不用修改。

/**
 *******************************************************************************
 * @brief   USART1 初始化 函数
 * @param   [in] bound
 * @return  None
 * @note    配置四要素:波特率、数据位、奇偶校验、停止位
 *******************************************************************************
 */
void USART1_Init(uint32_t bound)
{
    float32_t usartDIV;
    uint32_t divMantisaa;
    uint32_t divFraction;
    uint32_t fckMHZ = 84; //根据时钟系统配置
//**串口GPIO初始化******************************************
    RCC->AHB1ENR |= (1u<<0); //使能GPIOA时钟
    //PA9(TX):复用功能模式,推挽输出,映射到AF7
    GPIOA->MODER   &= ~( 3u<<(2*9)); //清零,输入
    GPIOA->MODER   |=  ( 2u<<(2*9)); //置位,复用功能模式
    GPIOA->OTYPER  &= ~( 1u<<   9 ); //清零,输出推挽
    GPIOA->OSPEEDR &= ~( 3u<<(2*9)); //清零,2 MHz(低速)
    GPIOA->OSPEEDR |=  ( 2u<<(2*9)); //置位,50 MHz(快速)
    GPIOA->PUPDR   &= ~( 3u<<(2*9)); //清零,无上拉或下拉
    GPIOA->AFR[1]  &= ~(15u<<(4*9-32)); //清零,映射到AF0
    GPIOA->AFR[1]  |=  ( 7u<<(4*9-32)); //置位,映射到AF7
    //PA10(RX):复用功能模式,输入,映射到AF7
    GPIOA->MODER   &= ~( 3u<<(2*10)); //清零,输入
    GPIOA->MODER   |=  ( 2u<<(2*10)); //置位,复用功能模式
    GPIOA->PUPDR   &= ~( 3u<<(2*10)); //清零,无上拉或下拉
    GPIOA->PUPDR   |=  ( 1u<<(2*10)); //置位,上拉
    GPIOA->AFR[1]  &= ~(15u<<(4*10-32)); //清零,映射到AF0
    GPIOA->AFR[1]  |=  ( 7u<<(4*10-32)); //置位,映射到AF7
//**串口控制器初始化****************************************
    RCC->APB2ENR |= (1u<<4);//使能USART1的时钟
    
    USART1->CR1 = 0; //整体清零。OVER8为0,数据8位,无奇偶校验
    USART1->CR1 |= (1u<<2); //使能接收器
    USART1->CR1 |= (1u<<3); //使能发送器
    USART1->CR2 &= ~(3u<<12); //1个停止位

    //配置波特率(求USARTDIV,再把USARTDIV的值写到BRR)
    usartDIV = (float32_t)(fckMHZ*1000000) / (bound*16);
    divMantisaa = usartDIV; //得到了整数
    divFraction = (uint32_t)((float64_t)(usartDIV - divMantisaa)*16 + 0.5); //0.5四舍五入 
    USART1->BRR = (divMantisaa<<4) | divFraction; //将整数和小数写入相应的位
	
	USART1->CR1 |= (1u<<13); //使能USART1
}

printf打印到串口核心代码
USART1->SR是STM32串口1状态寄存器,检测串口接收标志,是否收到数据
USART1->DR是STM32串口1数据寄存器
若不是STM32单片机 改成 自己单片机寄存器 就可以了

/**@copyright Copyright(c)2014-2011 XXXX Co.,Ltd. All rights reserved.
 *******************************************************************************
 * @file
 * @brief   
 * @author  缪某某 (ID:123456)
 * @version V1.0
 * @date    2014/05/08
 *******************************************************************************
 * @note
 * 
 * @attention
 * 
 *******************************************************************************
 * @par 修改日志:
 * <table>
 * <tr><th>Date        <th>Version  <th>Author(ID:xxxxxx)  <th>Description</tr>
 * <tr><td>2014/05/08  <td>V1.0     <td>缪某某(ID:123456)  <td>创建初始版本</tr>
 * </table>
 *******************************************************************************
 */


/**Private includes************************************************************/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "uart_printf.h"
#include "mcu.h"
#include "usart.h" //串口初始化
/* USER CODE END Includes */


/**Private typedef*************************************************************/
/* USER CODE BEGIN PT */

/* USER CODE END PT */


/**Private constants***********************************************************/
/* USER CODE BEGIN PC */
#define UARTx                       USART1

#define UART_TDR                    UARTx->TDR
#define UART_RDR                    UARTx->RDR
#define UART_SR                     UARTx->ISR
#define UART_ICR                    UARTx->ICR

#define UART_TD_END()               (UARTx->ISR & (1 << 7))
#define UART_RD_END()               (UARTx->ISR & (1 << 5))

#define UART_ORE_CHECK()            (UART_SR   & (1 << 3)) //ORE==1,上溢错误
#define UART_ORE_CLEAR_FLG()        (UART_ICR |= (1 << 3)) //清除溢出错误,否则可能会卡死. 向此位写入“1”时, ISR 寄存器中的 ORE 标志将清零.
/* USER CODE END PC */


/**Private macro***************************************************************/
/* USER CODE BEGIN PM */

/* USER CODE END PM */


/**Private variables***********************************************************/
/* USER CODE BEGIN PV */

/* USER CODE END PV */


/**Private function prototypes*************************************************/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */


/**
 *******************************************************************************
 * @brief   标准库需要的支持函数
 * @note    加入以下代码,支持printf函数,而不需要选择Use MicroLIB(MDK中Options => Target => Use MicroLIB)
 *******************************************************************************
 */
#if 1
#pragma import(__use_no_semihosting) //取消半主机状态
struct __FILE
{ 
    int handle; //Add whatever you need here
}__stdout;

void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式,标准库需要的支持函数
{ 
    x = x; 
}
#endif

/**
 *******************************************************************************
 * @brief   重定向c库 fputc 函数
 * @note    printf到串口
 *******************************************************************************
 */
int fputc(int ch, FILE *stream)
{
	UART_TDR = (u8)ch; //将 ch 赋给串口的发送寄存器,即重定向到串口,也可以是其他的接口
	while( !UART_TD_END()  ); //等待数据发送完成
	return ch;
}

/**
 *******************************************************************************
 * @brief   重定向c库 fgetc 函数
 * @note    串口到scanf
 *******************************************************************************
 */
int fgetc(FILE *stream)
{
	while( !UART_RD_END() ); //等待数据接收完成
	return UART_RDR; //返回接收值
}

/**
 *******************************************************************************
 * @brief   串口扫描接收 函数
 * @param   [in] u16 endFlg     - 接收结束标志(注意大小端,低字节先收,高字节在后收到)
 * @param   [in] char *pdata    - 字符串 缓存 地址
 * @param   [in] u32 maxSize    - 允许接收的最大字节数
 * @return  实际接收的字节数
 * @note    功能类似 scanf 函数, scanf存在很多注意事项比较难用,所用就自己写了一个
 *******************************************************************************
 */
u32 UartScanf(u16 endFlg, char *pdata, u32 maxSize)
{
    u8  rxEnd = 0;
    u32 rxCnt = 0;
    do
    {
        if(rxCnt >= maxSize) rxCnt = 0; //超出最大接收缓存
        if(UART_ORE_CHECK()) rxCnt = 0; //上溢错误检测
        
        if(UART_RD_END()) //读取数据寄存器接收标志不为空。表示接收到数据
        {
            pdata[rxCnt++] = UART_RDR; //读取RDR寄存器,同时也清了标志。
            if(rxCnt >= 2)
            {
                if(*(u16 *)&pdata[rxCnt-2] == endFlg) rxEnd = 1; //结束判断
            }
        }
        UART_ORE_CLEAR_FLG(); //清除溢出错误
    }while(!rxEnd);
    
    return rxCnt-2;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单片机实现 printf 打印输出,和电脑端一样用 的相关文章

  • /bin/sh -e

    e含义 每条指令之后 都可以用 去判断它的返回值 零就是正确执行 非零就是执行有误 加了 e之后 就不用自己写代码去判断返回值 返回非零 脚本就会自动退出
  • JUST技术:高效时空索引揭秘及使用指南

    一 问题背景 城市中超过80 的数据都与时空有关 如加油站点 出租车轨迹 交通路况等 这些数据多为半结构化和非结构化数据 并且需要管理的数据量巨大 传统的时空数据库管理海量数据时会出现性能严重下降的情况 如带有PostGIS插件的Postg
  • 编程题中的问题 2020-9-13

    1 多行的输入输出 Scanner sc new Scanner System in int t sc nextInt 以空格和回车区别的数 String s sc nextLine 或得一行的内容 因为好久没有接触基本的输入输出了 导致今
  • STM32进阶:使用STM32驱动ST7735S(内附核心源码)

    使用STM32驱动ST7735S 内附核心源码 感觉很久很久没有来博客更新了 历经千难万阻 终于做出来了TFT显示屏的SPI驱动 这里分享以下核心源码 接下来一段时间开始准备考科一了 后面有时间了再来更新 有三种模式下的驱动 一 软件SPI

随机推荐

  • Linux入门篇-RHEL8的网络管理

    简介 没有比 Linux 网络管理更重要的知识 只适用于 centos rhel 系列 不低于 rhel7 版本 RHEL8网络管理服务 在早期的Linux发行版本里几乎所有的网络服务都是network服务 从RHEL7开始红帽官方建议采用
  • 如何声明静态方法 和 实现?

    如何声明静态方法 和 实现 在 C 中 声明和实现静态方法 静态成员函数 与普通成员函数有一些区别 静态方法属于类本身 而不是类的对象 因此在声明和实现时需要特殊的语法 声明静态方法 在类的声明中 通过在函数原型前面添加关键字 static
  • Spring Boot整合Elasticsearch

    Elasticsearch是一个全文搜索引擎 专门用于处理大型数据集 根据描述 自然而然使用它来存储和搜索应用程序日志 与Logstash和Kibana一起 它是强大的解决方案Elastic Stack的一部分 我之前的一些文章中已经对此进
  • 专辑一:爱之初体验(初级)

    1 Everyone Has a Story in Life 每个人都有自己的故事 A 24 year old boy seeing out from the train s windows shouted Dad look the tre
  • C++子类不能使用初始化列表来初始化父类的属性

    不能使用初始化列表来初始化父类的属性 变量赋值 函数调用要在函数内 不能使用初始化列表来初始化父类的属性 报错 xx 不是类 xxx 的非静态数据成员或基类 主要原因 子类不能使用初始化列表来初始化父类的参数或属性 报错代码 class O
  • TCP/IP详解笔记(9)

    TFTP简单文本传输协议和BOOTP引导程序协议 TFTP协议 TFTP主要使用UDP协议 当需要将程序或文件同时向许多机器下载时就需要TFTP 或者当无盘系统工作时先广播一TFTP请求 网络上的TFTP服务器就会发送响应 其中包含可执行的
  • 秩亏自由网平差的附加条件法

    目录 一 原理概述 二 案例分析 三 代码实现 四 结果展示 一 原理概述 N B T P B N B TPB N
  • 为什么学习java

    我们为什么要学习java 前几天在上课的时候 老师问了一句这样的话 为什么要学习java 好家伙 当然是热 穷 爱 的 了 当然 如此肤浅也是万万不能的 于是我陷入沉思 是啊 现在主流编程语言这么多 python c c 为什么我要学习ja
  • 使用Tensorflow实现CNN进行手写数字识别

    传统的机器学习需要使用不同的特征提取算法获取特征 深度卷积神经网络为图像分类提供了统一的解决方案 一 卷积神经网络 卷积神经网络CNN是多层神经网络的一个变种 传统的多层神经网络 当隐层数变多时 节点数目过多时就会造成参数个数过多 训练难度
  • 【STM32】基本定时器

    基于stm32f103 基于 零死角玩转STM32 F103指南者 进行学习 定时器 分类 基本定时器 通用定时器 高级定时器 功能框图 简单来说就是来自APB或者AHB的时钟 经过PSC 1到65535分频 形成时基 每经过一个时基 TI
  • Matlab R2021b下载安装教程

    1 本人使用百度网盘下载 2 准备工具 内存较大的电脑 会占用20 G 请注意 百度网盘 还需要耐心 这点我待会细说 3 Matlab R2021b下载地址私信我即可 4 开始下载 然后就是漫长的等待过程 因为我在学校宿舍 网速慢 但是总体
  • 机器学习sklearn之贝叶斯网络实战(三)

    贝叶斯网络的结构学习 包括 基于评分的结构学习 基于约束的结构学习以及两者结合的结构学习方法 hybrid structure learning 评分函数主要分为两大类 贝叶斯评分函数 基于信息论的评分函数 贝叶斯评分函数主要包括 K2评分
  • 虚拟机VMware最详细下载与安装教程!

    前面给大家讲过了虚拟机VBox的安装 个人觉得还是没有VMware好用 因为VMware有快照功能 而VBox没有 所以今天就给大家说下VMware的安装 有很多细节方面需要注意 不然很可能安装失败 这里就说下15 5 2的安装 建议不要安
  • WMware WorkStation克隆CentOS7

    WMware WorkStation克隆CentOS7 1 克隆说明 vmware中的完整克隆是基于指定的虚拟机克隆出相同的一份出来 不必再安装 但是我们要保证几个地方不能一样 一个是主机名称 hostname 一个是虚拟网卡设备mac地址
  • webpack 的热更新是如何做到的?原理是什么?

    Hot Module Replacement 简称 HMR 在不需要刷新整个页面的同时更新模块 能够提升开发的效率和体验 热更新时只会局部刷新页面上发生了变化的模块 同时可以保留当前页面的状态 比如复选框的选中状态等 在 webpack 中
  • Java课题笔记~ 日期处理

    2 8 日期处理 2 8 1 日期注入 日期类型不能自动注入到方法的参数中 需要单独做转换处理 使用 DateTimeFormat注解 需要在springmvc xml文件中添加mvc annotation driven 标签 1 在方法的
  • RSA算法

    一 资料阅读 1 RSA算法 将两个大素数相乘十分容易 但那时想要对其乘积进行因式分解却极其困难 因此可以将乘积公开作为加密密钥 2 数字签名 又称公钥数字签名 电子签章 是一种类似写在纸上的普通的物理签名 但是使用了公钥加密领域的技术实现
  • 小程序登录及AppSecret(小程序密钥)

    在授权开发以后 需要提交小程序密钥 有小程序密钥第三方才有能力获取用户的一些信息 提供一些能力 平台分别提供多种方式实现微信登录 1 调用wx login接口 静默获取openid 适用场景 无需使用用户头像 昵称 Unionid信息 2
  • Echarts基本入门(一)

    一 关于Echarts图形的基本设置都是在 option中完成的 具体的配置可以参考官网链接 https www echartsjs com zh tutorial html ECharts 20 E4 B8 AD E7 9A 84 E6
  • 单片机实现 printf 打印输出,和电脑端一样用

    在学C语言时 printf 很好用 到了单片机 ARM时却不能用 那因为库中的 printf 是定向打印到显示屏的 所以我们把 printf 重新定向打印到串口就可以了 串口助手中就可以显示打印的内容 这样我们在单片机 ARM中就可以 像电