历史上的今天
今天是:2024年12月28日(星期六)
2021年12月28日 | STM32F103自定义的printf函数的实现
2021-12-28 来源:eefocus
在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,通常使用这个函数都是直接调用库函数来实现的,在单片机中如何要使用printf()函数一般都是在串口中进行重映射。如要在串口1中使用printf()函数,可以使用下面的代码进行重映射。
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
在串口1的c文件中,添加上面的代码后,使用printf()函数时,就可以通过串口1来打印了。那么不通过库函数的话,自己能不能实现printf()函数的功能呢?当然是可以的,下面就通过串口2来演示,如何在串口2上直接实现printf()函数的功能。
void UART2_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1、串口时钟使能 GPIO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// 2、串口复位
USART_DeInit(USART2);
// 3、GPIO端口设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2 TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA3 RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 4、串口参数初始化
USART_InitStructure.USART_BaudRate = bound;
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(USART2, &USART_InitStructure);
// 5、初始化NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 6、开启中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// 7、使能串口
USART_Cmd(USART2, ENABLE);
}
void USART2_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
res = USART_ReceiveData(USART2);
USART_SendData(USART2, res); //把接收到的数据发送出去
}
}
首先初始化串口2,初始化方式和正常情况下一样,初始化完成之后开始自定义一个函数来实现printf()函数的功能。
//自定义串口2 的printf 函数
char UART2_TX_BUF[200];
void u2_printf(char* fmt, ...) //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
{
u16 i, j;
va_list ap; //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
va_start(ap, fmt); //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束
vsprintf((char*)UART2_TX_BUF, fmt, ap); // 把生成的格式化的字符串存放在这里
va_end(ap);
i = strlen((const char*)UART2_TX_BUF); //此次发送数据的长度
for(j = 0; j < i; j++) //循环发送数据
{
while((USART2->SR & 0X40) == 0); //循环发送,直到发送完毕
USART2->DR = UART2_TX_BUF[j];
}
}
这个函数名定义为 :u2_printf(char* fmt, …) 使用省略号表示当前参数为可变参数。接下里就可以直接使用这个函数来打印数据了。在主函数中通过一段代码来测试串口1和串口2 printf函数的功能。
int main(void)
{
u8 t;
u8 len;
u16 times = 0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init();
UART2_Init(115200);
printf("USART1 AND USART2 TEST!!!");
while(1)
{
if(USART_RX_STA & 0x8000)
{
LED1 = !LED1;
len = USART_RX_STA & 0x3fff; //获取本次接收数据长度
printf("rn您发送的消息为:rn");
for(t = 0; t < len; t++)
{
USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待发送完成
}
printf("rn");
USART_RX_STA = 0;
}
else
{
times++;
if(times % 5000 == 0)
{
printf("串口实验rn");
u2_printf("串口实验rn"); //调用串口2 printf 函数
}
if(times % 300 == 0)
{
printf("请输入数据,以回车键结束rn");
u2_printf("请输入数据,以回车键结束rn"); //调用串口2 printf 函数
}
if(times % 30 == 0)
LED0 = !LED0;
delay_ms(10);
}
}
}
通过串口1和串口2输出同样的提示信息,然后用串口助手分别给串口1和串口2发送数据,当串口1和串口2接收到数据后就会通过串口打印出来。串口1和串口2输出的提示信息都是用printf函数输出的。

可以看出串口2的printf函数和功能和串口1的printf函数功能一样,可以正常输出字符串。也可以使用串口2的printf函数打印变量值,比如在代码中增加一句变量打印的功能。

每次输出提示信息的时候,顺便打印一下变量times的值。

可以看到变量times的值也可以正常打印。
史海拾趣
|
一、 闭路监视电视系统简介: CCTV系统结构: 电视监控系统(Closed Circuit Television,简称CCTV),一般由以下三部分组成: 前端部分: 主要由黑白(彩色)摄像机、镜头、云台、防 ...… 查看全部问答> |
|
高人可以帮我看下我的这个拨码开关程序老有点小我问题,,十万火急 process(clk_a) VARIABLE cnt_a : std_logic_vector(7 downto 0):=\"11111111\"; begin if (clk_a\'event and clk_a=\'1\') then if cnt_a=\"11111111\" then cnt_a:=d; full_a… 查看全部问答> |
|
Altium Designer 使用 —— 快速制作原理图封装当制作引脚数比较多的器件的原理图封装时,可以使用AD提供的“smart grid insert”功能快速制作原理图封装库。本文以制作K9F1G08为例,进行简单说明。K9F1G08的引脚分布如图1所示:1. 打开 excel ,按 ...… 查看全部问答> |
|
新建一个wince 的mfc exe,怎么在对话框中添加一个ie控件,浏览一个url(本地页面),thanks 新建的时候,选择,WCE MFC appWizard[exe] ,然后再选择Dialog base, 怎么样在该Dialog上添加一个ie控件,然后去浏览一个本地url? thanks… 查看全部问答> |
|
本人从朋友那里得到一份cadence(allegro)视频教程15.5板本,是一个培训班的内部视频讲义,清晰度很高,很具体详细,是自学的好资料。如果您没时间看书或者想很快学会allegro的话就请联系:13783696474 或者QQ:20247125 ...… 查看全部问答> |
|
1.最近我用sci调试RS485的时候遇到一个怪现象: 有时候(不是全部,重新加载程序的时候一般都是)cpu明明有数据发出来,SCIRX,SCITX波形正常,可是485驱动芯片出来的两线信号发不出来,示波器只能看到负半波.只要我把与其通信的人机界面的通讯口拔出来 ...… 查看全部问答> |
|
最近在学习FPGA方面知识,对时钟触发有了了解,但有好多问题有些不明白。希望大虾解释 如: always(posedge CLK) begin //执行任务块 end 假如时钟的周期为50nS,占空比50%;所执行的任务块比较耗时间,超过50nS,而此时任务块尚未完全执行完 ...… 查看全部问答> |




