/*
060703 erstellt
Wenn's mal klemmt, debuggen mit
1. Konsolenfenster ("cmd" starten)
>msp430-gdbproxy.exe --port=3333 msp430
2. Konsolenfenster ("cmd" starten)
>msp430-gdb solar.elf
(gdb) target remote localhost:3333
(gdb) load solar.a43
(gdb) break funktionsname
*/
#include "solar.h"
#include <mspgcc/ringbuffer.h> //Ringpuffer
#include <sys/cdefs.h> //NULL
#include <stdio.h> //puts()
#include "conio.h"
void delay(unsigned int d){
int i;
for (i = 0; i < d; i++) {
nop();
nop();
}
}
typedef struct{ // Abgleichpunkt
int ADvalue; // A/D-Wert
int DispValue; // Festkommazahl
}TAbgleich,*PAbgleich;
typedef struct{
TAbgleich a[2];
}TZweipunkt; // für Zweipunktabgleich
TZweipunkt InstrumentAbgleich[7]={ // überschreibbare Vorgabewerte
{{{0xFFF<<3,0},{0x000,1881}}}, // U(Solar) 0 .. 18,81 V
{{{0xFFF<<3,0},{0x000,3102}}}, // I(Solar) 0 .. 3,102 A
{{{0xFFF<<3,0},{0x000,1881}}}, // U(Last) 0 .. 18,81 V
{{{0xFFF<<3,0},{0x000,3102}}}, // I(Last) 0 .. 3,102 A
{{{0xFFF<<3,0},{0x000,1881}}}, // U(Akku) 0 .. 18,81 V
{{{0xC40<<3,0},{0x000,-2350}}}, // I(Akku) .. 0 .. -2,350 A
{{{0x4D3<<3,0},{0x680<<3,1000}}}}; // T(Chip) .. 0 .. 100,0 .. °C
// Wie Win16-GDI, allerdings (noch) ohne Rundung
// Der Compiler verwendet automatisch den Hardware-Multiplizierer,
// benutzt jedoch eine ungünstige Divisionsroutine
// (ggf. in Assembler nachzuprogrammieren, s4:s2=s2 Rest s2)
int MulDiv(int f1, int f2, int n) {
return (int)((long)f1*f2/n);
}
// Integerzahl <x> zwischen <u> und <o> eingrenzen
int Limit(int x, int u, int o) {
if (x<u) x=u;
if (x>o) x=o;
return x;
}
//Lineare Transformation von int zu int, kein Problem bei a==e
// Zahl <x>, die (nicht unbedingt) zwischen Abgleichpunkten <a> und <e> liegt,
// in den Bildbereich mit den Abgleichwerten <A> und <E> transformieren;
// heraus kommt also <X> als das transformierte <x>.
int iitrafo(int x, int a, int e, int A, int E) {
e-=a;
if (!e) return 0;
return MulDiv(x-a,E-A,e)+A;
}
// Zweipunktabgleichtabelle verwenden, Integer-Rechnung
int AD2Disp(int ADUval, const TZweipunkt*cal) {
return iitrafo(ADUval,cal->a[0].ADvalue,cal->a[1].ADvalue,
cal->a[0].DispValue,cal->a[1].DispValue);
}
typedef struct{
int u,i,p; // Spannung[mV], Strom[mA], Leistung[mW]
}TTriple, *PTriple; // Anzeige-Tripel
typedef struct{
int min, max; // als Festkommawerte
INT Val, Peak[2]; // als LED-Nummer (Großbuchstabe weil "Bild-Bereich")
INT peakdelay[2]; // zum Halten des Maximums (vor Rücklauf)
}TBarControl, *PBarControl;
TBarControl BarControl[9]={ // Minima und Maxima vorinitialisieren
{300, 1000},
{0, 300},
{0, 6000},
{300, 2000},
{0, 3000},
{0, 6000},
{880, 1440},
{-1000,1000},
{-1440,1440}};
#define BAR_EXPAND 1 // Minimum und Maximum an Messwert anpassen
#define BAR_FLASH 2 // Aufblitzen (durch größere Helligkeit)
#define PEAKDELAY 10
// In regelmäßigen Zeitabständen (ca. 10 Hz) aufrufen!
void BarUpdate(INT nBar,int val,INT flags){
unsigned long bar;
int Zero,Left,Right; // LED-Positionen (0..31)
TBarControl *pBar=BarControl+nBar;
if (flags&BAR_EXPAND) {
if (pBar->min>val) {pBar->min=val; flags|=BAR_FLASH;}
if (pBar->max<val) {pBar->max=val; flags|=BAR_FLASH;}
}
Zero =Limit(iitrafo(0, pBar->min,pBar->max,0,31),0,31);
Right=Limit(iitrafo(val,pBar->min,pBar->max,0,31),0,31);
Left=Zero; if (Right<Zero) {Left=Right; Right=Zero;}
if (pBar->Peak[1]<=Right) {
pBar->Peak[1]=Right; pBar->peakdelay[1]=PEAKDELAY;
}else if (pBar->peakdelay[1]) { // Zähler <>Null?
--pBar->peakdelay[1]; // Peak stehen lassen
}else if (pBar->Peak[1]>Zero) { // sonst Peak zur Null laufen lassen
pBar->Peak[1]--;
}
if (pBar->Peak[0]>=Left) {
pBar->Peak[0]=Left; pBar->peakdelay[0]=PEAKDELAY;
}else if (pBar->peakdelay[0]) { // Zähler <>Null?
--pBar->peakdelay[0]; // Peak stehen lassen
}else if (pBar->Peak[0]<Zero) { // sonst Peak zur Null laufen lassen
pBar->Peak[0]++;
}
// zwei Peaks waren zunächst nicht vorgesehen, aber wirklich nützlich
bar=TablCalcBargraf(Left,Right,pBar->Peak[1]);
bar|=SHLD[pBar->Peak[0]];
TablSetBargraf(nBar,bar);
if (flags&BAR_FLASH) TablHiliteDigit[nBar]|=0xF0;
else TablHiliteDigit[nBar]&=~0xF0;
}
// Zyklisch aufrufen!?
void HandleAllInstruments(void) {
INT k,t; // k=A/D-Kanal, t=Tableau-Nr.
int u,i,uu,ii,pp;
for (k=t=0; t<9; ) {
u=ADU_Get(k); // Spannung
uu=AD2Disp(u,&InstrumentAbgleich[k]);
k++;
i=ADU_Get(k); // Strom
ii=AD2Disp(i,&InstrumentAbgleich[k]);
k++;
pp=MulDiv(uu,ii,1000); // Leistung in 10-mW-Schritten
TablDecimalOut(t,uu,2); // in 10-mV-Schritten
BarUpdate(t,uu,BAR_EXPAND);
TermDrawValue(t,uu,2,TablGetBargraf(t),u);
t++;
TablDecimalOut(t,ii,3); // in 1-mA-Schritten
BarUpdate(t,ii,BAR_EXPAND);
TermDrawValue(t,ii,3,TablGetBargraf(t),i);
t++;
TablDecimalOut(t,pp,2); // in 10-mW-Schritten
BarUpdate(t,pp,BAR_EXPAND);
TermDrawValue(t,pp,2,TablGetBargraf(t),-1);
t++;
}
// hier sollte k==6 und t==9 sein
}
extern void Idle(void) {
// noch nichts tun, besser: schlafen!
}
RINGBUFFER_NEW(MsgQue,16); // Nachrichtenwarteschlange
enum{
MSG_KEYUP, // Nokia Pfeil hoch
MSG_KEYRECV, // Nokia Hörer abnehmen (receiver = Hörer)
MSG_KEYDOWN, // Nokia Pfeil runter
MSG_KEYHUP, // Nokia Hörer auflegen (hang-up = auflegen)
MSG_KEYLEFT, // Nokia Pfeil links
MSG_KEYOPTR, // Nokia Option rechts
MSG_KEYRIGHT, // Nokia Pfeil rechts
MSG_KEYOPTL, // Nokia Option links
MSG_SWL, // Nockenschalter links
MSG_SWM, // Nockenschalter Mitte
MSG_SWR, // Nockenschalter rechts
MSG_TICK, // Timer-Tick (1ms)
};
// Tasten-Drück- und Loslass-Ereginisse generieren, VT100 aktualisieren
// Zyklisch (ca. 10 Hz) aufrufen!
static void Tastenabfrage(void) {
static BYTE TastenVorher;
BYTE k=Inp_Tasten()^TastenVorher; // k = geänderte Zustände
BYTE ta=TermTaster; // Virtueller Tastenzustand
if (k) {
INT i;
BYTE mask;
if (k) for (i=0, mask=1; mask; i++, mask<<=1) {
if (k&mask) {
if (TastenVorher&mask) { // loslassen
ta&=~mask;
}else{ // drücken
ta|=mask;
ringbuffer_put(&MsgQue,i); // Kode 0..7 entsprechend obigem <enum>
}
TermSetTaster(ta); // Virtuelle Tasterstellung setzen
}
}
TastenVorher^=k;
}
}
static void Schalterabfrage(void) {
static BYTE SchalterVorher; // Reale Nockenschalterstellung
BYTE k=Inp_Schalter();
if (k!=SchalterVorher) {
SchalterVorher=k;
ringbuffer_put(&MsgQue,MSG_SWL+k);
TermSetSchalter(k); // Virtuelle Nockenschalterstellung seten
}
}
typedef struct _tagTimerEntry{
struct _tagTimerEntry *next;
unsigned StartAt; // nichtperiodisch!
void (*CallbackProc)(int);
int CallbackValue;
}TTimerEntry,*PTimerEntry;
TTimerEntry TimerList[1]; // erst mal nur ein Timer
PTimerEntry SetTimer(unsigned ms, void (*CallbackProc)(int), int CallbackValue) {
PTimerEntry pTE=TimerList;
// Jetzt müsste eine Einsortierung erfolgen!
pTE->StartAt=GetTickCount()+ms;
pTE->CallbackProc=CallbackProc;
pTE->CallbackValue=CallbackValue;
return pTE;
}
static void HandleTimer(void) {
PTimerEntry pTE=TimerList;
if (pTE->CallbackProc && (int)(GetTickCount()-pTE->StartAt)>=0) {
pTE->CallbackProc(pTE->CallbackValue);
pTE->CallbackProc=NULL;
}
}
static void RemoveTaster(int i) {
TermSetTaster(TermTaster&~SHL[i]);
}
// Eingabe von Hauptseite verarbeiten, liefert TRUE wenn Zeichen geschluckt
static bool HandleStdVt100Input(char c) {
static WORD DauVal;
int i=TermChar2KeyBit(c);
if (i!=-1 && !(TermTaster&SHL[i])) {
TermSetTaster(TermTaster|SHL[i]);
ringbuffer_put(&MsgQue,i);
SetTimer(100,RemoveTaster,i);
return true; // Zeichen verarbeitet!
}
switch (c) {
case 0x10: { // ^P = Pfeil hoch
DauVal+=10;
DAU_Put(0,DauVal);
TermDrawDAU0();
}return true;
case 0x0E: { // ^N = Pfeil runter
DauVal-=10;
DAU_Put(0,DauVal);
TermDrawDAU0();
}return true;
}
c&=~0x20; // Großbuchstaben
switch (c) {
case 'L': c=0; goto x;
case 'M': c=1; goto x;
case 'R': c=2; x: {
ringbuffer_put(&MsgQue,MSG_SWL+c);
TermSetSchalter(c);
}return true;
case 'X':
case 'Y':
case 'Z': {
c-='X';
Rel_SetState(c,!Rel_GetState(c));
TermDrawRelais(c);
}return true;
}
return false;
}
int main(void) {
unsigned int i,j;
// TTriple Solar/*,Last,Akku*/;
// int bar,peak=0,peakdelay=0;
WDTCTL = WDTPW|WDTHOLD;
// Die folgenden Register habe ich nirgends in der Doku gefunden:
BCSCTL1 &= ~XT2OFF; // XT2 = HF XTAL
do {
IFG1 &= ~OFIFG; // Clear OSCFault flag
for (i = 0xFF; i > 0; i--); // Time for flag to set
}while ((IFG1 & OFIFG) != 0); // OSCFault flag still set?
BCSCTL2 |= SELM1|SELS; // MCLK = XT2 (safe)
// FLL_CTL1*/ = 0x14; // SMCLK = 8 MHz
TablInit(); // Zuerst(!) wegen Timer
/*TEST*/
TablLeds[0][4]=2;
TablStrOut(0,"3.14");
TablHiliteDigit[0]=0x10; // probeweise Ziffer links HELL
// Tableau_Bargraf(4,0,31,32);
// Tableau_Bargraf(5,16,20,30);
Ser0Init();
ADU_Init();
DAU_Init();
_EINT(); // Globale Interrupts ein
DispInit(); // benötigt Interrupts für Zeitüberschreitungserkennung
TermInit();
conio_init();
if (DispFailed()) puts("Kein Display!");
Rel_SetState(0,false); TermDrawRelais(0);
Rel_SetState(1,true); TermDrawRelais(1);
Rel_SetState(2,false); TermDrawRelais(2);
for(;;){
AnsiSaveCursor();
TermDrawDrehstrich();
if (kbhit()) {
HandleStdVt100Input(AnsiGetKey());
}
Tastenabfrage();
Schalterabfrage();
HandleTimer();
// Tableau_Bargraf(8,0,i>>4,0);
i=(i+1)&0x1FF;
DispPrintf(14,3,"%3X",i);
DispPrintf(14,4,"%02X",Inp_Tasten());
HandleAllInstruments();
// DAU_Put(0,/*((long)i*28836)>>16*/0x7FF); // funktioniert?
// Solar.u=((0xFFF-i)*301033UL)>>16; // Spannung in 1-mV-Schritten
// 301033UL = 3300(1mV)*5,7(Widerstandsteilerverhältnis)/4095(FullScale)
// bar=Solar.u*30UL/12000;
// if (peak<=bar){peak=bar; peakdelay=100;}
// else {if (peakdelay) --peakdelay; else if (peak) --peak;}
// Tableau_Bargraf(0,0,bar,peak);
/*
DispPrintf(14,5,"%03X = %u,%03u V",i,Solar.u/1000,Solar.u%1000);
i=0xFFF-ADU_Get(1); // Strom der Solarzelle
Solar.i=(i*59572UL)>>16;
//3300(1mV)/4095(FullScale)*47kOhm/1MOhm/0,05(V/A)
TablDecimalOut(1,Solar.i,3);
Solar.p=((long)Solar.u*Solar.i)/1000;
TablDecimalOut(2,Solar.p/10,2);
*/
i=ADU_Get(7); // 3,55mV/K, 986mV @ 0°C
j=((i*112703UL)>>16)-2777;
// 2500(mV)/4095(FS)/3,55(mV/K)*10*65536
// 986mV/3,55mV/K*10
DispPrintf(14,6,"%03X = %u,%u °C",i,j/10,j%10); // Chiptemperatur
// Lampentest();
AnsiRestoreCursor();
Idle();
delay(0xffff);
if (DispFailed()) {
DispInit();
if (!DispFailed()) {
puts("Display wieder vorhanden!");
}
}
}
}
Vorgefundene Kodierung: ANSI (CP1252) | 4
|
|