您的位置:首页 > 其它

printf 函数的原理以及在单片机上重定向至LCD12832的实现

2015-11-29 09:36 681 查看
首先,printf 函数的的原型是这样的:

int printf(char const * format, ...);

*format 就是要格式化的字符串的起始地址。注意这个必须是字符串以'\0' 为结尾,否则格式化的的时候会以指针为起点一直向后格式化,直到在后面连续的内存中遇到一个'\0'

后面的 ”...“ 是变参列表。可变参数列表是通过宏来实现的,这些宏定义在 stdarg.h里。这个头文件定义了一个类型 va_list 和三个宏 va_start、va_arg、va_end。我们一边写代码一边讲吧。

首先我们要明白一件事儿,printf 本身是将输出定向到了标准输出流。OK,但是在单片机编程时我们想要将格式化的字符串打印到屏幕上呢?比如串口,或者是LCD。这就需要我们自己实现这个函数。单片机是***R的ATmega128 编译器是***R-GCC,目的是将格式化的字符串打印在LCD12832上。代码如下,当然重点在最后,你可以从后往前看。。。

#include<avr/io.h><span style="white-space:pre">	</span>//***R单品机外设寄存器定义

#include"Delay.h"<span style="white-space:pre">	</span>//延迟函数
#include"stdio.h"
#include"string.h"


#define FIRST 0x80//0xC0<span style="white-space:pre">	</span>//打印在LCD1602第一行命令码<span style="white-space:pre">	</span>
#define SECOND 0x90<span style="white-space:pre">		</span>//<span style="font-family: Arial, Helvetica, sans-serif;">打印在LCD1602第一行命令码</span><span style="white-space:pre">
</span>

#define FIRST_ROW   0<span style="white-space:pre">		</span>
#define SECOND_ROW 1


#define ON 0
#define OFF 1

#define H 0
#define L 1

#define SYNC(x)	(0xf8|(x<<1))
#define DATA_H(x) (x&0xf0)<span style="white-space:pre">	</span>//取高位
#define DATA_L(x) ((x&0xf)<<4)	//取低位

#define Spi_disable()	(SPCR&=~(1<<SPE)) 
#define Spi_enable()	(SPCR|=(1<<SPE))
//SPI总线通信IO口动作命令
//宏函数实现
#define LCD_CS_BIT	(1<<5)
#define LCD_CS(x) x?(PORTB&=~LCD_CS_BIT):(PORTB|=LCD_CS_BIT)<span style="white-space:pre">	</span>//片选信号

#define LCD_SCLK_BIT	(1<<6)
#define LCD_SCLK(x) x?(PORTE&=~LCD_SCLK_BIT):(PORTE|=LCD_SCLK_BIT)<span style="white-space:pre">	</span>

#define LCD_SID_BIT	(1<<5)
#define LCD_SID(x) x?(PORTE&=~LCD_SID_BIT):(PORTE|=LCD_SID_BIT)

#define LCD_RES_BIT (1<<0)
#define LCD_RES(x) x?(PORTF&=~LCD_RES_BIT):(PORTF|=LCD_RES_BIT)

#define RD	0x3	//11b	读数据
#define RC	0x2 //10b	读指令
#define WD	0x1 //01b	写数据
#define WC	0x0	//00b	写指令

void Data_tranfer(unsigned long int data_to_tran){
	char i = 24;
	while(i--){
		//LCD_SCLK(L);
		if(data_to_tran&0x800000){
			LCD_SID(H);
		}else{
			LCD_SID(L); 
		}
		LCD_SCLK(H);
		data_to_tran<<=1;
		LCD_SCLK(L);
	}
}
//写命令
void LCD_write_command(unsigned char cmd){
	unsigned long int sync;
	unsigned long int data_h;
	unsigned long int data_l;
	unsigned long int data_to_tran;
	Spi_disable();
	//数据格式化
	sync=SYNC(WC);
	data_h=DATA_H(cmd);
	data_l=DATA_L(cmd);

	data_to_tran=0x0|(sync<<16)|(data_h<<8)|(data_l);

	//开始发数据
	LCD_CS(ON);
	SPDR = sync;

	Data_tranfer(data_to_tran);

	LCD_CS(OFF);

	Delay_us(5);
	Spi_enable();
}
//写数据
void LCD_write_data(unsigned char data){
	unsigned long int sync;
	unsigned long int data_h;
	unsigned long int data_l;
	unsigned long int data_to_tran;
	Spi_disable();
	//数据格式化
	sync=SYNC(WD);
	data_h=DATA_H(data);
	data_l=DATA_L(data);

	data_to_tran=0x00|(sync<<16)|(data_h<<8)|(data_l);
	//开始发数据
	LCD_CS(ON);

	Data_tranfer(data_to_tran);

	LCD_CS(OFF);
	Delay_us(5);

	Spi_enable();
}
void LCD_Config(){
	//引脚配置
	DDRB|=LCD_CS_BIT;
	PORTB&=~LCD_CS_BIT;

	DDRF|=LCD_RES_BIT;

	DDRE|=(LCD_SID_BIT|LCD_SCLK_BIT);
	PORTE|=(LCD_SID_BIT|LCD_SCLK_BIT);

	LCD_RES(L);
	Delay_ms(10);
	LCD_RES(H);
	//SPI_Config
	LCD_write_command(0x30);
 	Delay_us(800);
	LCD_write_command(0x01);
 	Delay_ms(50);
 	LCD_write_command(0x06);
 	Delay_us(800);
 	LCD_write_command(0x0c);
}
//字符串格式化缓冲
char outbuf[32];
//返回值是打印数据数量
int printf_LCD(char row,const char *fmt,...){
//首先要定义一个变参列表
	va_list args;
//定义返回值
	int i;
//显示在第几行
	LCD_write_command(row);
//va_start也是个宏函数,第一个数据是变参列表,第二个数据是变参的数据类型,一般是变参列表前的最后一个数据。这么说可能比较绕,其实就是变参列表到底应该被当作什么数据类型处理?和它的前一个数据类型一样,这里就是把变参列表的数据类型强制转换成字符串。
	va_start(args,fmt);
//亮点来了,这个就是对字符串的格式化。他会将字符串格式化好放入缓冲数组中。
	vsprintf((char *)outbuf, fmt, args);
//标记变参列表使用结束
	va_end(args);
//开始把字符串中的元素逐个取出然后打印
	for(i=0;i<strlen((char *)outbuf);i++){
//实际上,标准C中有个putc() 函数。也可以通过重写putc函数来达到重定向的目的。
		LCD_write_data(outbuf[i]);
	}
//返回实际的打印字符数量
	return i;
}


<span style="white-space:pre">	</span>对于变参我啰嗦两句,我早年间看过在ARM上的变参函数编译成汇编后的结果。实际上,就是分配了一个连续的内存空间,然后va_list的处理就是一个指向这个内存空间起始地址的指针。每调用一次,这个指针就向后移动一次,所以重点是,你可以用到一半不用了,但是必须从头开始用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: