很久以前就想把LCD1602的计算器换成LCD12864,
目前逻辑是弄出来了,显示出了点小问题,往后再看看显示的问题
第一个文件:config.h
/*
*******************************************************************************
* 《手把手教你学51单片机(C语言版)》
* 配套 KST-51 单片机开发板 示例源代码
*
* (c) 版权所有 2014 金沙滩工作室/清华大学出版社 保留所有权利
* 获取更多资料请访问:http://www.kingst.org
*
* 文件名:config.h
* 描 述:工程配置头文件
* 版本号:v1.0.0
* 备 注:
*******************************************************************************
*/
#ifndef _CONFIG_H
#define _CONFIG_H
/* 通用头文件 */
#include
#include
/* 数据类型定义 */
typedef signed char int8; // 8位有符号整型数
typedef signed int int16; //16位有符号整型数
typedef signed long int32; //32位有符号整型数
typedef unsigned char uint8; // 8位无符号整型数
typedef unsigned int uint16; //16位无符号整型数
typedef unsigned long uint32; //32位无符号整型数
/* 全局运行参数定义 */
#define OSC_FREQ (11059200) //振荡器频率值,单位Hz
#define SYS_MCLK (OSC_FREQ/12) //系统主时钟频率,即振荡器频率÷12
/* IO引脚分配定义 */
sbit KEY_IN_1 = P2^4; //矩阵按键的扫描输入引脚1
sbit KEY_IN_2 = P2^5; //矩阵按键的扫描输入引脚2
sbit KEY_IN_3 = P2^6; //矩阵按键的扫描输入引脚3
sbit KEY_IN_4 = P2^7; //矩阵按键的扫描输入引脚4
sbit KEY_OUT_1 = P2^3; //矩阵按键的扫描输出引脚1
sbit KEY_OUT_2 = P2^2; //矩阵按键的扫描输出引脚2
sbit KEY_OUT_3 = P2^1; //矩阵按键的扫描输出引脚3
sbit KEY_OUT_4 = P2^0; //矩阵按键的扫描输出引脚4
sbit ADDR0 = P1^0; //LED位选译码地址引脚0
sbit ADDR1 = P1^1; //LED位选译码地址引脚1
sbit ADDR2 = P1^2; //LED位选译码地址引脚2
sbit ADDR3 = P1^3; //LED位选译码地址引脚3
sbit ENLED = P1^4; //LED显示部件的总使能引脚
#define LCD1602_DB P0 //1602液晶数据端口
sbit LCD1602_RS = P1^0; //1602液晶指令/数据选择引脚
sbit LCD1602_RW = P1^1; //1602液晶读写引脚
sbit LCD1602_E = P1^5; //1602液晶使能引脚
sbit DS1302_CE = P1^7; //DS1302片选引脚
sbit DS1302_CK = P3^5; //DS1302通信时钟引脚
sbit DS1302_IO = P3^4; //DS1302通信数据引脚
sbit I2C_SCL = P3^7; //I2C总线时钟引脚
sbit I2C_SDA = P3^6; //I2C总线数据引脚
sbit BUZZER = P1^6; //蜂鸣器控制引脚
sbit IO_18B20 = P3^2; //DS18B20通信引脚
sbit IR_INPUT = P3^3; //红外接收引脚
#endif
第二个文件keyboard.c
#include
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ '1', '2', '3', 0x26 }, //数字键1、数字键2、数字键3、向上键
{ '4', '5', '6', 0x25 }, //数字键4、数字键5、数字键6、向左键
{ '7', '8', '9', 0x28 }, //数字键7、数字键8、数字键9、向下键
{ '0', 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};
unsigned char pdata KeySta[4][4] = { //全部矩阵按键的当前状态
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
extern void Reset();
extern void GetResult();
extern void NumKeyAction(unsigned char n);
extern void OprtKeyAction(unsigned char type);
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void KeyAction(unsigned char keycode)
{
if((keycode >= '0') && (keycode <= '9'))
{
NumKeyAction(keycode - '0');
}
else if(keycode == 0x26)
{
OprtKeyAction(0);
}
else if(keycode == 0x28)
{
OprtKeyAction(1);
}
else if(keycode == 0x25)
{
OprtKeyAction(2);
}
else if(keycode == 0x27)
{
OprtKeyAction(3);
}
else if(keycode == 0x0D)
{
GetResult();
}
else if(keycode == 0x1B)
{
Reset();
LcdShowStr(15, 1, "0");
}
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
unsigned char i, j;
static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
for (i=0; i<4; i++) //循环检测4*4的矩阵按键
{
for (j=0; j<4; j++)
{
if (backup[i][j] != KeySta[i][j]) //检测按键动作
{
if (backup[i][j] != 0) //按键按下时执行动作
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
backup[i][j] = KeySta[i][j]; //刷新前一次的备份值
}
}
}
}
/* 按键扫描函数,需在定时中断中调用,推荐调用间隔1ms */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
};
//将一行的4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i=0; i<4; i++) //每行4个按键,所以循环4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
}
//执行下一次的扫描输出
keyout++; //输出索引递增
keyout &= 0x03; //索引值加到4即归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
}
第三个文件LCD12864.c
/*
*******************************************************************************
* 《手把手教你学51单片机(C语言版)》
* 配套 KST-51 单片机开发板 示例源代码
*
* (c) 版权所有 2014 金沙滩工作室/清华大学出版社 保留所有权利
* 获取更多资料请访问:http://www.kingst.org
*
* 文件名:Lcd12864.c
* 描 述:12864点阵液晶驱动模块
* 版本号:v1.0.0
* 备 注:适用于KST-51开发板配套的12864液晶模块
*******************************************************************************
*/
#define _LCD_12864_C
#include "config.h"
#include "Lcd12864.h"
/* 等待液晶准备好 */
void LcdWaitReady()
{
uint8 sta;
LCD12864_DB = 0xFF;
LCD12864_RS = 0;
LCD12864_RW = 1;
do {
LCD12864_E = 1;
sta = LCD12864_DB;
LCD12864_E = 0;
} while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止
}
/* 向液晶写入一字节命令,cmd-待写入命令值 */
void LcdWriteCmd(uint8 cmd)
{
LcdWaitReady();
LCD12864_RS = 0;
LCD12864_RW = 0;
LCD12864_DB = cmd;
LCD12864_E = 1;
LCD12864_E = 0;
}
/* 向液晶写入一字节数据,dat-待写入数据值 */
void LcdWriteDat(uint8 dat)
{
LcdWaitReady();
LCD12864_RS = 1;
LCD12864_RW = 0;
LCD12864_DB = dat;
LCD12864_E = 1;
LCD12864_E = 0;
}
/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
void LcdSetCursor(uint8 x, uint8 y)
{
unsigned char addr;
if (y == 0) //由输入的屏幕坐标计算显示RAM的地址
addr = 0x80 + x; //第一行字符地址从0x00起始
else if (y == 1)
addr = 0x90 + x; //第二行字符地址从0x40起始
else if (y == 2)
addr = 0x88 + x; //第二行字符地址从0x40起始
else if (y == 3)
addr = 0x98 + x; //第二行字符地址从0x40起始
LcdWriteCmd(addr | 0x80); //设置RAM地址
}
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{
LcdSetCursor(x, y); //设置起始地址
while (*str != '\0') //连续写入字符串数据,直到检测到结束符
{
LcdWriteDat(*str++);
}
}
/*
* 函数名:LcdShowString
* 描 述:在显示屏上显示一串字符串
* 输 入:str - 待显示字符串指针
* x - 屏幕显示横坐标(以像素为单位)
* y - 屏幕显示纵坐标(以像素为单位)
* 输 出:无
* 备 注:输入的字符串必须符合C语言规范,即以'\0'-NULL为结束标识;
* x、y坐标必须是16的整数倍,因DDRAM地址以全角字符(16*16pixel)为单位。
*/
void LcdShowString(uint8 x, uint8 y, uint8 *str)
{
uint8 addr;
//由输入的显示坐标计算DDRAM的地址
x >>= 4;
// y=48
y >>= 4;
// y=3
if (y >= 2)
{
//y = 3 - 2
y -= 2;
//x = 8 + 8
x += 8;
}
//addr = 16+8
//16+16
addr = y*16 + x;
//由起始DDRAM地址连续写入字符串
LcdWriteCmd(0x30); //启动DDRAM操作
LcdWriteCmd(0x80|addr);
//1000 0000 | 0001 1000
while (*str != '\0')
{
LcdWriteDat(*str);
str++;
}
}
/*
* 函数名:LcdShowImage
* 描 述:在显示屏上显示一幅图像
* 输 入:img - 待显示图像指针
* x - 屏幕显示横坐标(以像素为单位)
* y - 屏幕显示纵坐标(以像素为单位)
* w - 图像宽度(以像素为单位)
* h - 图像高度(以像素为单位)
* 输 出:无
* 备 注:x与w必须是16的整数倍,因CGRAM最小寻址单位为2字节;y与h可为0-63的任意值。
*/
void LcdShowImage(uint8 x, uint8 y, uint8 w, uint8 h, uint8 *img)
{
int16 i;
uint8 xi,yi;
uint8 xt,yt;
x >>= 4;
w >>= 3;
i = 0;
LcdWriteCmd(0x36); //启动CGRAM操作
for (yi=0; yi<h; yi++)
{
yt = y+yi;
xt = x;
if (yt >= 32)
{
yt -= 32;
xt += 8;
}
LcdWriteCmd(0x80|yt);
LcdWriteCmd(0x80|xt);
for (xi=0; xi<w; xi++)
{
LcdWriteDat(img[i++]);
}
}
}
/* 区域清除,清除从(x,y)坐标起始的len个字符位 */
void LcdAreaClear(uint8 x, uint8 y, uint8 len)
{
LcdSetCursor(x, y); //设置起始地址
while (len--) //连续写入空格
{
LcdWriteDat(' ');
}
}
void LcdFullClear()
{
LcdWriteCmd(0x01);
}
/*
* 函数名:LcdClearArea
* 描 述:清除屏幕上的一块图形显示区域
* 输 入:x - 区域起始横坐标(以像素为单位)
* y - 区域起始纵坐标(以像素为单位)
* w - 区域宽度(以像素为单位)
* h - 区域高度(以像素为单位)
* 输 出:无
* 备 注:x与w必须是16的整数倍,因CGRAM最小寻址单位为2字节;y与h可为0-63的任意值。
*/
void LcdClearArea(uint8 x, uint8 y, uint8 w, uint8 h)
{
uint8 xi,yi;
uint8 xt,yt;
x >>= 4;
w >>= 3;
LcdWriteCmd(0x36); //启动CGRAM操作
for (yi=0; yi<h; yi++)
{
yt = y+yi;
xt = x;
if (yt >= 32)
{
yt -= 32;
xt += 8;
}
LcdWriteCmd(0x80|yt);
LcdWriteCmd(0x80|xt);
for (xi=0; xi<w; xi++)
{
LcdWriteDat(0x00); //写入0x00以清除显示
}
}
}
/* 12864液晶初始化函数 */
void InitLcd12864()
{
uint8 x, y;
//字符模式初始化
LcdWriteCmd(0x30); //基本指令集
LcdWriteCmd(0x01); //清零字符显示
LcdWriteCmd(0x02); //地址归位
LcdWriteCmd(0x0C); //开显示
//图形模式初始化
LcdWriteCmd(0x34); //启动扩展指令,关闭图形显示
for (y=0; y<32; y++) //清零图形显示缓冲区
{
LcdWriteCmd(0x80|y);
LcdWriteCmd(0x80|0);
for (x=0; x<32; x++)
{
LcdWriteDat(0x00);
}
}
LcdWriteCmd(0x36); //开启图形模式显示
}
第四个文件LCD12864.H
/*
*******************************************************************************
* 《手把手教你学51单片机(C语言版)》
* 配套 KST-51 单片机开发板 示例源代码
*
* (c) 版权所有 2014 金沙滩工作室/清华大学出版社 保留所有权利
* 获取更多资料请访问:http://www.kingst.org
*
* 文件名:Lcd12864.c
* 描 述:12864点阵液晶驱动模块的头文件
* 版本号:v1.0.0
* 备 注:
*******************************************************************************
*/
#ifndef _LCD_12864_H
#define _LCD_12864_H
#include
#include
//12864液晶口线与板载1602液晶相同
#define LCD12864_DB LCD1602_DB
#define LCD12864_RS LCD1602_RS
#define LCD12864_RW LCD1602_RW
#define LCD12864_E LCD1602_E
#ifndef _LCD_12864_C
#endif
void InitLcd12864();
void LcdWaitReady();
void LcdFullClear();
//void LcdWriteCmd(uint8 cmd);
#endif
主函数:main.c文件
#include
#include "Lcd12864.h"
#include "config.h"
unsigned char step = 0;
unsigned char oprt = 0;
signed long num1 = 0;
signed long num2 = 0;
signed long result = 0;
unsigned char T0RH = 0;
unsigned char T0RL = 0;
void ConfigTimer0(unsigned int ms);
extern void KeyScan();
extern void KeyDriver();
extern void InitLcd1602();
extern void LcdShowString(uint8 x, uint8 y, uint8 *str);
extern void LcdFullClear();
extern void LcdAreaClear(uint8 x, uint8 y, uint8 len);
//extern void LcdShowString(uint8 x, uint8 y, uint8 *str);
extern void LcdShowImage(uint8 x, uint8 y, uint8 w, uint8 h, uint8 *img);
extern void LcdClearArea(uint8 x, uint8 y, uint8 w, uint8 h);
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void main()
{
EA = 1; //开总中断
ConfigTimer0(1); //配置T0定时1ms
InitLcd12864(); //初始化液晶
//LcdShowString(0, 0, "111111111111111"); //初始显示一个数字0
//LcdShowString(0, 16, "1111111111111111");
//LcdShowString(0, 32, "1111111111111111");
LcdShowString(112, 48, "0"); //因为字符显示只能占用一半
while (1)
{
KeyDriver(); //调用按键驱动
}
}
unsigned char LongToString(unsigned char *str, signed long dat)
{
signed char i = 0;
unsigned char len = 0;
unsigned char buf[12];
if(dat < 0)
{
dat = -dat;
*str++ = '-';
len++;
}
do{
buf[i++] = dat % 10;
dat /= 10;
}while(dat > 0);
len += i;
while(i-- > 0)
{
*str++ = buf[i] + '0';
}
*str = '\0';
return len;
}
void ShowOprt(unsigned char y, unsigned char type)
{
switch(type)
{
case 0: LcdShowString(0, y, "+"); break;
case 1: LcdShowString(0, y, "-"); break;
case 2: LcdShowString(0, y, "*"); break;
case 3: LcdShowString(0, y, "/"); break;
default: break;
}
}
void Reset()
{
num1 = 0;
num2 = 0;
step = 0;
LcdFullClear();
}
void NumKeyAction(unsigned char n)
{
unsigned char len = 0;
unsigned char str[12];
if(step > 1)
{
Reset();
}
if(step == 0)
{
num1 = num1*10 + n;
len = LongToString(str, num1);
LcdShowString(128 - len, 48, str);
}
else
{
num2 = num2*10 + n;
len = LongToString(str, num2);
LcdShowString(128 - len, 48, str);
}
}
void OprtKeyAction(unsigned char type)
{
unsigned char len;
unsigned char str[12];
if(step == 0)
{
len = LongToString(str, num1);
LcdAreaClear(0, 48, 128-len);
LcdShowString(128-len, 32, str);
ShowOprt(48, type);
LcdAreaClear(1, 48, 14);//待定
LcdShowString(112, 48, "0");
oprt = type;
step = 1;
}
}
void GetResult()
{
unsigned char len;
unsigned char str[12];
if(step == 1)
{
step = 2;
switch(oprt)
{
case 0: result = num1 + num2; break;
case 1: result = num1 - num2; break;
case 2: result = num1 * num2; break;
case 3: result = num1 / num2; break;
default: break;
}
len = LongToString(str, num2);
LcdAreaClear(1, 32, 128-1-len);
ShowOprt(32, oprt);
LcdShowString(128-len, 32, str);
len = LongToString(str, result);
LcdShowString(0, 48, "=");
LcdAreaClear(1, 48, 16-1-len);
LcdShowString(128-len, 48, str);
}
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 12; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = T0RH; //加载T0重载值
TL0 = T0RL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
}
/* T0中断服务函数,执行按键扫描 */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
KeyScan(); //按键扫描
}
遇到的问题:中文字符是1616,字符是816,数字属于英文字符,所以只占了8*16,中文字符的一半,默认是左半边对齐,右移是需要同时显示一个英文空格和一个英文字符。目的:想让字符0显示在12864的右下角
LCD12864.c
#include
typedef signed char int8; // 8位有符号整型数
typedef signed int int16; //16位有符号整型数
typedef signed long int32; //32位有符号整型数
typedef unsigned char uint8; // 8位无符号整型数
typedef unsigned int uint16; //16位无符号整型数
typedef unsigned long uint32; //32位无符号整型数
#define LCD1602_DB P0 //1602液晶数据端口
sbit LCD1602_RS = P1^0; //1602液晶指令/数据选择引脚
sbit LCD1602_RW = P1^1; //1602液晶读写引脚
sbit LCD1602_E = P1^5; //1602液晶使能引脚
#define LCD12864_DB LCD1602_DB
#define LCD12864_RS LCD1602_RS
#define LCD12864_RW LCD1602_RW
#define LCD12864_E LCD1602_E
void LcdShowString(uint8 x, uint8 y, uint8 *str);
void InitLcd12864();
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void LcdFullClear();
/* 等待液晶准备好 */
void LcdWaitReady()
{
uint8 sta;
LCD12864_DB = 0xFF;
LCD12864_RS = 0;
LCD12864_RW = 1;
do {
LCD12864_E = 1;
sta = LCD12864_DB;
LCD12864_E = 0;
} while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止
}
/* 向液晶写入一字节命令,cmd-待写入命令值 */
void LcdWriteCmd(uint8 cmd)
{
LcdWaitReady();
LCD12864_RS = 0;
LCD12864_RW = 0;
LCD12864_DB = cmd;
LCD12864_E = 1;
LCD12864_E = 0;
}
void LcdWriteDat(uint8 dat)
{
LcdWaitReady();
LCD12864_RS = 1;
LCD12864_RW = 0;
LCD12864_DB = dat;
LCD12864_E = 1;
LCD12864_E = 0;
}
void LcdSetCursor(unsigned char x, unsigned char y)
{
uint8 addr;
//由输入的显示坐标计算DDRAM的地址
x >>= 4;
y >>= 4;
if (y >= 2)
{
y -= 2;
x += 8;
}
addr = y*16 + x;
//由起始DDRAM地址连续写入字符串
LcdWriteCmd(0x30); //启动DDRAM操作
LcdWriteCmd(0x80|addr);
}
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{
LcdSetCursor(x, y);
while (*str != '\0')
{
LcdWriteDat(*str);
str++;
}
}
/* 区域清除,清除从(x,y)坐标起始的len个字符位 */
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len)
{
LcdSetCursor(x, y); //设置起始地址
while (len--) //连续写入空格
{
LcdWriteDat(' ');
}
}
/* 整屏清除 */
void LcdFullClear()
{
LcdWriteCmd(0x01);
}
void InitLcd12864()
{
uint8 x, y;
//字符模式初始化
LcdWriteCmd(0x30); //基本指令集
LcdWriteCmd(0x01); //清零字符显示
LcdWriteCmd(0x02); //地址归位
LcdWriteCmd(0x0C); //开显示
//图形模式初始化
LcdWriteCmd(0x34); //启动扩展指令,关闭图形显示
for (y=0; y<32; y++) //清零图形显示缓冲区
{
LcdWriteCmd(0x80|y);
LcdWriteCmd(0x80|0);
for (x=0; x<32; x++)
{
LcdWriteDat(0x00);
}
}
LcdWriteCmd(0x36); //开启图形模式显示
}
keyboard.c
#include
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ '1', '2', '3', 0x26 }, //数字键1、数字键2、数字键3、向上键
{ '4', '5', '6', 0x25 }, //数字键4、数字键5、数字键6、向左键
{ '7', '8', '9', 0x28 }, //数字键7、数字键8、数字键9、向下键
{ '0', 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};
unsigned char pdata KeySta[4][4] = { //全部矩阵按键的当前状态
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
extern void KeyAction(unsigned char keycode);
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
unsigned char i, j;
static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
for (i=0; i<4; i++) //循环检测4*4的矩阵按键
{
for (j=0; j<4; j++)
{
if (backup[i][j] != KeySta[i][j]) //检测按键动作
{
if (backup[i][j] != 0) //按键按下时执行动作
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
backup[i][j] = KeySta[i][j]; //刷新前一次的备份值
}
}
}
}
/* 按键扫描函数,需在定时中断中调用,推荐调用间隔1ms */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
};
//将一行的4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i=0; i<4; i++) //每行4个按键,所以循环4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
}
//执行下一次的扫描输出
keyout++; //输出索引递增
keyout &= 0x03; //索引值加到4即归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
}
main.c
#include
unsigned char step = 0; //操作步骤
unsigned char oprt = 0; //运算类型
signed long num1 = 0; //操作数1
signed long num2 = 0; //操作数2
signed long result = 0; //运算结果
unsigned char T0RH = 0; //T0重载值的高字节
unsigned char T0RL = 0; //T0重载值的低字节
void ConfigTimer0(unsigned int ms);
extern void KeyScan();
extern void KeyDriver();
extern void InitLcd12864();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);
extern void LcdFullClear();
void main()
{
EA = 1; //开总中断
ConfigTimer0(1); //配置T0定时1ms
InitLcd12864(); //初始化液晶
LcdShowStr(0, 48, "0"); //初始显示一个数字0
while (1)
{
KeyDriver(); //调用按键驱动
}
}
/* 长整型数转换为字符串,str-字符串指针,dat-待转换数,返回值-字符串长度 */
unsigned char LongToString(unsigned char *str, signed long dat)
{
signed char i = 0;
unsigned char len = 0;
unsigned char buf[12];
if (dat < 0) //如果为负数,首先取绝对值,并在指针上添加负号
{
dat = -dat;
*str++ = '-';
len++;
}
do { //先转换为低位在前的十进制数组
buf[i++] = dat % 10;
dat /= 10;
} while (dat > 0);
len += i; //i最后的值就是有效字符的个数
while (i-- > 0) //将数组值转换为ASCII码反向拷贝到接收指针上
{
*str++ = buf[i] + '0';
}
*str = '\0'; //添加字符串结束符
return len; //返回字符串长度
}
void ShowOprt(unsigned char y, unsigned char type)
{
switch(type)
{
case 0: LcdShowStr(112, y, "+"); break;
case 1: LcdShowStr(112, y, "-"); break;
case 2: LcdShowStr(112, y, "*"); break;
case 3: LcdShowStr(112, y, "/"); break;
default: break;
}
}
void Reset()
{
num1 = 0;
num2 = 0;
step = 0;
LcdFullClear();
}
/* 数字键动作函数,n-按键输入的数值 */
void NumKeyAction(unsigned char n)
{
unsigned char len;
unsigned char str[12];
if (step > 1) //如计算已完成,则重新开始新的计算
{
Reset();
}
if (step == 0) //输入第一操作数
{
num1 = num1*10 + n; //输入数值累加到原操作数上
len = LongToString(str, num1); //新数值转换为字符串
LcdShowStr(16 - len, 48, str); //显示到液晶第二行上
}
else
{
num2 = num2*10 + n;
len = LongToString(str, num2);
LcdShowStr(16 - len, 48, str);
}
}
void OprtKeyAction(unsigned char type)
{
unsigned char len;
unsigned char str[12];
if(step == 0)
{
len = LongToString(str, num1);
LcdAreaClear(0, 48, 16-len);
LcdShowStr(16-len, 32, str);
ShowOprt(32, type);
LcdAreaClear(1, 48, 14);
LcdShowStr(0, 48, "0");
oprt = type;
step = 1;
}
}
void GetResult()
{
unsigned char len;
unsigned char str[12];
if(step == 1)
{
step = 2;
switch(oprt)
{
case 0: result = num1 + num2; break;
case 1: result = num1 - num2; break;
case 2: result = num1 * num2; break;
case 3: result = num1 / num2; break;
default: break;
}
len = LongToString(str, num2);
ShowOprt(32, oprt);
//LcdAreaClear(1, 32, 16-1-len); //待定--发现LCD12864上删除了没有啥影响
LcdShowStr(16-len, 32, str);
len = LongToString(str, result);
LcdShowStr(112, 48, "=");
//LcdAreaClear(1, 48, 16-1-len); //待定--发现LCD12864上删除了没有啥影响
LcdShowStr(16-len, 48, str);
}
}
/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
if ((keycode>='0') && (keycode<='9')) //输入字符
{
NumKeyAction(keycode - '0');
}
else if(keycode == 0x26)
{
OprtKeyAction(0);
}
else if(keycode == 0x28)
{
OprtKeyAction(1);
}
else if(keycode == 0x25)
{
OprtKeyAction(2);
}
else if(keycode == 0x27)
{
OprtKeyAction(3);
}
else if(keycode == 0x0D)
{
GetResult();
}
else if(keycode == 0x1B)
{
Reset();
LcdShowStr(0, 48, "0"); //初始显示一个数字0
}
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 28; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = T0RH; //加载T0重载值
TL0 = T0RL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
}
/* T0中断服务函数,执行按键扫描 */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
KeyScan(); //按键扫描
}