/* Firmware für ATtiny24 auf Doppel-Digitalvoltmeter
für UZD1 und vergleichbare Labornetzteile, Henrik Haftmann
190528 erstellt
Hardware (nach Funktion sortiert):
13 PA0 ADC0 Analogeingang Spannung
12 PA1 ADC1 Analogeingang Strom
11 PA2 ADC2 frei
Charlieplex-Display [L*] [G] [R*]
2 PB0 - K0 Aa (links)
3 PB1 Aa K1 Ab
5 PB2 Ab K2 Ac
10 PA3 Ac K3 Ad
9 PA4 SCK ISP Ad K4 Ae
8 PA5 MISO ISP Ae K5 Af
7 PA6 MOSI ISP Af K6 Ag
6 PA7 Ag K7 - (rechts)
4 PB3 RESET ISP
1 Ucc 5P
14 GND GND
[L*] = Segment links vom gemeinsamen Anschluss
[G] = Gemeinsamer Anschluss (Katode)
[R*] = Segment rechts vom gemeinsamen Anschluss
Verwendung der Timer:
*/
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h> // sei()
#include <avr/pgmspace.h>
#include <avr/fuse.h> // todo
#include <avr/signature.h>
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
#define NOINIT __attribute__((section(".noinit")))
static byte display[8] NOINIT; // bereits für Charlieplex umgerechnete Ausgabedaten
const bool vqe21=true; // Sonderbehandlung für 3½-stellige Anzeige
// (Bei gleicher Verdrahtung andere effektive Anschlussbelegung für VQE21)
// Siebensegmentkode ausgeben
static void out7seg(byte dl,byte pos) {
byte dh=dl<<1;
byte k=1<<pos; // Katodenbit
dl&=k-1; // Bits unterhalb des Katodenbits, bei pos==0 wird dl=0
dh&=~(byte(k<<1)-1); // Bits oberhalb des Katodenbits, bei pos==7 wird dh=0
dl|=dh;
display[pos]=dl; // anzeigen lassen
}
// Zeichen ausgeben; zurzeit nur 0..9 (nicht '0'..'9')
static void outchar(byte c,byte pos) {
static const PROGMEM byte data7seg[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
byte dl=0;
if (c<10) {
if (vqe21 && !(pos&3)) dl=0x0A; // n darf hier nur 1 sein; Segmente „b“ und „d“ treiben
else dl=pgm_read_byte(data7seg+c);
}
out7seg(dl,pos);
}
// Dezimalzahlausgabe von rechts nach links, mindestens 2-stellig
// value darf nicht größer als 9999 sein, mit VQE21 nicht größer als 1999
static void showVal(word value,byte pos) {
char nk=1; // Hier: Mindestens 2 Ziffern ausgeben
word max=vqe21?1999:9999;
if (value>max) value=max;
do{ // Mindestens 1 Ziffer ausgeben
byte rem=value%10;
value/=10; // Compiler optimiert beide Divisionen zu einer
outchar(rem,--pos);
}while(--nk>=0 || value);
while (pos&3) outchar(-1,--pos); // mit „Leerzeichen“ nach links auffüllen
}
static byte curKatode NOINIT; // 0..7
// 1,2 kHz, ÷ 8 = 150 Hz Refresh-Rate
static void multiplex() {
byte i=++curKatode&7;
byte k=1<<i; // Katodenbit
i=display[i]; // Aktive Segmente; das Katodenbit wird dort 0 gehalten
k|=i; // Aktive Ausgänge (Katodenbit dazu)
DDRA=DDRB=0;
PORTB=i&7;PORTA=i&0xF8;// zu invertieren für gemeinsame Anode
DDRB=k&7; DDRA=k&0xF8;
}
static dword adcSum[2] NOINIT; // sammelt 10+8=18 Bit
static word a[2] NOINIT; // Akkumulierte Analogwerte zum Skalieren und Ausgeben in der Hauptschleife
ISR(ADC_vect) {
if (ADMUX&0x01) {
ADMUX&=~0x01;
adcSum[0]+=ADC;
multiplex();
}else{
ADMUX|= 0x01;
adcSum[1]+=ADC;
if (!curKatode) {
a[0]=adcSum[0]>>8; adcSum[0]=0; // 16 der 18 Bits abgreifen (2 LSB verwerfen)
a[1]=adcSum[1]>>8; adcSum[1]=0;
GPIOR0|=0x80; // Dem Hauptprogramm melden
}
}
}
const word CAL0=247; // Endwertkalibrierung für A/D-Wandler, 1024 = 1:1
const word CAL1=1438;
int main() {
for (byte i=0,m=1; i<8; i++,m<<=1) display[i]=~m; // Lampentest
ACSR |= 0x80; // Analogvergleicher deaktivieren
PRR = 0x0E; // Alles aus außer ADC
DIDR0 = 0xFF; // Gar keine Digitaleingänge!
MCUCR = 0x68; // Schlafmodus: ADC-Rauschunterdrückung; keine Pullups (= Flackereffekte beim Charlieplexen)
ADMUX|= 0x80; // Referenzspannung 1,1 V, Eingang ADC0
ADCSRA= 0xFC; // A/D-Wandler starten mit Abtastrate 1 MHz / 32 / 13 = 2,4 kSa/s
ADCSRB|=0x10; // linksbündig
sei();
for(;;) {
sleep_cpu();
if (GPIOR0&0x80) { // 1,2 kHz ÷ 256 = 4,7 Hz Update-Rate
showVal(((dword)a[0]*CAL0>>16)+3,4); // skalieren und ausgeben: Spannung
word w=(dword)a[1]*CAL1>>16;
if (w) w--; // Offset -1
showVal(w,8); // skalieren und ausgeben: Strom
GPIOR0&=~0x80;
}
}
}
Detected encoding: UTF-8 | 0
|