/*
Projekt: Photovoltaik-Präsentation "solar" mit MSP430
Detail: Steuerung des vollgrafischen Displays (DispXxx),
GDI-ähnliche Grafikfunktionen fürs Display (GrafXxx),
Tasten- und Schalter-Abfrage (Inp_Xxx)
060704 erstellt
0610xx Polygone implementiert
061220 auf DOS zurückgestutzt (für diejenigen, die sich an BGI erinnern:-)
*/
#include "solar.h"
#include <stdio.h> //vsprintf
#include <string.h> //memchr
/* Hardware-Anschluss:
P3.2 = L wenn Nockenschalter RECHTS
P3.3 = L wenn Nockenschalter LINKS
P4.7 = Tastenabfrageleitung (Pullup wurde nachgerüstet 060601)
P4.6 = Command (High) / Data (Low)
P4.5 = /Reset (Low-aktiv) fürs Display
P4.4 = /WR
P4.3 = Fontauswahl 8x8 (Low), 6x8 (High)
P4.2 = /RD
P4.1 = Textmode 40 Spalten (Low), 32 Spalten (High)
P4.0 = CE/ (steigende(!) Flanke)
P5 = bidirektionales Datenport, auch zur Tastenabfrage
*/
// Bitmasken für die (wichtigen) Steuerleitungen:
#define CE 0x01 // -------X
#define RD 0x04 // -----X--
#define WR 0x10 // ---X----
#define CD 0x40 // -X------
#define RST 0x20 // --X-----
// Alle übrigen Bits werden hier per "Ruhe-Byte" erledigt:
#if (FONT_X==6)
# define P4DEF 0xFD // Ruhezustand Z1111101
#elif (FONT_X==8)
# define P4DEF 0xF5 // Ruhezustand Z1110101
#else
# error Falsche vorgewählte Fontbreite (6 oder 8)!
#endif
// Mit den "select"-Byte-basierten Ein/Ausgaberoutinen werden äußerst kurze
// und schnelle Routinen ohne Fallunterscheidung möglich.
// Der Quelltext bleibt dennoch gut lesbar sowie flexibel.
#define WAIT_NORMAL 0x03 // ------XX (X = Bits werden bewertet)
#define WAIT_AUTO_IN 0x04 // -----X--
#define WAIT_AUTO_OUT 0x08 // ----X---
#define DISPLAY_FAILED 0x80 // X------- Code für Display-Versagen
#define WAIT_MAX 1 // max. 1..2 ms warten
// Diese globale Variable ist vertretbar, da die Display-Ansteuerung
// ohnehin nicht reentrant ist. Im wesentlichen eine WAIT-Bitmaske!
BYTE DispFlags;
#define BI_STATUS (P4DEF^CE^RD) // Port P4 für Status-Transfer
#define BI_DATA (P4DEF^CE^RD^CD)// Port P4 für Daten-Transfer
/************************************************************************
* Byte-Eingabe vom Display
* select==BI_STATUS: Status-Lesen (mit C/D = high)
* select==BI_DATA: Daten-Lesen (mit C/D = low)
************************************************************************/
static BYTE ByteIn(BYTE select){
P5DIR=0x00; // alles Eingabe
P4OUT=select;
select^=CE; // steigende Flanke bei CE/ erzeugen
P4OUT=select;
nop(); nop(); // 150 ns Zugriffszeit sicherstellen (1x NOP = 125 ns @ 8 MHz)
return P5IN;
}
// DispFlags gibt die Bit-Positionen an, die 1 sein müssen.
static void WaitReady(void){
unsigned tic;
if (DispFailed()) return;
tic=GetTickCount();
while (~ByteIn(BI_STATUS)&DispFlags) {
if (GetTickCount()-tic>WAIT_MAX) {
DispFlags|=DISPLAY_FAILED;
return;
}
}
}
// Wie ByteIn(), jedoch mit zusätzlichem Warten vor Ausführung des IN-Transfers
static BYTE ByteInW(BYTE select){
WaitReady();
return ByteIn(select);
}
#define BO_COMMAND (P4DEF^CE^WR) // Port P4 für Kommando-Transfer
#define BO_DATA (P4DEF^CE^WR^CD)// Port P4 für Daten-Transfer
/************************************************************************
* Byte-Ausgabe zum Display
* select==BO_COMMAND: Kommando-Ausgabe (mit C/D = high)
* select==BO_DATA: Daten-Ausgabe (mit C/D = low)
************************************************************************/
static void ByteOut(BYTE select, BYTE data){
P4OUT=select;
P5DIR=0xFF; // alles Ausgabe
P5OUT=data;
select^=CE; // steigende Flanke bei CE/ erzeugen
P4OUT=select;
}
static void ByteOutW(BYTE select, BYTE data){
WaitReady();
ByteOut(select,data);
}
/************************************************************************
* Nächstes Level: Präfigierung von 1-2 Daten-Zyklen
************************************************************************/
static void ZeroDataCommand(BYTE cmd){
ByteOutW(BO_COMMAND,cmd);
}
static void OneDataCommand(BYTE data, BYTE cmd){
ByteOutW(BO_DATA,data);
ZeroDataCommand(cmd);
}
static void TwoDataCommand(BYTE data1, BYTE data2, BYTE cmd){
ByteOutW(BO_DATA,data1);
OneDataCommand(data2,cmd);
} //Compiler erweist sich zu doof für "gemeinsame Enden"
// das "W" steht hier für "Word", nicht "Wait"!
static void TwoDataCommandW(WORD data, BYTE cmd){
TwoDataCommand((BYTE)data,(BYTE)(data>>8),cmd);
}
static BYTE CommandRead(BYTE cmd){
ByteOutW(BO_COMMAND,cmd);
return ByteInW(BI_DATA);
}
/************************************************************************
* Blockweise Datenübertragung (auch: Display löschen)
************************************************************************/
// --- Weil einige strukturell verschiedene Routinen zum Daten-Schreiben er-
// --- forderlich sind, zunächst Hilfsroutinen zum Aufbau von Schreibroutinen.
// --- [Alternative ist eine Routine mit Funktionszeiger, aber umständlich]
// Vorbereitung des Daten-Schreibens
#define PreDataWrite() { \
ZeroDataCommand(0xB0); /* "Data Auto Write Set" */ \
DispFlags|=WAIT_AUTO_OUT; \
}
#define PreDataWriteA(addr){ \
TwoDataCommandW(addr,0x24); /* "Address Pointer Set" */ \
PreDataWrite(); \
}
// Der "Mittelteil" des Daten-Schreibens
#define DoDataWrite(b){ \
ByteOutW(BO_DATA,b); /* Byte ausgeben */ \
DispFlags=WAIT_AUTO_OUT; \
}
#define PostDataWrite(){ \
ZeroDataCommand(0xB2); /* "Auto Mode Reset" (mit modifizierter DispFlags) */\
DispFlags=WAIT_NORMAL; \
}
// Schreiben von Datenblöcken (svw. "memcpy")
void DispDataWrite(WORD addr, const BYTE*data, unsigned len){
if (!len) return; // Nichts tun bei Länge Null
PreDataWriteA(addr);
do{
DoDataWrite(*data++); // Byte ausgeben
}while(--len);
PostDataWrite();
}
// svw. "memset" im Display-Speicher
void DispDataFill(WORD addr, BYTE fill, unsigned len){
if (!len) return; // Nichts tun bei Länge Null
PreDataWriteA(addr);
do{
DoDataWrite(fill); // Byte ausgeben
}while(--len);
PostDataWrite();
}
static const char gDisplaySpecial[]={
// ROM-Sonderzeichen (statt '¤' eigentlich 'Pt' - Peseta)
'Ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','Ä','Å',
'É','æ','Æ','ô','ö','ò','û','ù','ÿ','Ö','Ü','¢','£','¥','¤','ƒ',
// Selbst definierte Sonderzeichen (RAM)
'°','g','ß','±','µ',129,
};
// svw. "puts" in Display-Speicher
void DataPuts(WORD addr, const char*str, size_t len){
char c;
if (!str || !len) return; // nichts tun bei NULL oder Länge Null
PreDataWriteA(addr);
do{
c=*str++;
const char *p=memchr(gDisplaySpecial,c,sizeof(gDisplaySpecial));
if (p) c=p-gDisplaySpecial+0x60;
else c-=0x20;
DoDataWrite((BYTE)c); // Byte ausgeben
}while (--len);
PostDataWrite();
}
// Festlegung der Speicheraufteilung "auf dem Rücken des Displays"
// (keine Scroll-Tricks)
//#define GRAFSTART 0
//#define TEXTSTART (GRAFSTART+TEXT_X*GRAF_Y)
//#define CGSTART (((TEXTSTART+TEXT_X*TEXT_Y)+0x7FF)&0xF800)
// untere 11 Bits von CGSTART (Zeichengenerator-Start) müssen 0 sein
#define CGSTART 0 // Platz für (stets!!!) 256 Zeichen: 2KB
#define TEXTSTART CGSTART+256*8 // Platz für Text: 640B (bis 1 KB)
#define GRAFSTART (3*1024) // Platz für Grafik: 5KB
#define PAGEADD (8*1024) // Länge - bis zur nächsten Grafikseite
// Bei Speicherausbau 32K ergeben sich so handhabbare 4 Zeichengenerator-Seiten,
// 4 Textseiten (scrollbar zur Not) und 4 Grafikseiten
static const BYTE gUserChars[]={ // Zeichenbildtabelle fehlende Zeichen
0x0C,0x12,0x12,0x0C,0x00,0x00,0x00,0x00, // °
0x00,0x00,0x0F,0x11,0x11,0x0F,0x01,0x0E, // g hübscher
0x0C,0x12,0x12,0x16,0x11,0x11,0x16,0x00, // ß
0x00,0x04,0x04,0x1F,0x04,0x04,0x1F,0x00, // ±
0x00,0x00,0x12,0x12,0x12,0x12,0x1D,0x10, // µ
0x00,0x0E,0x11,0x11,0x11,0x0A,0x1B,0x00, // Omega, hier \x81
};
// Text-Ausgabe auf Display UND Terminal-Echo
// Zeichenkette muss vollständig auf Zeile passen; wird ggf. abgeschnitten
// Kursor muss (per AnsiSaveCursor()) außerhalb des Rollbereichs sein!
void DispPrintf(int x, int y, const char*template,...){
va_list p;
size_t len;
char buffer[TEXT_X],*s;
va_start(p,template);
len=vsnprintf(buffer,TEXT_X-x,template,p);
va_end(p);
if (!len) return;
DataPuts(TEXTSTART+y*TEXT_X+x,buffer,len);
// Echo auf Terminal
AnsiGotoXY(x+39,y+1);
AnsiSetColor(0x4F); // weiß auf blau (einzige Farbe)
s=buffer;
do{
putchar(*s);
s++;
}while(--len);
}
// Lesen von Datenblöcken (svw. "memcpy" rückwärts)
void DispDataRead(WORD addr, BYTE*data, unsigned len){
if (!len) return; // Nichts tun bei Länge Null
TwoDataCommandW(addr,0x24); // "Address Pointer Set"
ZeroDataCommand(0xB1); // "Data Auto Read Set"
// Beim ersten Datenbyte müssen alle 3 Bits High sein
DispFlags|=WAIT_AUTO_IN;
do{
*data++=ByteInW(BI_DATA); // (Byte einlesen)
DispFlags=WAIT_AUTO_IN; // Bei allen folgenden nur das eine Bit abwarten
}while(--len);
PostDataWrite(); // geht auch für's Lesen
}
// Öfters auftauchende Adresskonvertierung: x->Byte-Spalte, bitno->Bitnummer
#if (FONT_X==6) // 6x8-Font
# define X2ADDR(x,bitno) {bitno=5-(x%6); x/=6;} //Sieht der Compiler "div+mod"?
#else
# define X2ADDR(x,bitno) {bitno=~x&7; x>>=3;}
#endif
void GrafSetPixel(int x, int y, int color){
BYTE bitno;
if ((unsigned)x>=GRAF_X) return; // "unsigned"-Cast deckt x<0 ab!
if ((unsigned)y>=GRAF_Y) return; // Clipping!
X2ADDR(x,bitno);
TwoDataCommandW(GrafDC.ba+y*TEXT_X+x,0x24);
if (color) bitno|=0x08; // setzen, sonst rücksetzen
bitno|=0xF0;
ZeroDataCommand(bitno); // Bit setzen/rücksetzen
}
void GrafInternalSetPixel(int x, int y, int unused){
unsigned t=GrafDC.linestyle;
if (t&1) GrafSetPixel(x,y,GrafDC.color);
t=(t<<1)|(t>>15);
GrafDC.linestyle=t;
}
#define swap(x,y) {typeof(x) t=(x); (x)=(y); (y)=t;}
// Bresenham-Algorithmus...
void GrafLineDDA(int xa,int ya,int xe,int ye,TGrafLineDDAProc proc, int param){
int *lead,*trail; // Zeiger auf Führungs- und Folgegröße
int xs,ys; // ±1 (Schritt)
unsigned ramp,akku;
xe-=xa; xs=1; // xe = vzb. X-Differenz
if (xe<0) {xe=-xe; xs=-1;} // xe = vzl. X-Länge
ye-=ya; ys=1; // ye = vzb. Y-Differenz
if (ye<0) {ye=-ye; ys=-1;} // ye = vzl. Y-Länge
lead=&xa; trail=&ya; // "flache" Linie (X Führungsgröße) annehmen
if (ye<=xe){
if (xe==0) return; // beide Größen Null: nichts zeichnen!
}else{ // "steile" Linie (Y Führungsgröße)
lead=&ya; trail=&xa;
swap(xe,ye); // xe jetzt Länge in Pixel, ye<xe
swap(xs,ys); // xs = Richtung der Führungsgröße
}
ramp=(ye<<8)/xe; // Steilheit mit "8 Nachkommastellen"
akku=ramp>>1; // Akku mit Hälfte vorladen
do{
proc(xa,ya,param);
*lead+=xs;
akku+=ramp;
if (akku>=0x0100){ // besser wäre C-Flag auswerten?
akku-=0x0100;
*trail+=ys;
}
}while(--xe);
}
// Linie mit Linien-Stil
void GrafInternalLine(int xa, int ya, int xe, int ye){
GrafLineDDA(xa,ya,xe,ye,GrafInternalSetPixel,0);
}
TGrafDC GrafDC;
void GrafPolyline(const POINT *pt, int numpt){
const POINT *prev;
if (numpt<2) return;
numpt--;
do{
prev=pt++;
GrafInternalLine(prev->x,prev->y,pt->x,pt->y);
}while(--numpt);
}
static const BYTE Bit2Mask[8]={0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF};
// So wie Windows es tut, die Operationen in den Kode zu stecken
// und "life" auf dem Stack zu compilieren weiß ich (noch) nicht, wie das geht.
static BYTE DoRop3(BYTE D, BYTE P, BYTE S, BYTE rop3){
switch (rop3){ // TODO: müsste für alle 256 Fälle ausgearbeitet werden!
case BLACKNESS: D=0; break; // 00 0 R2_BLACK
case 0x05: D=~(D|P);break; // 05 DPon R2_NOTMERGEPEN
case 0x0A: D&=~P; break; // 0A DPna R2_MASKNOTPEN
case 0x0F: D=~P; break; // 0F Pn R2_NOTCOPYPEN
case NOTSRCERASE: D=~(D|S);break; // 11 DSon
case NOTSRCCOPY: D=~S; break; // 33 Sn
case SRCERASE: D=~D&S; break; // 44 SDna
case 0x50: D=~D&P; break; // 50 PDna R2_MASKPENNOT
case DSTINVERT: D=~D; break; // 55 Dn R2_NOT
case PATINVERT: D^=P; break; // 5A DPx R2_XORPEN
case 0x5F: D=~(D&P);break; // 5F DPan R2_NOTMASKPEN
case SRCINVERT: D^=S; break; // 66 DSx
case SRCAND: D&=S; break; // 88 DSa
case 0xA0: D&=P; break; // A0 DPa R2_MASKPEN
case 0xA5: D^=~P; break; // A5 DPxn R2_NOTXORPEN
case 0xAA: /*D=D*/ break; // AA D R2_NOP
case 0xAF: D|=~P; break; // AF DPno R2_MERGENOTPEN
case MERGEPAINT: D|=~S; break; // BB DSno
case MERGECOPY: D=S&P; break; // C0 SPa
case SRCCOPY: D=S; break; // CC S
case SRCPAINT: D|=S; break; // EE DSo
case PATCOPY: D=P; break; // F0 P R2_COPYPEN
case 0xF5: D=~D|P; break; // F5 PDno R2_MERGEPENNOT
case 0xFA: D|=P; break; // FA DPo R2_MERGEPEN
case PATPAINT: D|=P|~S;break; // FB DPSnoo
case WHITENESS: D=0xFF; break; // FF 1 R2_WHITE
}
return D;
}
/************************************************************************
* Kernfunktion für gefüllte Figuren: Füllen einer Pixelzeile
* xa,xe: Anfang und Ende+1 der horizontalen Linie
* y: Vertikalposition der Zeile
* pattern: Füllmuster
* data: Bilddaten (wenn später ein BitBlt draus wird)
************************************************************************/
void OutScanline(int xa, int xe, int y, BYTE pattern, BYTE*data){
BYTE bita,bite; // Bitnummer Anfangs- und Endpixel
BYTE mask,b; // Bitmaske und "Arbeitsbyte"
if ((unsigned)y>=GRAF_Y) return; // "unsigned"-Cast deckt y<0 ab!
if (xa<0) xa=0;
if (xe>=GRAF_X) xe=GRAF_X;
if (xa>=xe) return;
X2ADDR(xa,bita);
X2ADDR(xe,bite);
TwoDataCommandW(GrafDC.ba+y*TEXT_X+xa,0x24); // "Address Pointer Set"
// Vier Fälle (mindestens?)
if (xa==xe){ // Anfangs- und Endpixel fallen auf die gleiche Byte-Adresse
mask=Bit2Mask[bita]&~Bit2Mask[bite]; // Bitmasken-Kombination
goto partial;
}else{
if (bita!=FONT_X-1){ // "krumme" Anfangsadresse?
mask=Bit2Mask[bita];
b=CommandRead(0xC5); // "Data Read" ohne Adresszeiger-Veränderung
b=(b&~mask)|(DoRop3(b,pattern,0,GrafDC.rop2)&mask);
OneDataCommand(b,0xC0); // "Data Write" mit Auto-Inkrement
xa++;
}
switch (GrafDC.rop2) {
case R2_BLACK:
case R2_NOTCOPYPEN:
case R2_COPYPEN:
case R2_WHITE: { // Lesen des Zielbytes nicht nötig (Zeit sparen)
b=DoRop3(0,pattern,0,GrafDC.rop2);
if (xe>=xa+2){ // "memset" lohnt sich erst ab 2 Bytes
PreDataWrite();
do{
DoDataWrite(b);
xa++;
}while (xa!=xe);
PostDataWrite();
}else while (xa<xe){
OneDataCommand(b,0xC0); // "Data Write" mit Auto-Inkrement
xa++;
}
}break;
default: while (xa<xe) { // alle Modi mit Lesen des Zielbytes
b=CommandRead(0xC5); // "Data Read" ohne Adresszeiger-Veränderung
b=DoRop3(b,pattern,0,GrafDC.rop2);
OneDataCommand(b,0xC0); // "Data Write" mit Auto-Inkrement
xa++;
}
}
if (bite!=FONT_X-1){ // "krumme" Endadresse?
mask=~Bit2Mask[bite]; // Bitmasken-Kombination
partial:
b=CommandRead(0xC5); // "Data Read" ohne Adresszeiger-Veränderung
b=(b&~mask)|(DoRop3(b,pattern,0,GrafDC.rop2)&mask);
OneDataCommand(b,0xC0); // "Data Write" mit Auto-Inkrement
}
}
}
/************************************************************************
* Der folgende Kode für Polygone kann vorläufig nur ALTERNATE
* und kein vernünftiges Clipping
************************************************************************/
#define MAXLISTBYTES 600
// bestimmt maximale Polygonkomplexität sowie Stack-Speicherverbrauch
typedef struct {
WORD len;
BYTE list[MAXLISTBYTES];
}TList;
//=== Platz in Punktliste machen oder entfernen ===
static bool ListMachePlatz(TList*L, BYTE*p, int Platz) {
unsigned idx=p-L->list;
unsigned newlen=L->len+Platz;
unsigned tail=L->len-idx;
if (newlen>MAXLISTBYTES) return false;
if (Platz>0) memmove(p+Platz,p,tail);
else memmove(p,p-Platz,tail+Platz);
L->len=newlen;
return true;
}
//=== Punkt in Punktliste hinzufügen oder (wegen ALTERNATE) entfernen ===
static bool ListInsert(TList*L, int x, int y) {
BYTE *pb=L->list;
BYTE *pe=pb+L->len;
BYTE *pxl, xl; // X-Länge
// Koordinaten einschränken
if ((unsigned)y>=GRAF_Y) return true;
if (x<0) x=0; if (x>GRAF_X) x=GRAF_X;
// y-Koordinate suchen
for (; pb<pe; pb+=2+pb[1]) {
if (*pb==(BYTE)y) {
// Gleiche Y-Koordinate?
pxl=++pb;
pb++;
xl=*pxl; // muss >=1 sein!
do{
if (*pb==(BYTE)x) {
// Wenn Punkt bereits vorhanden, Punkt löschen
if (*pxl>1) {
ListMachePlatz(L,pb,-1); // nur X weg
(*pxl)--; // 1 weniger
}
else ListMachePlatz(L,pb-2,-3); // X und Y weg
return true;
}
if (*pb>(BYTE)x) break;
pb++;
}while (--xl);
// Punkt nicht vorhanden, hinzufügen
if (!ListMachePlatz(L,pb,1)) return false;
*pb=(BYTE)x;
(*pxl)++;
return true;
}
if (*pb>(BYTE)y) break;
}
// neue y-Koordinate erzeugen (pb zeigt auf Folge-y-Koordinate)
if (!ListMachePlatz(L,pb,3)) return false;
pb[0]=(BYTE)y; // neues Y
pb[1]=1; // nur 1 X in dieser Zeile
pb[2]=(BYTE)x; // neues X
return true;
}
//=== LineDDA für Polygone mit ALTERNATE-Besonderheiten: ===
// * Abarbeitung stets von oben nach unten
// * genau einem Punkt pro Y-Koordinate
static bool ListLineDda(TList*L, int xa, int ya, int xe, int ye) {
long xs,xp/*blindes gcc:*/=0;
ye-=ya; if (ye==0) return true; // bei waagerechten Linien nichts tun
xe-=xa;
// nach Y sortieren, ye>=ya (immer nach unten zeichnen)
if (ye<0) {ya+=ye; xa+=xe; xe=-xe; ye=-ye;}
xs=((long)xe<<16)/ye; // Steigung als Hex-Festkommazahl (auch negativ)
xp=(long)xa<<16; // Start als Hex-Festkommazahl
// hier ggf. runden! xp|=0x8000;
do{
if (!ListInsert(L,xp>>16,ya)) return false; // Polygon zu komplex
xp+=xs; // Festkomma-Steigung auf Festkomma-Position
ya++;
}while(--ye);
return true;
}
//=== ein Polygon in Punktliste einfügen ===
static bool ListPolyDda(TList*L, const POINT*pt, int numpt) {
const POINT *p0=pt,*pn;
if (numpt<0) return false; // darf nicht sein
if (numpt<=2) return true; // so tun, als wär's auch OK, oder?
do{
pn=p0;
if (numpt!=1) pn=pt+1; // nächster Punkt
if (!ListLineDda(L,pt->x,pt->y,pn->x,pn->y)) return false;
pt=pn;
}while(--numpt);
return true;
}
//=== mehrere Polygone in Punktliste einfügen ===
static bool ListPolyPolyDda(TList*L, const POINT*pt, const int*numpt, int numpoly) {
if (numpoly<=0) return false;
do{
if (!ListPolyDda(L,pt,*numpt)) return false;
pt+=*numpt++;
}while(--numpoly);
return true;
}
//=== Scanzeilen-Liste ausgeben ===
static void ListScanline(const TList*L) {
const BYTE *py=L->list;
const BYTE *pe=py+L->len;
for (;py<pe; py+=2+py[1]) {
int y=py[0];
int xn=py[1]; // muss gerade sein!!
const BYTE *px=py+2;
if (xn&1) return; // Notbremse
do{
OutScanline(px[0],px[1],y,GrafDC.pattern[y&7],NULL);
px+=2;
}while(xn-=2);
}
}
//=== Begrenzung für mehrere Polygone zeichnen ===
// (ist für Stiftbreiten>1 NICHT dasselbe wie mehrere Aufrufe von OutlinePoly)
void drawpolypoly(int numpoly, const int*numpt, const POINT*pt) {
if (numpoly<=0) return;
do{
drawpoly(*numpt,pt);
pt+=*numpt++;
}while(--numpoly);
}
void fillpolypoly(int numpoly, const int*numpt, const POINT*pt) {
TList L; // auf dem Stack, nicht statisch (besser Heap?)
L.len=0;
if (!ListPolyPolyDda(&L,pt,numpt,numpoly)) return;
ListScanline(&L/*,brush*/);
}
/************************************************************************
* PolyPolygon zeichnen
* (ist nicht dasselbe wie mehrere Aufrufe von Polygon()!)
************************************************************************/
bool GrafPolyPolygon(const POINT*pt, const int*numpt, int numpoly) {
if (GrafDC.brush) fillpolypoly(numpoly,numpt,pt);
if (GrafDC.pen) drawpolypoly(numpoly,numpt,pt);
return true;
}
/************************************************************************
* Polygon zeichnen
************************************************************************/
bool GrafPolygon(const POINT*pt, int numpt) {
return GrafPolyPolygon(pt,&numpt,1);
}
/************************************************************************
* Rechteck wie Windows mit Füllung und Rand
************************************************************************/
void GrafRectangle(int left, int top, int right, int bottom){
if (GrafDC.brush) bar(left,top,right,bottom);
if (GrafDC.pen) rectangle(left,top,right,bottom);
}
/************************************************************************
* Füllmuster (8 Byte) setzen, NULL für keine Füllung
************************************************************************/
void GrafSetBrush(const BYTE pattern[8]) {
if (pattern) {
memcpy(GrafDC.pattern,pattern,8);
GrafDC.brush=1;
}else GrafDC.brush=0; // HOLLOW_BRUSH
}
/************************************************************************
* Füllmuster (8 Byte) generieren mit Helligkeitseffekt
************************************************************************/
void GrafCreateSolidBrush(BYTE pattern[8], COLORREF color) {
int i,j;
unsigned a=0; // Akku
BYTE b/*blindes gcc:*/=0;
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
b<<=1;
a+=color;
if (a>=255) { // bei Überlauf...
b++; // Bit 0 setzen
a-=255; // Überlauf weg
}
}
if (i&1) b=(b<<1)|(b>>7); // rotieren für 45°-Effekt
*pattern++=b;
}
}
void setvisualpage(int page) {
TwoDataCommandW(GRAFSTART+page*PAGEADD,0x42); // Graphic Home
}
// GrafDC initialisieren
void initgraph(void) {
setvisualpage(0);
TwoDataCommandW(TEXT_X,0x43); // Graphic Area - svw. Bytes pro Zeile
setactivepage(0);
setviewport(0,0,getmaxx(),getmaxy(),false);
cleardevice();
setcolor(1); // weiße Linie
GrafDC.pen=1;
GrafDC.brush=1;
GrafDC.linestyle=(unsigned)-1; // durchgezogene Linie
GrafDC.rop2=R2_COPYPEN;
}
/************************************************************************
* Flüssigkristall-Anzeige 240x128 Pixel initialisieren:
* Ports auf Ausgabe schalten, 6x8-Font (40x16 Zeichen) benutzen
************************************************************************/
void DispInit(void){
int i;
static const POINT TestPoly[]={ // zum Test des Polygons: Fünfeck
{0,80},
{60,80},
{10,120},
{30,60},
{50,120}};
static const POINT TestPolyPoly[]={ // zum Test PolyPolygon: 2 Dreiecke
{90,75},
{120,120},
{60,120},
{60,90},
{120,90},
{90,135}};
static const int TPP33[]={3,3}; // ineinander verschachtelt zum Judenstern
DispFlags=WAIT_NORMAL;
P4OUT=P4DEF^RST; // Reset aktiv
P4DIR=0x7F; // alles Ausgang, außer Tastenabfrageleitung
for (i=100;i;i--) nop();
P4OUT=P4DEF; // Reset-Zustand entfernen
ZeroDataCommand(0x81); // "XOR"+"CGROM" Mode
if (DispFailed()) return;
TwoDataCommandW(CGSTART>>11,0x22); // Anfangsadresse der Zeichenbildtabelle
TwoDataCommandW(TEXTSTART,0x40); // Text Home: gleich hinter der Grafik
TwoDataCommandW(TEXT_X,0x41); // Text Area - svw. Zeichen pro Zeile
initgraph();
// for (i=0; i<8; i++) GrafDC.pattern[i] = i&1 ? 0x55 : 0xAA;
GrafCreateSolidBrush(GrafDC.pattern,128);
// Die ersten 400h liegen brach (wo ist das dokumentiert?)
DispDataWrite(CGSTART+0x400,gUserChars,sizeof(gUserChars));
ZeroDataCommand(0x9F); // "Text"+"Graphic" ON
// Ab hier nur Testkode!
DataPuts(TEXTSTART,"Hallo!",6); // müsste links oben erscheinen
DataPuts(TEXTSTART+15,"ÄÖÜäöüß±gµ\x81",11);
TwoDataCommand(0x02,0x03,0x21);
{
char a[32];
for (i=0; i<32; i++) a[i]=0x60+i;
DispDataWrite(TEXTSTART+40,a,32);
}
GrafSetPixel(100,100,1);
moveto(30,40); lineto(50,70); lineto(66,-4);
GrafRectangle(80,20,120,80);
GrafPolygon(TestPoly,elemof(TestPoly));
GrafPolyPolygon(TestPolyPoly,TPP33,elemof(TPP33));
bar(200,100,220,120); // Na??
bar(90,70,220,78);
GrafDC.rop2=R2_XORPEN;
bar(90,65,220,69);
}
/************************************************************************
* Tastenstatus der 8 Tasten erfragen
* Jede gedrückte Taste entspricht einem gesetzten Bit im Ergebnisbyte
************************************************************************/
BYTE Inp_Tasten(void){
BYTE mask,ret=0;
// P4OUT=P4DEF; // ggf. CE\ entfernen
P5DIR=0xFF; // alles Ausgänge
for (mask=0x01; mask; mask<<=1){
// In der Schaltung wurde ein Pullup an P4.7 vergessen
// (beim ATmega sind diese im Mikrocontroller:-)
// Statt Schaltungsänderung wird hier das Pin "dynamisch" aufgeladen...
P5OUT=0xFF; // keinen Taster abfragen (kein Kurzschluss erzeugen)
P4DIR=0xFF; // Pin P4.7 aufladen (P4OUT.7 = 1)
P4DIR=0x7F; // Pin P4.7 wieder hochohmig
P5OUT=~mask; // Alles high, bis auf die zu prüfende Taste
nop(); // Zeit lassen
if (!(P4IN&0x80)) ret|=mask; // eine gedrückte Taste zieht P4.7 nach Low
}
return ret;
}
/************************************************************************
* Zustand des Nockenschalters erfragen
************************************************************************/
BYTE Inp_Schalter(void) {
BYTE p3=~P3IN;
if (p3&0x08) return 0; // Schaltplan: J1 Kontakt links
if (p3&0x04) return 2; // Schaltplan: J1 Kontakt rechts
return 1;
}
Vorgefundene Kodierung: ANSI (CP1252) | 4
|
|