Source file: /~heha/enas/Convac-Ätzer/Firmware-190517.zip/Display.cpp

/* 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-80