?? 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 + -