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)
/**
显示字符模式定义,58和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、对内核中代码的分层设计(代码的解耦)还需要更多的实践,上面的代码只是简单的尝试,还有很多的地方需要注意(如底层函数的注册实现,设备结构体的更好的实现),后续有时间再改进。
浙公网安备 33010602011771号