📄 iopic18.c
字号:
//-----------------------------------------------------------------
// 名称: 输入输出程序
//-----------------------------------------------------------------
#include <pic18.h>
#include <stdlib.h>
#include <stdio.h>
#include "chess.h"
//-----------------------------------------------------------------
// LCD驱动程序
//-----------------------------------------------------------------
void lcd_cls();//清屏
void lcd_blit(WORD addr, const BYTE *img, BYTE num);//棋子绘制函数
void lcd_wrcmd(BYTE cmd); //写无参命令
void lcd_wrcmd1(BYTE cmd, BYTE arg);//写字节参数命令(或称单参数命令)
void lcd_wrcmd2(BYTE cmd, WORD arg);//写字参数命令 (或称双参数命令)
BYTE scankeypad(); //键盘扫描子程序
void sleep(INT msecs); //睡眠子程序
void tone(WORD period, WORD cycles);//声音输出子程序
//LCD命令代码
#define LCD_CURSOR 0x21 //设置光标地址(光标在屏幕上的位置)
#define LCD_OFFSET 0x22 //设置CGRAM偏移地址
#define LCD_ADDRESS 0x24 //设置待显示数据的DDRAM地址
#define LCD_TEXTHOME 0x40 //设置文本区首地址
#define LCD_TEXTAREA 0x41 //设置文本区宽度
#define LCD_GFXHOME 0x42 //设置图形区首地址
#define LCD_GFXAREA 0x43 //设置图形区宽度
#define LCD_ORMODE 0x80 //设置图/文"或"操作模式
#define LCD_XORMODE 0x81 //设置图/文"异或"操作模式
#define LCD_ANDMODE 0x83 //设置图/文"与"操作模式
#define LCD_ATTRMODE 0x84 //设置"属性"模式
#define LCD_DISPLAY 0x90 //设置显示开/关
#define LCD_CLINES 0xA0 //光标线设置(1线,2线,...,8线[块状]光标)
#define LCD_AUTOWRITE 0xB0 //自动写
#define LCD_AUTOREAD 0xB1 //自动读
#define LCD_AUTORESET 0xB2 //自动读/写取消(复位)
#define LCD_WRITEINC 0xC0 //写(地址递增)
#define LCD_READINC 0xC1 //读(地址递增)
#define LCD_WRITEDEC 0xC2 //写(地址递减)
#define LCD_READDEC 0xC3 //读(地址递减)
#define LCD_SCREENPEEK 0xE0 //读屏幕(一字节)
#define LCD_SCREENCOPY 0xE8 //拷贝屏幕(一行)
#define LCD_BITSET 0xF0 //置位操作
//PORTA端口RA0~RA3分别连接WR,RD,CE,C/D
//在PORTA端发送下面字节时分别实现读状态/写命令/写数据/禁止操作
#define LCD_STATUS 0x09 //1001读LCD状态
#define LCD_COMMAND 0x0A //1100写命令
#define LCD_DATA 0x02 //0010写数据
#define LCD_DONE 0x0F //1111禁止操作
//国际象棋棋子位图,存放于pieces.c
extern const BYTE LCD_BITMAPS[];
//控制棋子闪烁的时钟嘀嗒数
#define FLASHTICKS 30
//-----------------------------------------------------------------
// 棋盘初始化函数
//-----------------------------------------------------------------
void panel_init()
{
//初始化I/O端口及其他设置
PORTA = PORTB = PORTC = PORTD = 0xFF;
TRISA = TRISC = TRISE = 0;
RBPU = 0;//PORTB端口内部弱上拉
SPEN = 1; CREN = 1;//串口初始化
SPBRG = 0x19; //19200 Baud @ 8MHz
TXSTA = 0xA4; //CSRC/TXEN (内部时钟,8位模式,异步操作,高速)
printf("\nProteus VSM Tiny Chess\n");
//图像屏幕每行32字节(256像素),驻留于地址0
lcd_wrcmd2(LCD_GFXHOME, 0x0000); //定义图形区DDRAM首地址
lcd_wrcmd2(LCD_GFXAREA, 32); //图形区每行数据占32字节
lcd_wrcmd2(LCD_TEXTHOME, 0x2000); //定义文本区DDRAM首地址
lcd_wrcmd2(LCD_TEXTAREA, 32); //文本区每行占用32字节(8个字符)
lcd_wrcmd2(LCD_OFFSET, 0x3000>>11);//定义CGRAM偏移地址(右移11位取得高5位)
//加载自定义的字符"■"的点阵数据,后面将用每16个这样的字符(4x4=16)构成一个
//棋盘黑色格子.LCD内置字符编码范围为0x00~0x7F,第1个自定义字符的编码为
//0x80(10000000),由于CGRAM地址格式为:5+8+3,0x3000>>11已经设置了该地址的
//高5位为00110,接下来是8位的自定义编码10000000,最后是3位一个字符的8字节点阵
//数据索引000~111(0~7),故编码为0x80的自定义字符点阵数据在CGRAM中的首地址为:
//00110-10000000-000,即:0x3400,下面的语句设置了该地址
lcd_wrcmd2(LCD_ADDRESS, 0x3400);
//从0x3400地址开始写入8字节全1点阵数据.
for (INT i = 0; i < 8 ; i++) lcd_wrcmd1(LCD_WRITEINC, 0xFF);
lcd_wrcmd(LCD_DISPLAY + 0x0C); //0C:使能图形与文本显示,禁止光标显示与闪烁
lcd_wrcmd(LCD_XORMODE); //图文重叠(混合)时以异或模式显示
}
//-----------------------------------------------------------------
// 清除棋盘:先清屏,然后绘制黑/白交错的棋盘格子
//-----------------------------------------------------------------
void panel_cls()
{
COORD r, c; lcd_cls();//首先清空屏幕
//再在256x256的屏幕(32 x 8 = 256)上绘制黑/白交错的棋盘格子
//每个黑色格子由16个编码为0x80的"■"字符拼凑显示而成
for (r = 0; r < 32; r += 1) //全屏可显示32行字符(256/8=32)
{ //每列也可显示32个字符,通过c+=4使之每显示4个字符后
//横向跳过4个字符宽度(4个字符宽度 = 棋盘一列的宽度)
for (c = 0; c < 32; c += 4)
{ //下面开始在8行8列(8x8)的棋盘格子上交错显示黑色格子
//算法说明: 将r/4或c/4可由字符行得到棋盘格行,
//例如r=0~3为棋盘的第0行(r/4=0),当r=4~7时为棋盘的第1行(r/4=1)
//棋盘中奇数行偶数列,或偶数行奇数列为黑色格子,故有:
//if (r / 4 % 2 != c / 4 % 2 )
//由于/与%运算占用机器时间较多,通过>>或&可消除/与%,因而进一步有:
if ((r >> 2 & 0x01) != (c >> 2 & 0x01)) //注意加括号提高&的优等级
//显然,上面的关系式在r与c的二进制位中的第2位不同为0或1为真,
//由于第2位的掩码为0B00000100 = 0x04,
//故上面的判断语句还写成下面的语句,通过异或(^)来编写条件式
//if ((r & 0x04) ^ (c & 0x04))
{ //设置LCD DDRAM字符显示区起始地址:0x2000 + r * 32 + c
//(0x2000为字符区的起始地址)
lcd_wrcmd2(LCD_ADDRESS, 0x2000 + r * 32 + c);
//在一个黑色棋盘格子内横向显示4个"■"(自定义字符编码为0x80),
//占据1/4的显示面积,LCD_WRITEINC设置显示地址自动递增
for (INT i = 0; i < 4; i++) lcd_wrcmd1(LCD_WRITEINC, 0x80);
}
}
}
}
//-----------------------------------------------------------------
// 在棋盘的(r,c)位置绘制棋子(棋子p含有棋子名称与颜色信息)
//-----------------------------------------------------------------
void panel_draw(COORD r, COORD c, PIECE p)
{
const BYTE *sprite; //指向棋子像素字节的指针sprite
//矩阵键盘扫描得到的行号是从上到下编号分别为0~7
//而棋盘格子的行号是由下至上编号分别为0~7,要将键盘扫描得到的
//行号r转换为棋盘行号r,因而有:
r = 7 - r;
//下面根据p计算出spite,使其指向棋子p的全盘棋子点阵数组中的首地址
//奇行偶列或偶行奇列为黑色格子,否则为白色格子
//4位的棋子编码中低3位(X001-X110)标识棋子名称,高位X标识棋子颜色
//取棋子颜色时&0x08,取棋子名称时&0x07
BYTE r1 = r & 0x01, c1 = c & 0x01, p1 = p & 0x08;
//LCD_BITMAPS中的点阵字节为2048,前后各1024字节(1024/32/4=8个棋子图案)
//前半部分是“白格白子”或“黑格黑子”点阵数据,第0,7号棋子为空
//点阵中“白格白子”与“黑格黑子”的点阵数据完全相同
//后半分部是“黑格白子”或“白格黑子”点阵数据(1024/32/4=8个棋子图案)
//点阵中“黑格白子”或“白格黑子”的点阵数据完全相同
if ((r1 != c1 && p1 == WHITE) || (r1 == c1 && p1 == BLACK))
{ //“黑格白子”或“白格黑子”点阵数据在LCD_BITMAPS的后半部分,首地址为0x0400
//由于每个棋子点阵横向占4字节,故有*4,更详细说明参考LCD_BITMAPS的取模说明.
sprite = LCD_BITMAPS + (p & 0x07) * 4 + 0x0400;
}
else
{ //“白格白子”或“黑格黑子”点阵首地址在LCD_BITMAPS前半部分
sprite = LCD_BITMAPS + (p & 0x07) * 4;
}
panel_blit(r, c, sprite); //调用底层函数绘制棋子像素位
}
//-----------------------------------------------------------------
// 将指定棋子图像绘制在棋盘上的底层函数
//-----------------------------------------------------------------
void panel_blit(COORD r, COORD c, const BYTE *sprite)
{
//棋子点阵数组LCD_BITMAPS中每个棋子图像像素为:32x32,占32x32/8=128字节,
//每行8个棋子,故有一整行棋子点阵总字节数为:128x8=1024字节
//棋子点阵为32x32,即每个棋子图像32行,每行32个像素点占32/8=4字节
//下面首先根据棋盘(r,c)得到棋子点阵首地址:
WORD addr = r * 1024 + c * 4;
//从指定地址开始绘制一个棋子的32行像素
for (INT i = 0; i < 32; i++)
{ //每循环一次绘制棋子32行像素中的一行(4字节,32个像素)
lcd_blit(addr, sprite, 4);
//然后将显示DDRAM地址及取像素地址分别递增32字节
//跳到下一行的DDRAM地址和下一行的取点阵地址
addr += 32; sprite += 32;
}
}
//-----------------------------------------------------------------
// 选取待移动的棋子,如果点击的待移入位置是新位置则返回真
//-----------------------------------------------------------------
BOOL panel_getmove(BYTE from[], BYTE to[])
{ BYTE key, fdelay, fstate = 0; PIECE p;
//在用户首次移动后设置随机种子
if (movecount == 1) srand (TMR1);
//扫描键盘(即8x8的触模屏区)得到键值key
key = scankeypad();
//返回有效编码范围为0~63,无键按下时返回FALSE
if (key == 0xFF) return FALSE;
//根据键值计算得出待移动棋子的位置(行/列)
from[0] = key / 8; from[1] = key % 8;
//根据待移动棋子当前位置得到棋子p("7-"的原因见上一函数说明)
p = board[7 - from[0]][from[1]];
//如果棋子为空(点击是无棋子的格子)或棋子为黑子(因为已方执白)
//则直接返回FALSE,否则继续下一步处理
if (p == EMPTY || p & BLACK) return FALSE;
fdelay = 0;
//如果选子按键未释放
while (scankeypad() != 0xFF)
{ //所选中的棋子在原位通过反复反相,形成闪烁显示
if (fdelay-- == 0)
{ panel_invert(from, fstate ^= 1); fdelay = FLASHTICKS;
}
}
//如果选子按键已经释放
while ((key = scankeypad()) == 0xFF)
{ //所选中的棋子仍在原位通过反复反相,继续闪烁显示
if (fdelay-- == 0)
{ panel_invert(from, fstate ^= 1); fdelay = FLASHTICKS;
}
}
//闪烁停止,如果fstate为1时则表示棋子是在奇数次反相后停止的,
//此时棋子已不同于原样了,故要再执行一次反相,还原出其原始特征
if (fstate) panel_invert(from, 0);
//根据键值得到目标位置的键盘行/列号
to[0] = key / 8; to[1] = key % 8;
//将起始键盘行号转换为起始格子行号,将目标键盘行号转换为目标格子行号
from[0] = 7 - from[0]; to[0] = 7 - to[0];
//等待按键释放
while (scankeypad() != 0xFF);
//当待移入的是新位置时返回真
return from[0] != to[0] || from[1] != to[1];
}
//-----------------------------------------------------------------
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -