#include "Convac.h"
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <stdlib.h> // exit()
/**************
* Buszugriff *
**************/
byte BUS::ioRead(byte a) {
PORTD = a;
PORTE&=~0x40; // Kartenauswahl aktivieren
PORTF&=~0x02; // !IOSTB aktivieren
NOP2(); // Display: 140 ns
if ((a&0xF8)==BA_DISPLAY) { // Sonderfall Display
_delay_us(1);
PORTD|= 0x04; // E auf High
_delay_us(2);
}
if (a==BA_ANALOG+1) _delay_us(2.4); // Sonderfall AD7828
byte b= PINB; // Byte einlesen
if ((a&0xF8)==BA_DISPLAY) {
PORTD&=~0x04; // E auf Low (mitten im Zyklus)
_delay_us(2);
}
PORTF|= 0x02;
PORTE|= 0x40;
return b;
}
void BUS::ioWrite(byte a, byte b) {
PORTD = a;
PORTF&=~0x01; // !WR aktivieren (wird vom Display ignoriert)
PORTB = b; // Byte anlegen
DDRB = 0xFF;
PORTE&=~0x40; // Kartenauswahl aktivieren
PORTF&=~0x02; // !IOSTB aktivieren
NOP2();
if ((a&0xF8)==BA_DISPLAY) {
_delay_us(1);
PORTD|= 0x04; // E auf High
_delay_us(2);
PORTD&=~0x04; // E auf Low (mitten im Zyklus)
_delay_us(2);
}
PORTF|= 0x02;
PORTE|= 0x40;
DDRB = 0x00;
PORTB = 0xFF; // Pullups
PORTF|= 0x01;
}
/****************************
* EEPROM-gespiegelte Daten *
****************************/
EEDATA eedata; // Genau 1024 Bytes groß!
// (Der ATmega32U4 hat ungewöhnlich wenig EEPROM.)
RLISTE *rl; // Dynamisch verwaltete Rezeptliste, hinter der Globalen Sicherheit
word gslen; // Länge der dynamischen Daten in Bytes = Ergebnis von gs->size()
/*
Die Rezeptspeicherverwaltung geht von dicht hintereinendergelegten Arrays aus.
Statt Speicher zu fragmentieren wird Platz mittels memmove verwaltet.
Der RAM ist größer als der EEPROM, daher wird die gesamte Liste im RAM gehalten
und im Hintergrund mit dem EEPROM synchronisiert.
Alle Arrays und Strukturen sind dynamisch. Bei knapp 1 KByte ist dann Schluss.
*/
/*****************
* Hilfsroutinen *
*****************/
void bootstart() {
cli();
PORTF|=0x80; // Ausgabetreiber am Bus deaktivieren
USBCON=0x20; // USB aus
UHWCON=0; // USB-Spannungsregler aus
PLLCSR=0; // PLL aus
// WDTCSR=0x18;
// WDTCSR=0; // Watchdog aus (sonst verreckt der Atmel-Urlader)
SP=RAMEND;
asm("jmp 0x7E00"); // Adresse des Urladers (ggf. ändern!)
}
// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
bool eeprom_update(const void*ram, void*eeprom, size_t len) {
if (EECR&0x02) return true;
const byte*src=(const byte*)ram;
byte*dst=(byte*)eeprom;
for (;len;src++,dst++,len--) {
EEAR=(word)dst;
EECR|=1;
if (EEDR!=*src) {
EEDR=*src; // Adresse steht noch in EEAR
cli();
EECR|=0x04;
sei();
EECR|=0x02; // immer noch mit gesperrten Interrupts (wird zu SBI compiliert)
return true;
}
}
return false;
}
volatile TICKER ticker;
ISR(TIMER1_CAPT_vect,ISR_NAKED) {
#if 1
asm volatile(
" push r0 \n"
" push r16 \n"
" in r0,%0 \n"
" lds r16,ticker \n"
" subi r16,-1 \n"
" sts ticker,r16 \n"
" brcs 1f \n"
" lds r16,ticker+1\n"
" sbci r16,-1 \n"
" sts ticker+1,r16\n"
" lds r16,ticker+2\n"
" sbci r16,-1 \n"
" sts ticker+2,r16\n"
" lds r16,ticker+3\n"
" sbci r16,-1 \n"
" sts ticker+3,r16\n"
"1: out %0,r0 \n"
" pop r16 \n"
" pop r0 \n"
" reti \n"
::"I"(_SFR_IO_ADDR(SREG)):"memory");
#else
++ticker.l; // compiliert zeitraubend mit vielen push/pop
#endif
}
// Die Idle-Prozedur wartet und ruft zwei Prozeduren zyklisch auf.
// Außerdem wird der EEPROM dem RAM nachgeführt.
void idle() {
static byte proctic;
static byte clocktic;
if (10<=byte(ticker.b-proctic)) {
proctic+=10;
process();
KEYS::peek();
}else if (100<=byte(ticker.b-clocktic)) {
clocktic+=100;
CLOCK::show(); // Hier keine Rekursion zu befürchten
}else{
eeprom_update((byte*)&eedata,(byte*)&eesave,sizeof(CONFIG)+gslen); // alles!
usbPoll();
PORTD|= 0x20; // Schlafmodus (= !Last) anzeigen
sleep_cpu(); // maximal 10 ms
PORTD&=~0x20; // (das Restprogramm beschreibt PORTD.5 mit Null)
}
}
// Wartet (Mindest-)Zeit [beißt sich nicht mit tick()]
void wait(byte ms10) {
byte tic=ticker.b;
do idle(); while (ms10>byte(ticker.b-tic));
}
// Piepser an OC4D mit variabler Frequenz und Tastverhältnis etwa 50% ansteuern
void beepon(byte f) {
OCR4C =f; // immer im CTC-Modus
OCR4D =f>>1; // 50%
TCCR4C=0x09; // OC4D aktivieren
TCCR4B=0x08; // CK/128 => 488 Hz
}
static void beepoff() {
TCCR4B=0; // Timer anhalten (wirkungslos!?)
TCCR4C=0x00; // OC4D freigeben
}
void beep(byte f, byte ms10) {
beepon(f);
wait(ms10);
// OCR4C =f; // sollte die Frequenz festlegen, ändert aber nichts
// wait(1);
beepoff();
}
// Blockierende Version mit Tastenpiep, <ms10>=0 wartet unendlich.
// ruft beim Warten zyklisch idle() auf für Hintergrundaktivität
// Die Funktionstasten liefern (logischerweise) 0xF1..0xF6
byte getkey(byte ms10) {
byte c;
byte tic=ticker.b;
do{
idle(); // mindestens 1× (sonst kommt bei vielen Tastendrücken der Watchdog)
if (ms10 && ms10<=byte(ticker.b-tic)) return 0;
}while (!(c=KEYS::readchar()));
beep(0xFF,5);
return c;
}
/**********************************************
* Menüsystem (= Funktionstastenbeschriftung) *
**********************************************/
// Malt einen Menüpunkt (Funktionstastenbeschriftung)
// <prefix> ist für Checkboxen und Radiobuttons gedacht.
void paintMenuItem(byte x, const char*s,wchar_t prefix) {
prepareField(x+1,53,x+39,63);
prepareField(x+1,53,x+39,63);
if (s) {
if (prefix) {
d.X=x+2; // linksbündig
d.print(prefix); // Schaltzeichen
d.X+=2; // Freiraum
}else d.X=x+20-(d.getTextExtent(s)>>1); // Text zentrieren
d.print(s); // Text ausgeben
}
}
void paintMenuZoom() {
paintMenuItem(5*40,F("Zoom"),24); // (logischerweise stets leere) Checkmark
}
// <mask> erlaubt das gezielte Ausblenden von Tastenfunktionen,
// wobei das entsprechende Feld weiß ausgemalt (gelöscht) wird.
// <change> erlaubt das Vermeiden der (langsamen) Textausgabe,
// wenn sich nur wenig ändert.
// Die Bits darin gehen von links nach rechts (Bit 0 = Taste F1 usw.)
// Change Bit 7 lässt die Trennlinien malen.
void paintMenu(const char*s,byte mask,byte change) {
if (!change) return;
idle();
d.flags=d.COLOR|d.R2_COPYPEN; // kein COOKED
if (change&0x80) {
d.clrClip();
d.drawLine(0,52,239,52); // waagerecht oben
}
byte chg=change; // Bit 7 retten für senkrechte Linien
for (byte x=0;;) {
if (change&1) paintMenuItem(x,mask&1?s:NULL);
x+=40;
d.clrClip();
if (x==240) break;
if (chg&0x80) d.drawLine(x,53,x,63); // Trennlinie senkrecht
change>>=1;
if (mask>>=1) s=strend1(s); // nächster Teilstring
}
idle();
}
void errorbeep() {
beep(128,20);
}
// Modaler Unterdialog, pfeift auf zoom.
// Bei aktivem "zoom" muss eben alles nachher neu gemalt werden.
bool yesno() {
paintMenu(F("Ja\0Nein"),3);
switch (getkey()) {
case 0xF1: return true;
case 0xF2:
case '\r': return false;
}
errorbeep(); // Alle anderen Tasten (auch Ziffern) anmeckern
return false; // sowie auch raus
}
byte tic; // Snapshot um eine bestimmte Zeit zu warten
bool ticCheck(byte ms10) { // liefert true mit einer festen Frequenz
if (ms10>byte(ticker.b-tic)) return false;
tic+=ms10;
return true;
}
/*
// Wartet Zeit vom letzten Aufruf von tick(), d.h. generiert eine Frequenz
// ¦ Ein Argument von 50 generiert 2 Hz.
static void ticWait(byte ms10) {
do idle(); while (!ticCheck(ms10));
}
*/
void xorFocusRect(byte l, byte t, byte r, byte b) {
d.flags=d.COLOR|d.R2_XORPEN;
d.drawRect(l,t,r,b);
}
// Startet Zähler und setzt das Sichtbarkeitsbit
void startFocusCounter() {
ticStart();
setFocusVis();
}
/*===================*
* Uhr und Animation *
*===================*/
// Dezimalzahl zweistellig mit führender Null ausgeben, d = 99
static void outDD(word z) {
udiv_t q=udiv(z,10);
d.print(q.quot+'0');
d.print(q.rem+'0');
}
// eine Zeit in "hh:mm:ss" ausgeben, maximal 18:12:15
// Benötigt 34×11 Pixel
static void outTime(word s) {
udiv_t q=udiv(s,60);
udiv_t r=udiv(q.quot,60);
outDD(r.quot);
d.print(':');
outDD(r.rem);
d.print(':');
outDD(q.rem);
}
static DISP dispsave; // ein paar Byte zur Kontextsicherung
namespace CLOCK{
void paint() {
prepareField(188,0,223,10); // 36×11 Pixel
d.X=189; // etwas Platz links
outTime(ticker.l/100U); // Sekunden Uptime (endet bei 18:12:15)
}
// Uhr aus Idle-Kontext heraus aktualisieren, Aufrufr alle 1 s
void show() {
if (zoom) return; // keine Uhr
dispsave=d;
paint();
d=dispsave;
}
}/*namespace CLOCK*/
namespace ANI{
enum{
W=12, // „zählende“ Pixel: Breite-3
H=8, // Höhe-3
ANILEN=W+H+W+H,
};
static byte state; // Animationsstatus 0..39
// Animationsrechteck 14×11 Pixel mit angefasten Ecken (RoundRect),
static void setPixel(byte nr) {
while (nr>=ANILEN) nr-=ANILEN;
byte x,y;
if (nr<W) { // oben
x=nr+1; y=0;
if (!nr) y++; // Ecke links oben
}else if ((nr-=W)<H) { // rechts
x=W+2; y=nr+1;
if (!nr) x--; // Ecke rechts oben
}else if ((nr-=H)<W) { // unten
x=W+1-nr; y=H+2; // linksherum
if (!nr) y--; // Ecke rechts unten
}else{ nr-=W; // links
x=0; y=H+1-nr; // nach oben
if (!nr) x++; // Ecke links unten
}
d.drawPixel(x+225,y);
}
// Animierten Prozessfortschritt anzeigen: Umrandung der Aktionsnummer animieren
// (mit geringstmöglichem Prozessoraufwand)
void animate() {
byte i=state;
if (!zoom) {
dispsave=d;
d.clrClip();
d.flags=d.COLOR|d.R2_XORPEN;
setPixel(i); // Pixel löschen am Schwanz
setPixel(i+ANILEN/2); // Pixel setzen am Kopfende zur Hälfte voraus
d=dispsave;
}
if (++i>=ANILEN) i=0; state=i;
}
void paint() {
prepareField(224,0,239,10); // löscht auch die „Schlange“
byte n=run.aktion;
if (n) {
d.X=228; // 2 Pixel Luft links und rechts (Optik)
outDD(n); // Zahl ausgeben
for (byte i=0; i<ANILEN/2; i++) setPixel(state+i); // alle Pixel setzen
}
}
void show() {
if (!run.aktion) state=0;
if (zoom) return;
dispsave=d;
paint();
d=dispsave;
}
}/*namespace ANI*/
void onZoomClear() {
CLOCK::paint();
ANI::paint();
}
// Text oben zentriert ausgeben
void centerTitle(const char*s,byte y) {
prepareField(52,y,187,y+10); // 52 Pixel Platz oben rechts für die Uhr lassen!
if (s) {
byte x=d.getTextExtent(s);
d.X=x>136?52:120-(x>>1); // zu langen Text linksbündig ausgeben und rechts clippen lassen
d.print(s);
}
}
ListView lv;
Edit ed;
// Vertikale Pixelposition eines Items in der Liste bestimmen
// (Sichtbarkeit ist ein anderes Thema!)
word lvPixelY(byte val) {
return val*lv.dih; // lv.H muss zuerst gesetzt werden!
}
word lvScreenY(byte line) {
return lvPixelY(line)-lv.pos+lv.T; // oben
}
// Funktionstasten umrechnen, <b> ab 0, nicht ab 0xF1
byte transcode(byte b) {
static const PROGMEM byte tbl[6]={ed.CUL,ed.CUR,'.','-',',',ed.BS};
return pgm_read_byte(tbl+b);
}
byte lvInit(byte pos,byte max) {
if (pos>max) pos=max;
lv.dih=d.getFontHeight();
lv.init(0,zoom?0:11,239,zoom?63:50);
lv.pos+=lv.posIntoView(pos);
d.clrClip(); // damit die Scrollbar gemalt wird
lv.setLen(lvPixelY(++max));
clrFocusVis();
return pos;
}
// generiert Blinken und führt das Fokus-Sichtbarkeitsbit mit
// (Nur für ganze Zeilen, hier Rezeptliste und Alarmliste)
void lvXorFocus(byte line) {
d.saveClrClip();
lv.xorFocusRect(line);
xorFocusVis();
d.restoreClip();
}
byte updown(byte focus,byte action,byte max) {
focus++; if (action==0xF1) focus-=2;
if (focus>max) focus=action==0xF1?max:0;
return focus;
}
/******************
* Programmablauf *
******************/
// Aufruf alle 100 ms im Idle-Kontext
void process() {
if (!inIdle) { // Nicht rekursiv aufrufen bloß weil's piept
enterIdle();
wdt_reset(); // Überwachung des regelmäßigen Aufrufs (sonst Bluescreen)
run.tick(); // Überwacht auch externe Schalter und Tasten
leaveIdle();
}
}
/*=====================*
* Maschinensicherheit *
*=====================*/
ISR(WDT_vect,ISR_NAKED) {
exit(-2);
}
ISR(BADISR_vect,ISR_NAKED) {
exit(-1);
}
// der „Bluescreen Of Death“ (BSOD) ist hier:
extern "C" void exit(int) __attribute__((noreturn));
extern "C" void exit(int code) {
PORTF|=0x80; // Alle Ausgabetreiber auf dem Bus hochohmig
beepon(100);
cli(); // nötig für normalen exit()-Aufruf
d.init();
d.clear();
d.print(F("Absturz! Softwarefehler!"));
// 1 = unerwarteter Interrupt, 2 = Watchdog-Interrupt, 3 = EEPROM-Programmdaten korrupt, nun gelöscht
d.print(floatstr(code),0,20);
d.print(F("Neustart: Maschine aus- und einschalten"),0,40);
bootstart();
//for(;;); // keine Rückkehr!
}
Detected encoding: UTF-8 | 0
|