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上。代码如下,当然重点在最后,你可以从后往前看。。。
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的处理就是一个指向这个内存空间起始地址的指针。每调用一次,这个指针就向后移动一次,所以重点是,你可以用到一半不用了,但是必须从头开始用。
相关文章推荐
- 基础知识总结
- 线索二叉树的实现
- 常见的预处理指令
- 天声人語 20151129 遺伝子組み換えサケ、米市場に
- 和仙剑《逍遥叹》
- 优化Android studio的编译速度
- 【论文】Normalized Cuts and Image Segment小结
- Java 单例模式的常见应用场景
- Java 抽象方法学习小结
- 整理Javascript基础入门学习笔记
- 汇编语言基础知识摘要(《汇编语言》王爽)第 3 / 17 章
- [Pycharm介绍]pycharm - 最智能的python IDE -安装篇
- 网站加速 四大免费CDN服务评测大PK
- CodeForces - 467C George and Job
- 简单的Http共享文件SimpleHTTPServer
- 【openjudge】摘花生
- Oracle补丁的简单介绍
- 位操作实现加减乘除四则运算
- 【openjudge】移动路线
- 【openjudge】登山