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