LCD1602 单片机上代码的实现和代码结构的分析

1、单片机引脚和设备引脚之间的通信
LCD1602使用并口与单片机进行通信,主要包括D0-D7 8个数据线和3根控制线(RS(指令和数据寄存器的区分),R/W(读写控制引脚),E(开始读写引脚,理解为串行总线的时钟线))。设备可以被配置为使用4线或8线数据宽度的数据线通信(实现方式为刚开始的几个配置指令只识别高4位)。下面是51单片机实现的数据通信的代码,代码写的比较low,主要分析代码架构:
database1602.h 基本数据交流的头文件:

#ifndef _DATABASE1602_H
#define _DATABASE1602_H

void delay1ms(void);
void delay(u16 time);


u8 read_status_8(void);
u8 read_data_8(void);
void write_data_8(u8 wdata);
void write_commond_8(u8 commond);


u8 read_status_4(void);
u8 read_data_4(void);
void write_data_4(u8 wdata);
void write_commond_4(u8 commond);
void write_commond_4_4(u8 commond);



#endif

对应的.c文件:

#include "config.h"
#include "database1602.h"


/*延时1ms程序*/
void delay1ms(void)				
{
	u8 i,j;
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i); 
}

/*根据设定的值延迟*/
void delay(u16 time){
	while(time--);
}

/*读取LCD1602状态寄存器的值*/
u8 read_status_8(void){
	u8 value = 0;
	//datap = 0xff;
	DATA0 = 1;
	DATA1 = 1;
	DATA2 = 1;
	DATA3 = 1;
	DATA4 = 1;
	DATA5 = 1;
	DATA6 = 1;
	DATA7 = 1;
	EN = 0;
	delay(50);
	RS = 0;
	RW = 1;
	delay(50);
	EN = 1;
	delay(10);
	//value = datap;
	if(DATA0 == 1){
		value = value | 0x01;
	}else{
		value = value | 0x00;
	}
	if(DATA1 == 1){
		value = value | 0x02;
	}else{
		value = value | 0x00;
	}
	if(DATA2 == 1){
		value = value | 0x04;
	}else{
		value = value | 0x00;
	}
	if(DATA3 == 1){
		value = value | 0x08;
	}else{
		value = value | 0x00;
	}
	if(DATA4 == 1){
		value = value | 0x10;
	}else{
		value = value | 0x00;
	}
	if(DATA5 == 1){
		value = value | 0x20;
	}else{
		value = value | 0x00;
	}
	if(DATA6 == 1){
		value = value | 0x40;
	}else{
		value = value | 0x00;
	}
	if(DATA7 == 1){
		value = value | 0x80;
	}else{
		value = value | 0x00;
	}
	delay(10);
	EN = 0;
	RS = 0;
	RW = 0;
	return value;
}


/*读取数据寄存器的值*/
u8 read_data_8(void){
	u8 rdata = 0;
	//datap = 0xff;
	DATA0 = 1;
	DATA1 = 1;
	DATA2 = 1;
	DATA3 = 1;
	DATA4 = 1;
	DATA5 = 1;
	DATA6 = 1;
	DATA7 = 1;
	
	EN = 0;
	delay(50);
	RS = 1;
	RW = 1;
	delay(50);
	EN = 1;
	delay(10);
	//rdata = datap;
	
	if(DATA0 == 1){
		rdata = rdata | 0x01;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA1 == 1){
		rdata = rdata | 0x02;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA2 == 1){
		rdata = rdata | 0x04;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA3 == 1){
		rdata = rdata | 0x08;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA4 == 1){
		rdata = rdata | 0x10;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA5 == 1){
		rdata = rdata | 0x20;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA6 == 1){
		rdata = rdata | 0x40;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA7 == 1){
		rdata = rdata | 0x80;
	}else{
		rdata = rdata | 0x00;
	}
	//rdata = P0;
	delay(10);
	EN = 0;
	RS = 0;
	RW = 0;
	return rdata;
}


/*写数据到数据寄存器*/
void write_data_8(u8 wdata){
	u8 value = wdata;
	EN = 0;
	delay(50);
	RS = 1;
	RW = 0;
	delay(50);
	EN = 1;
	delay(50);
	//datap = value;
	if(value & 0x01){
		DATA0 = 1;
	}else{
		DATA0 = 0;
	}
	if((value >> 1) & 0x01){
		DATA1 = 1;
	}else{
		DATA1 = 0;
	}
	if((value >> 2) & 0x01){
		DATA2 = 1;
	}else{
		DATA2 = 0;
	}
	if((value >> 3) & 0x01){
		DATA3 = 1;
	}else{
		DATA3 = 0;
	}
	if((value >> 4) & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 5) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 6) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 7) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	//P0 = value;
	delay(50);
	EN = 0;
	RS = 0;
	RW = 0;
}


/*写命令到命令寄存器*/
void write_commond_8(u8 commond){
	u8 value = commond;
	EN = 0;
	delay(50);
	RS = 0;
	RW = 0;
	delay(50);
	EN = 1;
	delay(50);
	//datap = value;
	if(value & 0x01){
		DATA0 = 1;
	}else{
		DATA0 = 0;
	}
	if((value >> 1) & 0x01){
		DATA1 = 1;
	}else{
		DATA1 = 0;
	}
	if((value >> 2) & 0x01){
		DATA2 = 1;
	}else{
		DATA2 = 0;
	}
	if((value >> 3) & 0x01){
		DATA3 = 1;
	}else{
		DATA3 = 0;
	}
	if((value >> 4) & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 5) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 6) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 7) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	//P0 = value;
	delay(50);
	EN = 0;
	RS = 0;
	RW = 0;
}


/*读取LCD1602状态寄存器的值*/
u8 read_status_4(void){
	u8 value = 0;
	//datap = 0xff;
	DATA4 = 1;
	DATA5 = 1;
	DATA6 = 1;
	DATA7 = 1;
	EN = 0;
	delay(50);
	RS = 0;
	RW = 1;
	delay(50);
	EN = 1;
	delay(10);
	//value = datap;
	if(DATA4 == 1){
		value = value | 0x10;
	}else{
		value = value | 0x00;
	}
	if(DATA5 == 1){
		value = value | 0x20;
	}else{
		value = value | 0x00;
	}
	if(DATA6 == 1){
		value = value | 0x40;
	}else{
		value = value | 0x00;
	}
	if(DATA7 == 1){
		value = value | 0x80;
	}else{
		value = value | 0x00;
	}
	delay(10);
	EN = 0;
	delay(10);
	EN = 1;
	delay(10);
	if(DATA4 == 1){
		value = value | 0x01;
	}else{
		value = value | 0x00;
	}
	if(DATA5 == 1){
		value = value | 0x02;
	}else{
		value = value | 0x00;
	}
	if(DATA6 == 1){
		value = value | 0x04;
	}else{
		value = value | 0x00;
	}
	if(DATA7 == 1){
		value = value | 0x08;
	}else{
		value = value | 0x00;
	}
	delay(10);
	EN = 0;
	RS = 0;
	RW = 0;
	return value;
}

/*读取数据寄存器的值*/
u8 read_data_4(void){
	u8 rdata = 0;
	//datap = 0xff;
	DATA4 = 1;
	DATA5 = 1;
	DATA6 = 1;
	DATA7 = 1;
	
	EN = 0;
	delay(50);
	RS = 1;
	RW = 1;
	delay(50);
	EN = 1;
	delay(10);
	if(DATA4 == 1){
		rdata = rdata | 0x10;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA5 == 1){
		rdata = rdata | 0x20;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA6 == 1){
		rdata = rdata | 0x40;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA7 == 1){
		rdata = rdata | 0x80;
	}else{
		rdata = rdata | 0x00;
	}
	delay(10);
	EN = 0;
	delay(10);
	EN = 1;
	delay(10);
	if(DATA4 == 1){
		rdata = rdata | 0x01;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA5 == 1){
		rdata = rdata | 0x02;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA6 == 1){
		rdata = rdata | 0x04;
	}else{
		rdata = rdata | 0x00;
	}
	if(DATA7 == 1){
		rdata = rdata | 0x08;
	}else{
		rdata = rdata | 0x00;
	}
	delay(10);
	EN = 0;
	RS = 0;
	RW = 0;
	return rdata;
}


/*写数据到数据寄存器*/
void write_data_4(u8 wdata){
	u8 value = wdata;
	EN = 0;
	delay(50);
	RS = 1;
	RW = 0;
	delay(50);
	EN = 1;
	delay(50);
	if((value >> 4) & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 5) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 6) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 7) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	delay(50);
	EN = 0;
	delay(50);
	EN = 1;
	delay(50);
	if(value & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 1) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 2) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 3) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	delay(50);
	EN = 0;
	RS = 0;
	RW = 0;
}


/*写命令到命令寄存器*/
void write_commond_4(u8 commond){
	u8 value = commond;
	EN = 0;
	delay(50);
	RS = 0;
	RW = 0;
	delay(50);
	EN = 1;
	delay(50);
	if((value >> 4) & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 5) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 6) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 7) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	delay(50);
	EN = 0;
	delay(50);
	EN = 1;
	delay(50);
	if(value & 0x01){
		DATA4 = 1;
	}else{
		DATA4= 0;
	}
	if((value >> 1) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 2) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 3) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	delay(50);
	EN = 0;
	RS = 0;
	RW = 0;
}

void write_commond_4_4(u8 commond){
	u8 value = commond;
	EN = 0;
	delay(50);
	RS = 0;
	RW = 0;
	delay(50);
	EN = 1;
	delay(50);
	if((value >> 4) & 0x01){
		DATA4 = 1;
	}else{
		DATA4 = 0;
	}
	if((value >> 5) & 0x01){
		DATA5 = 1;
	}else{
		DATA5 = 0;
	}
	if((value >> 6) & 0x01){
		DATA6 = 1;
	}else{
		DATA6 = 0;
	}
	if((value >> 7) & 0x01){
		DATA7 = 1;
	}else{
		DATA7 = 0;
	}
	delay(50);
	EN = 0;
	RS = 0;
	RW = 0;
}

2、通过基础数据通信对设备的操作代码,相当于通过基础的交流进行更上一层的交互,这里主要包括怎么样初始化设备,怎么样配置设备,和对设备的操作(设备实现的功能)。
下面是设备操作的代码:

driver1602.h是关于设备内部寄存器和控制位的声明(这里写的不是很好,后续再进一步改进):

#ifndef _DRIVER1602_H
#define _DRIVER1602_H

#include "config.h"

#define CLEAR_DISPLAY 0x01	
#define RETURN_HOME 0x02 

#define STATUS_BF_BUSY 0x80
#define STATUS_BF_READY 0x00

/**
* 接口数据宽度定义,4位和8位宽两种
*/
#define interface_data_width_4 (0 << 4)
#define interface_data_width_8 (1 << 4)

/**
*显示屏的行数定义
*/
#define display_line_1 (0 << 3)
#define display_line_2 (1 << 3)

/**
*显示字符模式定义,5*8和5*10两种模式
*/
#define font_mode_5_8 (0 << 2)
#define font_mode_5_10 (1 << 2)

/**
*在改变DDRAM内容时光标或显示的移动方向
*/
#define ddram_move_direction_left (0 << 2)
#define ddram_move_direction_right (1 << 2)

/**
*在改变DDRAM内容时是光标移动还是显示移动
*/
#define ddram_move_cursor (0 << 3)
#define ddram_move_display (1 << 3)

/**
*设置显示器的开和关
*/
#define display_off (0 << 2)
#define display_on (1 << 2)

/**
*设置光标的开关
*/
#define cursor_off (0 << 1)
#define cursor_on (1 << 1)

/**
*设置光标位置的字符是否闪烁
*/
#define cursor_character_blink_off (0 << 0)
#define cursor_character_blink_on (1 << 0)

/**
*数据读写期间AC增加还是减小
*/
#define rw_data_ac_decrement (0 << 1)
#define rw_data_ac_increment (1 << 1)

/**
*数据读写期间显示画面是否移动
*/
#define rw_data_display_no_move (0 << 0)
#define rw_data_display_move (1 << 1)

/**
*CGRAM模式设置
*/
#define cgram_mode_disable 0
#define cgram_mode_enable 1

/**
*DDRAM模式设置
*/
#define ddram_mode_disable 0
#define ddram_mode_enable 1

/**
* 引脚定义结构体
*/
typedef struct
{
	u8 num;
	u8 data_pin[8];
}PIN;

/**
*LCD1602显示器标准定义结构体
*/
typedef struct
{
	u8 data_width;				//接口的数据宽度定义
	u8 display_lines;			//显示的行数
	u8 display_font;			//显示的字符格式
	u8 rw_ac_mode;				//读写期间AC地址增加模式
	u8 rw_display_move;			//读写期间显示移动
	u8 display_on_off;			//显示开关
	u8 cursor_on_off;			//光标的开关
	u8 cursor_blink;			//光标位置的字符闪烁
	u8 ddram_cursor_display_move;		//改变DDRAM期间光标移动
	u8 ddram_sc_move_driection;	//改变DDRAM期间光标或显示移动方向
	u8 ddram_first_line_address;//第一行的DDRAM地址
	u8 ddram_second_line_address;//第二行的DDRAM地址
	u8 cgram_address;			//CGRAM地址	
}LCD1602_initTypeDef;

/**
*基础操作函数
*/
void init1602(LCD1602_initTypeDef *lcd);
void write_commond_status(u8 commond);
void write_data_status(u8 wdata);

/**
*基础设置函数
*/
void clear_display(void);
void return_home(void);
u8 read_counter(void);
s8 lcd1602_set_ac(LCD1602_initTypeDef *lcd,u8 mode);
s8 lcd1602_set_rw_display_move(LCD1602_initTypeDef *lcd,u8 mode);
s8 lcd1602_set_display_on_off(LCD1602_initTypeDef *lcd,u8 onoff);
s8 lcd1602_set_cursor_on_off(LCD1602_initTypeDef *lcd,u8 onoff);
s8 lcd1602_set_change_ddram_cursor_display(LCD1602_initTypeDef *lcd,u8 cursor_display,u8 direction);
s8 lcd1602_set_font(LCD1602_initTypeDef *lcd,u8 font);

u8 read_data_current(void);
u8 read_data_local(u8 addr);
void read_string(u8 addr,u8 len,u8 *val);

/**
* CGRAM操作函数
*/
s8 write_one_cgram_5_8(u8 local,u8 *cgdata);
s8 write_one_cgram_5_10(u8 local,u8 *cgdata);

#endif

对应的.c文件如下:

#include "config.h"
#include "driver1602.h"
#include "database1602.h"

u8 width = 0;

/*写带检测忙状态位的命令寄存器*/
void write_commond_status(u8 commond){
	u8 flag = STATUS_BF_BUSY;
	while(flag == STATUS_BF_BUSY){
		if(width == interface_data_width_8){
			flag = read_status_8()&0x80;
		}else{
			flag = read_status_4()&0x80;
		}
	}
	if(width == interface_data_width_8){
		write_commond_8(commond);
	}else{
		write_commond_4(commond);
	}
}

/*写带忙检测的数据寄存器*/
void write_data_status(u8 wdata){
	u8 flag = STATUS_BF_BUSY;
	while(flag == STATUS_BF_BUSY){
		if(width == interface_data_width_8){
			flag = read_status_8()&0x80;
		}else{
			flag = read_status_4()&0x80;
		}
	}
	if(width == interface_data_width_8){
		write_data_8(wdata);
	}else{
		write_data_4(wdata);
	}
}

/*LCD1602的硬件初始化后默认设置8位数据接口模式
但是为了兼容4位接口模式,需要软件再做下面的初始化
*/
static void lcd_zero_init(void){
	u8 i = 0;
	//首先延迟15ms
	for(i = 0;i<15;i++){
		delay1ms();
	}
	if(width == interface_data_width_8){
		write_commond_8(0x30);
	}else{
		write_commond_4_4(0x30);
	}
	//再延迟4.1ms,这里延迟5ms
	for(i=0;i<5;i++){
		delay1ms();
	}
	if(width == interface_data_width_8){
		write_commond_8(0x30);
	}else{
		write_commond_4_4(0x30);
	}
	//再延迟100us,这里延迟5ms
	for(i=0;i<5;i++){
		delay1ms();
	}
	if(width == interface_data_width_8){
		write_commond_8(0x30);
	}else{
		write_commond_4_4(0x30);
		write_commond_4_4(0x20);
	}
}

/*
初始化LCD1602显示器的配置
*/
void init1602(LCD1602_initTypeDef *lcd){
	u8 tmp0 = 0;
	u8 tmp1 = 0;
	width = lcd->data_width;
	lcd_zero_init();
	if(width == interface_data_width_8){
		tmp1 = lcd->display_lines | lcd->display_font | 0x30;
	}else{
		tmp1 = lcd->display_lines | lcd->display_font | 0x20;	
	}
	write_commond_status(tmp1);
	write_commond_status(0x08);		//显示,光标和闪烁都关
	write_commond_status(0x01);		//清除显示器
	tmp1 = 0;
	tmp1 = lcd->rw_ac_mode | lcd->rw_display_move | 0x04;
	write_commond_status(tmp1);		//设置在读写期间AC的模式和显示的移动
	//芯片手册要求的初始化完成
	//根据配置信息继续初始化
	tmp1 = 0;
	tmp1 = lcd->display_on_off | lcd->cursor_on_off | lcd->cursor_blink | 0x08;
	write_commond_status(tmp1);	
	tmp1 = 0;
	tmp1 = lcd->ddram_cursor_display_move | lcd->ddram_sc_move_driection | 0x10;
	write_commond_status(tmp1);
}


/*内容全部清除,显示消失,地址计数器AC=0,自动加1模式
显示归位,游标和显示回到原点,不改变移位元元设置模式
*/
void clear_display(void){
	write_commond_status(0x01);
}


/*地址计数器AC=0,光标和游标所在位的字符回原点,DDRAM中的内容不改变*/
void return_home(void){
	write_commond_status(0x02);
}

/*返回计数器的值*/
u8 read_counter(void){
	u8 count = 0;
	if(width == interface_data_width_8){
		count = read_status_8();
	}else{
		count = read_status_4();
	}
	return (count & 0x7f);
}

/**
*设置AC的增加方式
*/
s8 lcd1602_set_ac(LCD1602_initTypeDef *lcd,u8 mode){
	u8 tmp = 0;
	if(mode == rw_data_ac_decrement){
		lcd->rw_ac_mode = rw_data_ac_decrement;
		tmp = 0x04 | lcd->rw_display_move;
		write_commond_status(tmp);
	}else if(mode == rw_data_ac_increment){
		lcd->rw_ac_mode = rw_data_ac_increment;
		tmp = 0x06 | lcd->rw_display_move;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}

/**
*设置读写数据时显示是否移动
*/
s8 lcd1602_set_rw_display_move(LCD1602_initTypeDef *lcd,u8 mode){
	u8 tmp = 0;
	if(mode == rw_data_display_no_move){
		lcd->rw_display_move = rw_data_display_no_move;
		tmp = 0x04 | lcd->rw_ac_mode;
		write_commond_status(tmp);
	}else if(mode == rw_data_display_move){
		lcd->rw_display_move = rw_data_ac_increment;
		tmp = 0x05 | lcd->rw_ac_mode;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}

/**
*设置显示是否关闭
*/
s8 lcd1602_set_display_on_off(LCD1602_initTypeDef *lcd,u8 onoff){
	u8 tmp = 0;
	if(onoff == display_off){
		lcd->display_on_off = display_off;
		tmp = 0x08 | lcd->cursor_on_off | lcd->cursor_blink;
		write_commond_status(tmp);
	}else if(onoff == display_on){
		lcd->display_on_off = display_on;
		tmp = 0x0c | lcd->cursor_on_off | lcd->cursor_blink;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}

/**
*设置显示的光标是否打开
*/
s8 lcd1602_set_cursor_on_off(LCD1602_initTypeDef *lcd,u8 onoff){
	u8 tmp = 0;
	if(onoff == cursor_off){
		lcd->cursor_on_off = cursor_off;
		tmp = 0x08 | lcd->display_on_off | lcd->cursor_blink;
		write_commond_status(tmp);
	}else if(onoff == cursor_on){
		lcd->cursor_on_off = cursor_on;
		tmp = 0x0a | lcd->display_on_off | lcd->cursor_blink;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}

/**
*设置显示的光标是否闪烁
*/
s8 lcd1602_set_cursor_blink(LCD1602_initTypeDef *lcd,u8 onoff){
	u8 tmp = 0;
	if(onoff == cursor_character_blink_off){
		lcd->cursor_blink = cursor_character_blink_off;
		tmp = 0x08 | lcd->display_on_off | lcd->cursor_on_off;
		write_commond_status(tmp);
	}else if(onoff == cursor_character_blink_on){
		lcd->cursor_blink = cursor_character_blink_on;
		tmp = 0x09 | lcd->display_on_off | lcd->cursor_on_off;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}

/**
*设置改变DDRAM内容时光标和显示的移动
*/
s8 lcd1602_set_change_ddram_cursor_display(LCD1602_initTypeDef *lcd,u8 cursor_display,u8 direction){
	u8 tmp = 0;
	if(cursor_display == ddram_move_cursor){
		lcd->ddram_cursor_display_move = ddram_move_cursor;
	}else if(cursor_display == ddram_move_display){
		lcd->ddram_cursor_display_move = ddram_move_display;
	}else{
		return -1;
	}
	
	if(direction == ddram_move_direction_left){
		lcd->ddram_sc_move_driection = ddram_move_direction_left;
	}else if(direction == ddram_move_direction_right){
		lcd->ddram_sc_move_driection = ddram_move_direction_right;
	}else{
		return -1;
	}
	tmp = 0x10 | lcd->ddram_sc_move_driection | lcd->ddram_cursor_display_move;
	write_commond_status(tmp);
	return 0;
}

/**
*设置字体格式
*/
s8 lcd1602_set_font(LCD1602_initTypeDef *lcd,u8 font){
	u8 tmp = 0;
	if(font == font_mode_5_8){
		lcd->display_font = font_mode_5_8;
		tmp = 0x30 | lcd->display_lines;
		write_commond_status(tmp);
	}else if(font == font_mode_5_10){
		lcd->display_font = font_mode_5_10;
		tmp = 0x34 | lcd->display_lines;
		write_commond_status(tmp);
	}else{
		return -1;
	}
	return 0;
}


/*读取默认位置的数据,当前数据指针所指数据*/
u8 read_data_current(void){
	u8 cur = 0;
	u8 value;
	if(width == interface_data_width_8){
		cur = read_status_8();
	}else{
		cur = read_status_4();
	}
	cur = cur & 0x7f;
	cur = cur | 0x80;
	value = read_data_local(cur);
	return value;
}


/*读取指定位置的字符*/
u8 read_data_local(u8 addr){
	u8 value;
	write_commond_status(addr|0x80);
	if(width == interface_data_width_8){
		value = read_data_8();
	}else{
		value = read_data_4();
	}
	return value;
}

/*从指定位置,读取指定长度的字符串*/
void read_string(u8 addr,u8 len,u8 *val){
	u8 i = 0;
	write_commond_status(addr|0x80);
	for(i=0;i<len;i++){
		if(width == interface_data_width_8){
			val[i]=read_data_8();
		}else{
			val[i]=read_data_4();
		}
	}
}

/*设置光标到指定位置*/
s8 set_cursor_local(u8 local,u8 line){
	if(local < DDRAM_PER_LINE_SIZE){
		if(line == 1){
			write_commond_status(DDRAM_ADDRESS_FIRST_LINE + local);
		}else if(line == 2){
			write_commond_status(DDRAM_ADDRESS_SECOND_LINE + local);
		}else{
			return -2;
		}
	}else{
		return -1;
	}
	return 0;
}

/*
写CGRAM空间 5*8字符
自定义显示
参数1:要写入的第几个字符
参数2:要写入的字符模板
*/
s8 write_one_cgram_5_8(u8 local,u8 *cgdata){
	u8 i = 0;
	if(local > 7){
		return -1;
	}
	write_commond_status(CGRAM_ADDRESS_SET_BASE| (local << 3));
	for(i = 0;i < 8;i++){
		write_data_status(*cgdata);
		cgdata++;
	}
	return 0;
}

/*
写5*10字符的CGRAM
CGRAM最多4个字符
参数1:要写入的第几个字符
参数2:要写入的字符模板
*/
s8 write_one_cgram_5_10(u8 local,u8 *cgdata){
	u8 i = 0;
	if(local > 3){
		return -1;
	}
	write_commond_status(CGRAM_ADDRESS_SET_BASE| (local << 4));
	for(i = 0;i < 11;i++){
		write_data_status(*cgdata);
		cgdata++;
	}
	return 0;
}

3、本来想使用在系统中使用配置文件的方式实现一些配置(在程序运行时更改配置参数)。这里在单片机中没有更好的方式,只能是更改这个配置文件更改再编译生成代码,只是可以简单写代码的方式。
配置文件代码如下(config.h):

#ifndef _CONFIG_H
#define _CONFIG_H

#include <reg52.h>

#define u8 unsigned char
#define u16 unsigned int
#define s8 char
#define s16 int
	
#define true 0x01
#define false 0x00
	


sbit RS = P2^0;						//定义指令和数据引脚
sbit RW = P2^1;						//定义读写引脚
sbit EN = P2^2;						//定义读写使能引脚

//#define datap P0 					//定义数据端口

sbit DATA0 = P0^0;
sbit DATA1 = P0^1;
sbit DATA2 = P0^2;
sbit DATA3 = P0^3;
sbit DATA4 = P0^4;
sbit DATA5 = P0^5;
sbit DATA6 = P0^6;
sbit DATA7 = P0^7;


#define CGRAM_ADDRESS_SET_BASE 0x40			//定义CGRAM的地址

#define DDRAM_ADDRESS_SET_BASE 0x80			//定义DDRAM的基地址
#define DDRAM_ADDRESS_FIRST_LINE 0x80		//定义显示的第一行的地址
#define DDRAM_ADDRESS_SECOND_LINE 0xC0 		//定义显示的第二行的地址

#define DDRAM_TOTAL_SIZE 80					//定义总的DDRAM的大小
#define DDRAM_PER_LINE_SIZE 40				//定义每一行的DDRAM大小
#define DDRAM_FIRST_LINE_SIZE 0x87			//定义第一行DDRAM的结束地址
#define DDRAM_SECOND_LINE_SIZE 0xE7			//定义第二行DDRAM的结束地址
																		

#endif

4、面向应用层的代码,主要是对底层驱动的再一次封装,这里有些函数放在驱动代码和应用代码中没有很好的区分,更好的区分可以更像内核中代码的实现。
应用代码如下,chardriver1602.h是头文件:

#include "config.h"

#ifndef _CHARDRIVER1602_H
#define _CHARDRIVER1602_H


s8 write_first(u8 local,u8 value);
s8 write_second(u8 local,u8 value);
s8 write_char(u8 local,u8 value,u8 line);
s8 write_first_line_string(u8 local,u8 *str);
s8 write_second_line_string(u8 local,u8 *str);
s8 write_string(u8 local,u8 line,u8 *str);


s8 clear_char(u8 local,u8 line);
s8 clear_line(u8 line);
s8 clear_string(u8 local,u8 len,u8 line);
s8 replace_char(u8 local,u8 line,u8 value);
s8 replace_string(u8 local,u8 line,u8 *str);

void displaystring(u8 *str);


s8 save_cgram_table();
s8 cgram_show_num(u8 x,u8 num);


#endif

对应的.c文件如下(这个代码中的对CGRAM相关的显示实现来自STC官网论坛的另外一个博主,显示出效果很好):

#include "driver1602.h"
#include "chardriver1602.h"
#include "string.h"


#define LT 0	//数字分块的序号
#define UB 1
#define RT 2
#define LL 3
#define LB 4
#define LR 5
#define UMB 6
#define LMB 7

#define B00000 00	
#define B00001 01
#define B00010 02
#define B00011 03
#define B00100 04
#define B00101 05
#define B00110 06
#define B00111 07

#define B01000 08
#define B01001 09
#define B01010 10
#define B01011 11
#define B01100 12
#define B01101 13
#define B01110 14
#define B01111 15

#define B10000 16
#define B10001 17
#define B10010 18
#define B10011 19
#define B10100 20
#define B10101 21
#define B10110 22
#define B10111 23

#define B11000 24
#define B11001 25
#define B11010 26
#define B11011 27
#define B11100 28
#define B11101 29
#define B11110 30
#define B11111 31


unsigned char code _LIF[8][8] = {
{ B00111, B01111, B11111, B11111, B11111, B11111, B11111, B11111},	//数字分块图像
{ B11111, B11111, B11111, B00000, B00000, B00000, B00000, B00000},
{ B11100, B11110, B11111, B11111, B11111, B11111, B11111, B11111},
{ B11111, B11111, B11111, B11111, B11111, B11111, B01111, B00111},
{ B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111},
{ B11111, B11111, B11111, B11111, B11111, B11111, B11110, B11100},
{ B11111, B11111, B11111, B00000, B00000, B00000, B11111, B11111},
{ B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111},
};


unsigned char code bevelChar[11][2][3] = {	//每个数字所用的分块,32代表空
  {{LT, UB, RT}, {LL, LB, LR}}, //0 
  {{UB, RT, 32}, {LB, LMB, LB}}, //1 
  {{UMB, UMB, RT}, {LL, LB, LB}}, //2 
  {{UMB, UMB, RT}, {LB, LB, LR}}, //3 
  {{LL, LB, LMB}, {32, 32, LMB}}, //4 
  {{LT, UMB, UMB}, {LB, LB, LR}}, //5 
  {{LT, UMB, UMB}, {LL, LB, LR}}, //6 
  {{UB, UB, RT}, {32, 32, LMB}}, //7
  {{LT, UMB, RT}, {LL, LB, LR}}, //8
  {{LT, UMB, RT}, {LB, LB, LR}}, //9
  {{ 32, 32, 32}, {32, 32, 32}} //Blank
};




/*写第一行的一个字符*/
s8 write_first(u8 local,u8 value){
	if(local <= DDRAM_PER_LINE_SIZE){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE + local);
		write_data_status(value);
	}else{
		return -1;
	}
	return 0;
}

/*写第二行的一个字符*/
s8 write_second(u8 local,u8 value){
	if(local <= DDRAM_PER_LINE_SIZE){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE + local);
		write_data_status(value);
	}else{
		return -1;
	}
	return 0;
}

/**
*给指定行的位置写一个字符
*参数1:写的字符的位置
*参数2:要写的字符值
*参数3:要写的行
*返回值:0成功写入,其它值错误
*/
s8 write_char(u8 local,u8 value,u8 line){
	if(line == 1){
		if(local <= DDRAM_PER_LINE_SIZE){
			write_commond_status(DDRAM_ADDRESS_FIRST_LINE + local);
			write_data_status(value);
		}else{
			return -1;
		}
	}else if(line == 2){
		if(local <= DDRAM_PER_LINE_SIZE){
			write_commond_status(DDRAM_ADDRESS_SECOND_LINE + local);
			write_data_status(value);
		}else{
			return -1;
		}
	}else{
		return -2;
	}
	return 0;
}

/**
*写第一行字符串
*/
s8 write_first_line_string(u8 local,u8 *str){
	u8 len = 0;
	len = strlen(str);
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if((local + len) > DDRAM_PER_LINE_SIZE){
		len = DDRAM_PER_LINE_SIZE - local;
	}
	write_commond_status(DDRAM_ADDRESS_FIRST_LINE + local);
	while(len > 0){
		write_data_status(*str);
		str++;
		len--;
	}
	return 0;
}

/**
*写第二行字符串
*/
s8 write_second_line_string(u8 local,u8 *str){
	u8 len = 0;
	len = strlen(str);
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if((local + len) > DDRAM_PER_LINE_SIZE){
		len = DDRAM_PER_LINE_SIZE - local;
	}
	write_commond_status(DDRAM_ADDRESS_SECOND_LINE + local);
	while(len > 0){
		write_data_status(*str);
		str++;
		len--;
	}
	return 0;
}


/*根据行号判断写入第一行还是第二行,写入字符串*/
s8 write_string(u8 local,u8 line,u8 *str){
	if(line == 1){
		write_first_line_string(local,str);
	}else if(line == 2){
		write_second_line_string(local,str);
	}else{
		return -1;
	}
	return 0;
}

/*清除特定位置的字符并保持原来地址计数器的位置不变*/
s8 clear_char(u8 local,u8 line){
	u8 curlocation = read_counter();
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if(line == 1){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE + local);
	}else if(line == 2){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE + local);
	}else{
		return -2;
	}
	write_data_status(0x20);
	write_commond_status(curlocation|0x80);
	return 0;
}


/*清除一行,一行的40个字符全部清除*/
s8 clear_line(u8 line){
	u8 i = 0;
	if(line == 1){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE);
	}else if(line == 2){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE);
	}else{
		return -1;
	}
	for(i = 0;i<40;i++){
		write_data_status(0x20);
	}
	return 0;	
}



/*清除特定位置的几个字符,且不改变原始的AC值*/
s8 clear_string(u8 local,u8 len,u8 line){
	u8 s = local;
	u8 length = len;
	u8 i = 0;
	u8 curlocation = read_counter();
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if((s + length) > DDRAM_PER_LINE_SIZE){
		length = DDRAM_PER_LINE_SIZE - s;
	}
	if(line == 1){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE + s);
	}else if(line == 2){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE + s);
	}else{
		return -2;
	}
	for(i = 0;i<length;i++){
		write_data_status(0x20);
	}
	write_commond_status(curlocation|0x80);
	return 0;
}


/*替换特定位置的字符,且不改变光标位置*/
s8 replace_char(u8 local,u8 line,u8 value){
	u8 s = local;
	u8 curlocation = read_counter();
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if(line == 1){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE + s);
	}else if(line == 2){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE + s);
	}else{
		return -2;
	}
	write_data_status(value);
	write_commond_status(curlocation|0x80);
	return 0;
}


/*替换特定位置的几个字符*/
s8 replace_string(u8 local,u8 line,u8 *str){
	u8 s = local;
	u8 i = 0;
	u8 curlocation = read_counter();
	u8 len = strlen(str);
	if(local > DDRAM_PER_LINE_SIZE){
		return -1;
	}
	if(line == 1){
		write_commond_status(DDRAM_ADDRESS_FIRST_LINE + s);
	}else if(line == 2){
		write_commond_status(DDRAM_ADDRESS_SECOND_LINE + s);
	}else{
		return -2;
	}
	if((s + len) > DDRAM_PER_LINE_SIZE){
		len = DDRAM_PER_LINE_SIZE - s;
	}
	while(len > 0){
		write_data_status(*str);
		str++;
		len--;
	}
	write_commond_status(curlocation|0x80);
	return 0;
}


/*从开始位置显示一整段字符到可显示的位置*/
void displaystring(u8 *str){
	u8 length = strlen(str);
	if(length <= 16){
		write_first_line_string(0,str);
	}else{
		write_first_line_string(0,str);
		write_second_line_string(0,str+16);
	}
}


//存入数字分块的图像
s8 save_cgram_table(){
	u8 i;
	for(i=0;i<8;i++)
		write_one_cgram_5_8(i,_LIF[i]);
	return write_one_cgram_5_8(0,_LIF[0]);
}

//显示超大数字	x:显示位置0~3;num:显示的值
s8 cgram_show_num(u8 x,u8 num){
	u8 m,n;
	if(x < 4)
	{
		for(m = 0;m < 2;m++)
		{
			if(m == 0){
				write_commond_status(DDRAM_ADDRESS_FIRST_LINE + (x * 4));
			}else if(m == 1){
				write_commond_status(DDRAM_ADDRESS_SECOND_LINE + (x * 4));
			}else{
				return -1;
			}
			for(n = 0;n < 3;n++)
			{
				write_data_status(bevelChar[num][m][n]);
			}
		}
	}
	return 0;
}

5、主函数主要是显示2行字符,并测试了对中间某个字符的删除和替换操作并保持光标位置不变,并调用了CGRAM的显示功能,显示另外的自定义字符效果,代码如下(main.c):

#include "chardriver1602.h"
#include "driver1602.h"
#include "lib.h"
#include <intrins.h>

sbit ML = P1^4;
sbit MR = P1^5;

void main(){
	u8 i = 0;
	LCD1602_initTypeDef lcd;
	lcd.data_width = interface_data_width_4;
	lcd.display_lines = display_line_2;
	lcd.display_font = font_mode_5_8;
	lcd.rw_ac_mode = rw_data_ac_increment;
	lcd.rw_display_move = rw_data_display_no_move;
	lcd.display_on_off = display_on;
	lcd.cursor_on_off = cursor_on;
	lcd.cursor_blink = cursor_character_blink_off;
	lcd.ddram_cursor_display_move = ddram_move_cursor;
	lcd.ddram_sc_move_driection = ddram_move_direction_left;
	lcd.ddram_first_line_address = DDRAM_ADDRESS_FIRST_LINE;
	lcd.ddram_second_line_address = DDRAM_ADDRESS_SECOND_LINE;
	lcd.cgram_address = CGRAM_ADDRESS_SET_BASE;
	//datap = 0xff;
	P0 = 0xff;
	P2 = 0xff;
	P3 = 0xff;
	init1602(&lcd);
	
	displaystring("abcdefghigklmnopqrstuvwxyz");
	for(i = 0;i<5;i++){
		delay1s();
	}
	
	clear_char(3,1);
	for(i = 0;i<5;i++){
		delay1s();
	}
	replace_char(5,1,'W');
	for(i = 0;i<5;i++){
		delay1s();
	}
	
	clear_display();
	
	save_cgram_table();
	
	for(i = 0;i<2;i++){
		delay1s();
	}
	
	cgram_show_num(0,0);
	cgram_show_num(1,1);
	cgram_show_num(2,2);
	cgram_show_num(3,3);
	
	for(i = 0;i<5;i++){
		delay1s();
	}
	
	cgram_show_num(0,4);
	cgram_show_num(1,5);
	cgram_show_num(2,6);
	cgram_show_num(3,7);
	
	for(i = 0;i<5;i++){
		delay1s();
	}
	
	cgram_show_num(0,8);
	cgram_show_num(1,9);
	cgram_show_num(2,10);
	cgram_show_num(3,0);
	
	for(i = 0;i<2;i++){
		delay1s();
	}
	
	while(1){
		MR = 0;
		ML = 1;
		for(i = 0;i<10;i++){
			delay1s();
		}
		MR = 1;
		ML = 0;
		for(i = 0;i<5;i++){
			delay1s();
		}
	}
}

包含的lib.h和lib.c文件如下:

#ifndef _LIB_
#define _LIB_

#define u8 unsigned char
#define u16 unsigned int


u8 hexToInt(u8 hex);
void hexToIntString(u8 hex,u8 *value);
void delay1s();
void delay100ms(void);
void Delay50us(void);

#endif

lib.c文件:

#include "lib.h"
#include <intrins.h>
/*工具类*/

/*将十六进制转换为整数字符*/
u8 hexToInt(u8 hex){
	u8 val=0;
	if(hex>=0x00&&hex<=0x09){
		val = hex+0x30;
	}
	return val;
}

/*将十六进制转换为整数字符串*/
void hexToIntString(u8 hex,u8 *value){
	u8 hval = hex;
	u8 i = 0;
	u8 temp = 0;
	while((hval/0x0a!=0)||(hval%0x0a!=0)){
		temp = hval%0x0a;
		value[i] = hexToInt(temp);
		hval = hval/0x0a;
		if(hval < 0x00){
			break;
		}
		temp = 0;
		i++;
	}
}

/*延时1s程序*/
void delay1s(){
    unsigned char data i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

/*延时100ms程序*/
void delay100ms(void){
   unsigned char data i, j;

	i = 180;
	j = 73;
	do
	{
		while (--j);
	} while (--i);
}

/*延迟50us*/
void Delay50us(void)	//@11.0592MHz
{
	unsigned char data i;

	i = 20;
	while (--i);
}

实际运行效果如下面图所示:





6、对内核中代码的分层设计(代码的解耦)还需要更多的实践,上面的代码只是简单的尝试,还有很多的地方需要注意(如底层函数的注册实现,设备结构体的更好的实现),后续有时间再改进。

posted @ 2025-03-25 22:03  xiaobing3314  阅读(113)  评论(0)    收藏  举报