/* Projekt Convac-Ätzer
Zugriff auf das Display mit dem Controller HD61830
240 × 64 Pixel = (30×8) × 64 Pixel = 40 × 8 Zeichen 5×7 = 40 × 5 Zeichen 5×11
http://www.delta-components.com/products/infos/lcd-graphic/HD61830/5.htm
http://www.delta-components.com/products/infos/lcd-graphic/HD61830/8.htm
Henrik Haftmann, 161012
Beim Controller ist angeschlossen, siehe
http://www.tu-chemnitz.de/~heha/enas/Convac-Ätzer/Eagle.zip/Terminal.wmf?as=SVG
A0 C/!D Kommando/Daten
A1 R/!W Lesen/Schreiben
A2 E Freigabe
!CSD !CS Chipauswahl = !SEL0
Quarzoszillator nur 1,84 MHz!
*/
#include "Convac.h" // BUS-Klasse, beep()
// Befehl zum HD61830 (inklusive 1-Byte-Datenphase) absetzen.
// Mit gesetztem Bit 7 von <c> kein Busy abfragen, kein Kommando ausgeben.
// Bei Kommando "Byte lesen" (0x0D oder 0x8D) wird <d> ignoriert und ein Byte gelesen
// Ansonsten wird <d> ausgegeben, und die Funktion liefert kurzerhand <d>.
byte DISP::instr(byte c, byte d) {
if (!(c&0x80)) {
byte cnt=0;
do{
if (!(BUS::ioRead(BUS::BA_DISPLAY+3)&0x80)) break; // Busy-Flag abfragen
}while(--cnt); // nicht alles blockieren, wenn Display tot
BUS::ioWrite(BUS::BA_DISPLAY+1,c); // Kommando
}
switch (c) {
case 0x0D: BUS::ioRead(BUS::BA_DISPLAY+2); // nobreak; weil zweimal (Dummy-Read)
case 0x8D: return BUS::ioRead(BUS::BA_DISPLAY+2);
}
BUS::ioWrite(BUS::BA_DISPLAY+0,d); // Datenbyte
return d;
}
void DISP::instr16(byte c, word d) {
instr(c,d);
instr(c+1,d>>8);
}
void DISP::clear() {
instr16(0x08,0); // Display Start Address Registers
instr16(0x0A,0); // Cursor Address Registers = Datenschreibadresse
byte c=0x0C;
word bytes=BYTES_X*PIXEL_Y;
do{
instr(c,0); // Das erste Byte mit Kommando
c|=0x80; // alle folgenden ohne Kommando
}while(--bytes);
clrClip();
gotoXY(0,0);
}
void DISP::init() {
flags=COLOR|R2_COPYPEN;
instr(0x00,0x32); // Grafikmodus
instr(0x01,0x07); // byteweise
instr(0x02,BYTES_X-1); // 30 Bytes
instr(0x03,0x3F); // Multiplex Ratio Register
}
// Clipping (Abschneiderechteck)
void DISP::saveClrClip() {
clipbuf[0]=clip.L;
clipbuf[1]=clip.T;
clipbuf[2]=clip.R;
clipbuf[3]=clip.B;
clrClip();
}
void DISP::restoreClip() {
clip.L=clipbuf[0];
clip.T=clipbuf[1];
clip.R=clipbuf[2];
clip.B=clipbuf[3];
}
void DISP::andClipX(byte l, byte r) {
if (l<=r && clip.L<l) clip.L=l; // nach rechts
if (clip.R>r) clip.R=r; // nach links
}
bool DISP::clipX() {
if (L>R && L>=0x80 // Blt-Rechteck links außerhalb beginnend?
|| L<clip.L) J+=clip.L-L,L=clip.L; // linke Seite einkürzen
if (R>clip.R) R=clip.R; // rechte Seite einkürzen
return R>=L; // return true when not empty
}
bool DISP::clipOk() const{ // Punkt (X,Y) im Abschneiderechteck?
return clip.L<=X && X<=clip.R && clip.T<=Y && Y<=clip.B;
}
// Positionsberechnung
// remove a limiting bitblt window; CS must be 0
void DISP::unsetWindow() {
L=clip.L; T=clip.T; R=clip.R; B=clip.B;
}
/*********************
* Punkte und Linien *
*********************/
void DISP::lineTo(byte x, byte y) {
if (y==Y) { // horizontale Linie
if (x>X) L=X, R=x;
else L=x, R=X;
patLine(y*BYTES_X,flags&COLOR?0xFF:0);
}else{ // vertikale oder schräge Linie
char xs=x<X?-1:1;
char ys=y<Y?-1:1;
byte w=x<X?X-x:x-X; // X-Ausdehnung-1
byte h=y<Y?Y-y:y-Y; // Y-Ausdehnung-1
int err=w-h;
for(;;){
setPixel();
if (x==X && y==Y) break;
int e2=err<<1;
if (e2+h>0) err-=h, X+=xs;
if (e2<w) err+=w, Y+=ys;
}
}
gotoXY(x,y);
}
/*********************
* Bit-Blocktransfer *
*********************/
void DISP::addrXY(word xy) {
instr16(0x0A,xy);
}
// p = Pattern
// m = Maske (1 = Bits aus Pattern, 0 = Bits aus Destination unverändert lassen
void DISP::bltByte(word xy, byte rop, byte p, byte m){
byte d=0;
byte n=~m;
rop&=0x0F;
if (n || rop!=0b1100) {
d=instr(0x0D,0); // vorhandenes Byte lesen
addrXY(xy); // Adresse zurückstellen
}
n&=d;
switch (rop) {
// P = 1100
// D = 1010
case 0b0000: break; // R2_BLACK (R2_ZERO) 0
case 0b0001: n|=~(d|p)&m; break; // R2_NOTMERGEPEN DPon
case 0b0010: n|=d&~p&m; break; // R2_MASKNOTPEN DPna
case 0b0011: n|=~p&m; break; // R2_NOTCOPYPEN Pn
case 0b0100: n|= ~d& p &m; break; // R2_MASKPENNOT PDna
case 0b0101: n=d^m; break; // R2_NOT Dn
case 0b0110: n|=(d^p)&m; break; // R2_XORPEN DPx
case 0b0111: n|=~(d& p)&m; break; // R2_NOTMASKPEN DPan
case 0b1000: n|= d& p &m; break; // R2_MASKPEN DPa
case 0b1001: n|=~(d^ p)&m; break; // R2_NOTXORPEN DPxn
case 0b1010: n=d; break; // R2_NOP D
case 0b1011: n|= (d|~p)&m; break; // R2_MERGENOTPEN DPno
case 0b1100: n|=p&m; break; // R2_COPYPEN P
case 0b1101: n|=(~d| p)&m; break; // R2_MERGEPENNOT PDno
case 0b1110: n|= (d| p)&m; break; // R2_MERGEPEN DPo
case 0b1111: n|=m; break; // R2_WHITE (R2_ONES) 1
}
instr(0x0C,n);
}
void DISP::bltPixel(bool p) {
byte xl=X>>3;
byte xn=X&7;
word xy=Y*BYTES_X+xl;
addrXY(xy); // Adresse setzen
switch (flags&0x0F) {
case 0b0000: instr(0x0F,xn); break; // immer löschen
case 0b0011: instr(p?0x0E:0x0F,xn); break;
case 0b1010: break;
case 0b1100: instr(p?0x0F:0x0E,xn); break;
case 0b1111: instr(0x0E,xn); break; // immer setzen
default: xn=1<<xn; bltByte(xy,flags,p?xn:0,xn);
}
}
void DISP::setPixel() {
if (clipOk()) bltPixel(flags&COLOR);
}
// Füllt Zeile Y von L bis R (inklusive) mit Pattern p, für Polygone
// Das Pattern ist fest an das Byteraster gebunden, wird nicht geschoben
void DISP::patLine(word yy, byte p) {
byte x=L>>3; // linkes Byte
byte m=(1<<(L&7))-1; // linke Ausschlussmaske (0 = ganzes Byte)
byte xr=R>>3; // rechtes Byte
byte mr=0xFE<<(R&7); // rechte Ausschlussmaske (0 = ganzes Byte)
yy+=x; // in Startadresse umwandeln
xr-=x; // in Bytezähler umwandeln; 0 = 1 Byte, 1 = 2 Byte usw.
addrXY(yy); // adressieren
for(;;) {
if (!xr) m|=mr; // Endausschluss anwenden
bltByte(yy,flags,p,~m); // ausgeben mit Maske
if (!xr) break; // fertig
yy++;
xr--; // nächstes Byte
m=0; // kein Ausschluss
}
}
void DISP::fillRect() {
for (byte y=T;;y++) {
patLine(y*BYTES_X,flags&COLOR?0xFF:0);
if (y==B) break;
}
}
void DISP::patRect() {
for (byte y=T;;y++) {
patLine(y*BYTES_X,brush[y&7]);
if (y==B) break;
}
}
void DISP::drawRect(byte l, byte t, byte r, byte b) {
byte w=width;
if (!w) w=1;
if (byte(w<<1)<=byte(r-l) && byte(w<<1)<=byte(b-t)) {
fillRect(l ,t ,r-w ,t+w-1); // oben
fillRect(l ,t+w ,l+w-1,b ); // links
fillRect(r-w+1,t ,r ,b-w ); // rechts
fillRect(l+w ,b-w+1,r ,b ); // unten
}else fillRect(l,t,r,b); // ohne Hohlraum
}
// Füllt Zeile Y von L bis R inklusive mit Daten vom Flash, für Text und Bitmaps
// Ohne Clipping! Das muss der Aufrufer sicherstellen.
void DISP::bltLine(word yy, const byte*data, int startbit) {
startbit-=L&7; // ein paar Bits nach links ansetzen (ggf. negativ)
data+=startbit>>3; // Quelladresse (für Bit 0 im ersten Byte)
startbit&=7; // erstes Bit in Quelle (für Bit 0 im ersten Byte)
byte x=L>>3; // linke Byteadresse
byte m=(1<<(L&7))-1; // linke Ausschlussmaske, niemals 0xFF, 0 = ganzes Byte
byte xr=R>>3; // rechte Byteadresse
byte mr=0xFE<<(R&7); // rechte Ausschlussmaske, niemals 0xFF, 0 = ganzes Byte
yy+=x; // in Startadresse umwandeln
xr-=x; // in Bytezähler umwandeln; 0 = 1 Byte, 1 = 2 Byte usw.
addrXY(yy); // Erstes Byte (in Zeile y) adressieren
for(;;){
// Die Berechnung der Datenadresse kann über den gegebenen Speicherbereich
// überlaufen, nach unten und nach oben.
// Bei der Umsetzung des Algorithmus' auf Architekturen mit Speicherschutz
// muss der Zugriff auf irrelevante Daten unterdrückt werden.
byte b=word(data)&0x8000?pgm_read_byte(data):*data;
data++;
if (startbit) {
b>>=startbit;
b|=(word(data)&0x8000?pgm_read_byte(data):*data)<<(8-startbit);
}
if (!xr) m|=mr; // Endausschluss anwenden
bltByte(yy,flags,b,~m);
if (!xr) break; // fertig
yy++;
xr--; // nächstes Byte
m=0; // kein Ausschluss
}
}
// Vom Flash-Speicher, hier LSBfirst!!
// Daten max. 4 KByte, startbit = beliebig, positiv
void DISP::bitblt(const byte*data, int bitsperline, int startbit) {
startbit+=K*bitsperline+J; // Clipping korrigieren
for (byte y=T;;y++) {
bltLine(y*BYTES_X,data,startbit);
if (y==B) break; // auch für den Fall B==255
startbit+=bitsperline;
}
}
void DISP::readLine(word yy, byte*data) {
byte l=L>>3; // linke Byteadresse
byte r=R>>3; // rechte Byteadresse
yy+=l;
r-=l;
addrXY(yy);
byte c=0x0D; // mit Kommandoausgabe
for(;;) {
*data++=instr(c,0); // Byte lesen
if (!r) break; // fertig
--r; // nächstes Byte
c=0x8D; // weiterlesen ohne Kommandoausgabe
}
}
void DISP::copyLine(byte ys, byte yd, int dx) {
// FIXME: Sollte für horizontalen Scroll nicht alle Bytes lesen & schreiben
readLine(ys*BYTES_X,buf);
bltLine(yd*BYTES_X,buf,dx);
}
void DISP::scroll(int dx, int dy) {
flags=flags&0xF0|R2_COPYPEN; // keine besonderen Rasteroperationen
#ifdef CHECK_DIST
byte dxa=dx<0?-dx:dx; // Absolutwerte müssen in Bytes passen!
if (L+dxa>R) return;
byte dya=dy<0?-dy:dy;
if (T+dya>B) return;
#endif
if (dy>=0) { // nach oben: von oben nach unten
for (byte y=T;;y++) {
copyLine(y+dy,y,dx);
if (y+dy==B) break;
}
}else{ // nach unten: von unten nach oben
for (byte y=B;;y--) {
copyLine(y+dy,y,dx);
if (y+dy==T) break;
}
}
}
// liefert Ende des FlashStrings, hinter der terminierenden Null, für Ketten
const char*strend1(const char*p,byte i) {
asm volatile(
" mov r1,%2 \n"
" tst r1 \n"
" breq 2f \n" // nichts tun bei i=0
"1: rcall take_byte \n"
" tst r22 \n"
" brne 1b \n"
" dec r1 \n"
" brne 1b \n"
"2: \n"
:"=z"(p):"0"(p),"r"(i));
return p;
}
/********************************************************
* Vollgrafische Zeichenausgabe mit Proportionalschrift *
********************************************************/
// Holt ein Byte vom RAM (Bit 7 gelöscht) oder vom Flash (Bit 7 gesetzt)
// also ATtiny10 oder PIC16F145x emulierend, inkrementiert den Zeiger Z
// Nur von Assembler aufrufbar!
// PE: Z = Byte-Zeiger
// PA: Z = Byte-Zeiger 1 Byte vorgerückt
// R22 = Byte
// VR: -
extern "C" byte take_byte(/*const char*&Z*/) __attribute__((naked));
byte take_byte() { asm(
" sbrs r31,7 \n"
" ld r22,Z+ \n"
" sbrc r31,7 \n"
" lpm r22,Z+ \n"
" ret \n"
);}
// UTF-8 string processing helpers
// No error checking!
// 2-byte UTF-8 (110x xxxx 10xx xxxx)
// 3-byte UTF-8 (1110 xxxx 10xx xxxx 10xx xxxx)
// PE: Z = String-Zeiger
// PA: Z = String-Zeiger 1..3 Bytes vorgerückt
// W22 = 16-bit-Zeichen
// ZF = gesetzt wenn Null
// VR: -
extern "C" wchar_t take_wchar(/*const char*&Z*/) __attribute__((naked));
wchar_t take_wchar() {
asm(
" push r20 \n"
" rcall take_byte \n"
" clr r23 \n"
" cpi r22,0xC2 \n" // ASCII oder ungültiges Lead-Byte?
" brcs 1f \n" // Als ASCII oder CP1252 annehmen
" cpi r22,0xF0 \n" // Hier niemals 4-Byte-UTF-8
" brcc 1f \n"
" mov r23,r22 \n"
" cpi r22,0xE0 \n" // 11?x xxxx
" brcs 2f \n"
" swap r23 \n" // xxxx 1110
" andi r23,0xF0 \n" // xxxx 0000
" rcall take_byte \n" // 10xx xxxx
" lsl r22 \n"
" lsl r22 \n" // xxxx xx00
" swap r22 \n" // xx00 xxxx
" mov r20,r22 \n"
" andi r22,0x0F \n" // 0000 xxxx
" or r23,r22 \n" // xxxx xxxx
" rjmp 3f \n"
"2: lsl r23 \n"
" lsl r23 \n" // 0xxx xx00
" swap r23 \n" // xx00 0xxx
" mov r20,r23 \n"
" andi r23,0x07 \n" // 3 Bit
"3: andi r20,0xC0 \n" // 2 Bit
" rcall take_byte \n" // 10xx xxxx
" andi r22,0x3F \n" // 00xx xxxx
" or r22,r20 \n" // xxxx xxxx
"1: mov r20,r22 \n"
" or r20,r23 \n"
" pop r20 \n"
" ret \n");
/*
ret=*s++;
if (ret&0x80) {
if (ret&0x20) {
ret=ret<<12|(*s++&0x3F)<<6;
}else ret=ret<<6&0x07C0;
ret|=*s++&0x3F;
}*/
}
const wchar_t*wcschr_P(const wchar_t*p,wchar_t c) {
for(;;p++) {
wchar_t v=pgm_read_word(p);
if (v==c) return p;
if (!v) return 0;
}
}
static wchar_t uni2cp1252(wchar_t c) {
// Generiert die Kodepositionen 0x80..0x9F
static const PROGMEM wchar_t codes[]=
L"€Ω‚ƒ„…†‡ˆ‰Š‹Œ≤Žπ" // 0x.. 81 8D 8F 90 9D
L"Δ‘’“”•–—∞™š›œ≥žŸ"; // Neu gegenüber CP1252 sind Ω ≤ π Δ ≥
if (c>128) {
const wchar_t*p=wcschr_P(codes,c);
if (p) c=0x80+p-codes;
}
return c;
}
void DISP::newline() {
X=clip.L;
Y+=cfont.cy; // TODO: Scrollen
}
void DISP::print(wchar_t c) {
if (flags&COOKED) {
switch (c) {
case '\n': newline(); return;
case '\a': beep(); return;
}
}
c=uni2cp1252(c);
if (c<cfont.first || c>=cfont.first+cfont.count) c='?';
c-=cfont.first;
int bitsperline=0;
int startbit=0;
char cx=cfont.cx;
char cy=cfont.cy;
const byte*addr=cfont.bits;
if (cx<=0) { // Proportionalschrift
char w=0;
for (byte i=0;;) {
byte b=pgm_read_byte(addr++);
for (byte m=0; m<2; m++) { // Zwei Nibbles
byte n=(b&15)-cx; // Zeichenbreite
if (i==(byte)c) {
startbit=bitsperline; // Wert retten
w=n;
}
bitsperline+=n; // Breiten summieren
b>>=4;
if (++i==cfont.count) goto exi;
}
}
exi: cx=w;
}else{ // Diktengleiche Schrift
bitsperline=cfont.cx+7&~7;
addr+=(byte)c*((byte)bitsperline>>3)*cy;
}
if (flags&COOKED && X+cx-1>clip.R) newline();
targetXY(X+cx-1,Y+cy-1);
if (clipRect()) { // Zeichenzelle berechnen
bitblt(addr,bitsperline,startbit);
}
X+=cx; // Position nächstes Zeichen
if (X>=PIXEL_X) X=PIXEL_X; // Überlange Strings nicht links hineinkommen lassen
// (Dieser Kode würde für 256 Pixel breite Displays nicht funktionieren,
// u.a. weil die Clipping-Funktion für Clip-Rechtecke „von links“ funktionieren muss.
// Allgemein gibt es hier Probleme wenn Dislaybreite + max. Zeichenbreite > 255 ist.)
}
void DISP::print(const char *s) {
asm(
" movw r30,r22 \n"
" rjmp 1f \n"
"2: push r30 \n" // Z retten
" push r31 \n"
" push r24 \n"
" push r25 \n"
" rcall _ZN4DISP5printEw \n" // mit W24 (this) und W22 (wchar_t) aufrufen
" pop r25 \n"
" pop r24 \n"
" pop r31 \n"
" pop r30 \n"
"1: rcall take_wchar \n" // W24 (this) wird nicht verändert
" brne 2b \n");
}
int DISP::getTextExtent(wchar_t c) {
int ext=cfont.cx;
if (ext>0) return ext; // Konstante für diktengleiche Schrift
c=uni2cp1252(c);
if (c<cfont.first || c>=cfont.first+cfont.count) c='?';
c-=cfont.first;
byte b=pgm_read_byte(cfont.bits+(c>>1));
if (c&1) b>>=4; // use high nibble
return (b&15)-ext; // "add" the minimum character width
}
int DISP::getTextExtent(const char *s) {
int ext=0;
asm(
" rjmp 1f \n"
"2: push r30 \n" // Z retten
" push r31 \n"
" push r24 \n" // this retten
" push r25 \n"
" push %A0 \n" // ext retten
" push %B0 \n"
" rcall _ZN4DISP13getTextExtentEw\n" // mit W24 (this) und W22 (wchar_t) aufrufen
" pop %B0 \n"
" pop %A0 \n"
" add %A0,r24 \n"
" adc %B0,r25 \n"
" pop r25 \n"
" pop r24 \n"
" pop r31 \n"
" pop r30 \n"
"1: rcall take_wchar \n" // W24 (this) wird nicht verändert
" brne 2b \n"
:"=r"(ext):"0"(ext),"z"(s),"r"(this));
return ext;
}
void DISP::setFont(const byte*font) {
#if 1
asm(
"1: lpm r0,Z+ \n"
" st X+,r0 \n"
" dec %2 \n"
" brne 1b \n"
" st X+,r30 \n"
" st X+,r31 \n"
::"z"(font),"x"(&cfont),"r"(4));
#else
cfont.cx=pgm_read_byte(font++);
cfont.cy=pgm_read_byte(font++);
cfont.first=pgm_read_byte(font++);
cfont.count=pgm_read_byte(font++);
cfont.bits=font;
#endif
}
// Globale Variable für das eine physikalische Display
DISP d;
Detected encoding: UTF-8 | 0
|