手动写一个printf函数与spinrtf函数,基于Cortex-M处理器

本文介绍了在嵌入式系统中,为提高代码效率和实现特定控制,自定义printf函数的方法。通过汇编和C语言结合,实现了print和sprint函数,支持不同输出设备和格式控制,包括十进制、十六进制、八进制和浮点数等。
摘要由CSDN通过智能技术生成

在看这篇文章之前,你肯定有一个疑问,既然stdio.h文件中已经自带了printf函数,为什么还要自己再写一个呢?

第一,可以提高代码效率,可以删除一些不需要的功能,提高代码运行速度。

第二,可以自定义控制字符,比如在12864屏幕上通过控制字符来控制行数和列数。

目录

1.汇编部分

2.C语言部分


1.汇编部分

/*
 *
 * @函数名称: print
 *
 * @函数功能: print函数入口
 *
 * @输入参数: c 控制字符串
 *			  ... 其他传参
 *
 * @返 回 值: 无
 *
 * @注   释: 无
 *
 */


__asm void print(const char* c,...)
{
	PRESERVE8
	extern  xprint
	
	PUSH	{R3}//将传参压入栈中
	PUSH	{R2}//将传参压入栈中
	PUSH	{R1}//将传参压入栈中
    MOV		R1,R0//将控制字符串指针传入R1,相当传入xprint函数的con参数 (控制字符串指针)
	MOV		R2,SP//将栈指针传入R2,相当传入xprint函数的sp参数 (栈指针)
    MOV		R0,#0x00//将0x00传入R0,相当传入xprint函数的s参数 (回写地址)
	PUSH 	{LR}//压入返回地址

	BL.W	xprint//调用xprint函数
					
	POP  	{LR}//弹出返回地址
	ADD		SP,#0x0C//释放栈
	BX		LR//根据LR寄存器中的地址,返回上一个函数
}
/*
 *
 * @函数名称: sprint
 *
 * @函数功能: sprint函数入口
 *
 * @输入参数: s 输出地址
 *  		  c 控制字符串
 *			  ... 其他传参
 *
 * @返 回 值: 无
 *
 * @注   释: 无
 *
 */

__asm void sprint(char* s,const char* c,...)
{
	PRESERVE8
	extern  xprint
	
	PUSH	{R3}//将传参压入栈中
	PUSH	{R2}//将传参压入栈中
	PUSH	{R0}//将输出地址压入栈中
	ADD		R2,SP,#0x04//将栈指针传入R2,相当传入xprint函数的sp参数 (栈指针)
	MOV		R0,SP//将栈指针传入R0,相当传入xprint函数的s参数 (回写地址)
	PUSH 	{LR}//压入返回地址

	BL.W	xprint//调用xprint函数
					
	POP  	{LR}//弹出返回地址
	ADD		SP,#0x0C//释放栈
	BX		LR//根据LR寄存器中的地址,返回上一个函数
}

2.C语言部分

/*
 *
 * @函数名称: pchar
 *
 * @函数功能: 输出接口
 *
 * @输入参数: ch	字符
 *
 * @返 回 值: 无
 *
 * @注    释: 该函数可以用于重定向
 *
 */
int pchar(const char ch)
{
    //这个函数内代码可以改成你所要输出设备驱动代码

	USART_SendData(USART1,ch);//发送到串口
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	return (ch);
}
/*
 *
 * @函数名称: pchar
 *
 * @函数功能: sprint函数字符回流
 *
 * @输入参数: ch	字符
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 该函数可以用于重定向
 *
 */
static void _spchar(const char ch,int* s)
{
	if(s){//如果s传参不为零,就表明有输出地址
		*((char*)*s) = ch;//将字符写入到回流地址中
		*s = (int)*s + 1;//将回流地址向后移一位
	}
	else{
		pchar(ch);//直接输出
	}
}
/*
 *
 * @函数名称: _printU10
 *
 * @输入参数: num	无符号十进制数
 * @输入参数: ctl	前置0个数
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _printU10(unsigned int num,int ctl,int* s)
{
	int _FLAG = 100000000;//初始化第十位除数
	char _FLAG1 = 0;//用于表示前一位是否有输出
	if((num / 1000000000) % 10 > 0){//如果第十位数大于零,就输出第十位
		_spchar((char)(num / 1000000000) % 10 + 48,s);
		_FLAG1 = 1;//标记位曾输出数值
	}
	ctl = 9  - ctl;
	while(_FLAG > 1){
		if(((num / _FLAG) % 10) > 0 || _FLAG1 == 1 || ctl == 0){
			_spchar((char)((num / _FLAG) % 10) + 48,s);
			_FLAG1 = 1;//标记位曾输出数值
		}
		_FLAG /= 10;
		if(ctl > 0){
			ctl -= 1;
		}
	}
	_spchar(num % 10 + 48,s);//输出最后一位
}
/*
 *
 * @函数名称: _printS10
 *
 * @函数功能: print内联输出有符号十进制数
 *
 * @输入参数: num	有符号十进制数
 * @输入参数: ctl	前置0个数
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _printS10(int num,int ctl,int* s)
{
	if(num < 0){//是否为负值
		num = (~num) + 1;//取绝对值
		_spchar('-',s);//输出负号
	}
	_printU10(num,ctl,s);//无使用号十进制数输出
}
/*
 *
 * @函数名称: _printSring
 *
 * @函数功能: print内联输出字符串
 *
 * @输入参数: p		字符串首地址
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _printSring(int p,int* s)
{
	while(*(char*)p != '\0'){//遇到结束符时结束循环
		_spchar(*(char*)p++,s);//逐个输出字符
	}
} 
/*
 *
 * @函数名称: _print16
 *
 * @函数功能: printf内联输出十六形式
 *
 * @输入参数: num	十六进制数值
 * @输入参数: c		ASCII码起始值
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _print16(int num,int c,int* s)
{
	char _Bit = 28;//初始化当前位数
	char _Flag = 0;//用于表示前一位是否有输出
	char _Buf;//用于数值缓存
	_spchar('0',s);//输出0
	_spchar('x',s);//输出x
	if(num == 0x00){//如果数值为零,直接输出00
		_spchar(0 + 48,s);//输出0
		_spchar(0 + 48,s);//输出0
	}else{
		for(;;){
			_Buf = ((num >> _Bit) & 0x0F);//取当前位数
			/*
				例如 num = 0x12345678
				num 右移28位并只取最后八位,得 0x12
			*/
			if(_Buf >= 10){//当数值大于等于十时
				_spchar(c + (_Buf - 10),s);
				_Flag = 1;//标记位曾输出数值
			}else if(_Buf > 0 || _Flag > 0){//当数值或前一位是有输出时才输出
				_spchar(_Buf + 48,s);
				_Flag = 1;//标记位曾输出数值
			}
			if(_Bit <= 0){//当前位数为零时,退出函数
				return;
			}
			_Bit -= 4;//位数减四
		}
	}
}
/*
 *
 * @函数名称: _print8
 *
 * @函数功能: printf内联输出八进制
 *
 * @输入参数: num	八进制数值
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _print8(int num,int* s)
{
	char _Bit = 30;//初始化当前位数
	char _Flag = 0;//用于表示前一位是否有输出
	char _Buf;//用于数值缓存
	_Buf = ((num >> _Bit) & 0x03);//取当前位数
	_spchar('0',s);//输出0
	if(_Buf > 0){//
		_spchar(_Buf + 48,s);
		_Flag = 1;//标记位曾输出数值
	}
	for(;;){
		_Buf = ((num >> _Bit) & 0x07);//取当前位数
		/*
			例如 num = 02215053170
			num 右移30位并只取最后三位,得 02
		*/
		if(_Buf > 0 || _Flag > 0){
			_spchar(_Buf + 48,s);//当数值或前一位是有输出时才输出
			_Flag = 1;//标记位曾输出数值
		}
		if(_Bit <= 0){//当前位数为零时,退出函数
			return;
		}
		_Bit = _Bit - 3;//位数减三
	}
}
/*
 *
 * @函数名称: _print_lf
 *
 * @函数功能: print内联输出双精度浮点数(64位)
 *
 * @输入参数: num	双精度浮点数
 * @输入参数: s		sprint函数回流数据
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
static void _print_lf(double* num,char ctrl,int* s)
{
	int _Value = 0;//
	int m;//倍数
	char _Count;//
	int _Zero = 0;//
	double _Buf;//浮点缓冲
	char p;//精度

	if(ctrl > 0){//如果对输出精度有要求
		p =  ctrl;//使用指定精度
	}else{//如果对输出精度有要求
		p = _print_lf_retain;//使用默认精度
	}

	_Buf = *num;
	m = 10;
	_printS10((int)_Buf,0,s);//输出整数部分
	_spchar('.',s);//输出小数点
	for(_Count = 1; _Count < (p + 1); _Count++){//将小数部分转换成整数
		/*
			例如: 1.2345 转换后为: 2345
		*/
		_Value = (_Value * 10) + ((unsigned int)(_Buf * m) % 10);
		if(_Value == 0){//如果为零,则记录下来
			_Zero = _Zero + 1;
		}
		m = m * 10;
	}
	for(_Count = 1; _Count < 20;_Count++){//检测小数部分是否为空值
		if((_Value % 10) == 0){//如果小数部分是空值,继续向后搜索
			_Value = _Value / 10;
		}else{//如果小数部分不是空值,就需要步零
			for(;_Zero > 0;_Zero--){
				_spchar('0',s);
			}
			break;
		}
	}
	
	_printU10(_Value,0,s);//输出小数部分
	
	
}
/*
 *
 * @函数名称: xprint
 *
 * @函数功能: print函数子函数
 *
 * @输入参数: s 回写地址
 * 			  con 控制字符串地址
 *			  sp 栈指针
 *
 * @返 回 值: 无
 *
 * @注    释: 无
 *
 */
void xprint(int* s,char* con,int sp)
{	
	
	char LeftValue = 0;//左值,表示小数点前面的数值
	char RightValue = 0;//右值,表示小数点后面的数值

	for(;;)
	{
		switch(*con){
			case '%':
						con++;
						xprintc:
						switch(*con){
							case 'M'://切换控制字符
									con = (char*)*((int*)(sp));
									con --;
									sp = sp + sizeof(char*);//指向下一个传参
									break;
							case 'd'://以十进制形式输出带符号整数(正数不输出符号)
									_printS10(*((int*)(sp)),LeftValue,s);
									sp = sp + sizeof(signed int);//指向下一个传参
									break;
							case 'l'://输出无符号整数
									if(*(con + 1) != 'u'){
										_spchar((char)*con,s);
										break;
									}
									con++;
							case 'u'://输出无符号整数
									_printU10((*((unsigned int*)(sp))),LeftValue,s);
									sp = sp + sizeof(unsigned int);//指向下一个传参
									break;									
							case 'p'://输出指针地址
									
									_printU10((int)(sp),LeftValue,s);
									sp = sp + sizeof(int*);//指向下一个传参
									break;
							case 's'://输出字符串
									
									_printSring(*((int*)(sp)),s);
									sp = sp + sizeof(char*);//指向下一个传参
									break;
							case 'c'://输出单个字符
									
									_spchar((char)(*((int*)(sp))),s);
									sp = sp + sizeof(char*);//指向下一个传参
									break;
							case 'X'://以十六进制大写形式输出无符号整数(输出前缀0x)
									
									_print16(*((int*)(sp)),'A',s);
									sp = sp + sizeof(unsigned int);//指向下一个传参
									break;
							case 'x'://以十六进制小写形式输出无符号整数(输出前缀0x)
									
									_print16(*((int*)(sp)),'a',s);
									sp = sp + sizeof(unsigned int);//指向下一个传参
									break;
							case 'o'://以八进制形式输出无符号整数(输出前缀0)
									
									_print8(*((int*)(sp)),s);
									sp = sp + sizeof(unsigned int);//指向下一个传参
									break;
							case 'f'://输出双精度浮点
									if((sp % 8) != 0){//检查指针是否8位对齐,如果没有对齐将偏移
										sp = sp + sizeof(unsigned int);//指向下一个传参
									}
									_print_lf((double*)(sp),RightValue,s);
									sp = sp + sizeof(double);//指向下一个传参
									break;
							case '.'://右值
									con++;//向后移动,指向ASCII数字
									RightValue = 0;//清空右值
									while(*con >= '0' && *con <= '9'){//在ASCII数字范围
										RightValue = (RightValue * 10) + *con - '0';//将ASCII码的表示的数字转换成十进制
										con++;//向后
									}
									goto xprintc;//结束转换
							default:
									if(*con >= '0' && *con <= '9'){//左值
										LeftValue = 0;
										while(*con >= '0' && *con <= '9'){//在ASCII数字范围
											LeftValue = (LeftValue * 10) + *con - '0';//将ASCII码的表示的数字转换成十进制
											con++;//向后
										}
										goto xprintc;//结束转换
									}else{//非右值,进行输出
										_spchar((char)*con,s);//直接将字符打印
									}
									break;
								
						}
						break;
			case '\0'://结束符
						return;
			case '\n'://回车符
						_spchar('\n',s);
						break;
			case '\b'://自定义
						break;
			case '\a'://自定义
						break;
			default:  //其他情况
						_spchar((char)*con,s);//直接将字符打印
						break;
		}
		con++;//控制字符串指针后移
		LeftValue = 0;//清空左值
		RightValue = 0;//清空右值
	}
  
}

3.测试代码与效果

	print("有符号整数测试:%d\n",-123456);
	print("无符号整数测试:%lu\n",-123456);
	print("浮点数测试:%f\n",1234.5678);
	print("大写十六进制测试:%X\n",0x1234ABCD);
	print("小写十六进制测试:%x\n",0x1234ABCD);
	print("八进制测试:%o\n",0123456);
	print("字符测试:%c\n",'a');
	print("字符串测试:%s\n","Hello World");
	print("整数测试:%3d\n",99);
	print("浮点数测试:%.2f\n",1234.5678);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值