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

在学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;
}

猜你喜欢

转载自blog.csdn.net/weixin_46672094/article/details/107197600