/* Firmware für den ATmega32U4 eines eHaJo Display-Adapters
Henrik Haftmann, 180711
Aufgaben:
1. Frequenzmessung an 10 Eingängen mit Pullup (max. 1 kHz der Flanken)
2. Report über serielle Schnittstelle
Hardware (nach Ports sortiert; 44 Pins):
SS 8 PB0 i0 SS E0 (Buchse links oben)
SC 9 PB1 i1 SCL E1
MO 10 PB2 i2 MOSI E2
MI 11 PB3 i3 MISO E3
8 28 PB4 i4 ADC11 E4
9 29 PB5 i5 OC1A !OC4B ADC12 E5 (untere Reihe)
10 30 PB6 i6 OC1B OC4B ADC13 E6
11 12 PB7 i7 OC1C OC0A !RTS E7
5 31 PC6 OC3A !OC4A E8
13 32 PC7 ICP3 OC4A CLK0 E9
3 18 PD0 I0 SCL OC0B frei
2 19 PD1 I1 SDA frei
RX 20 PD2 I2 RxD frei
TX 21 PD3 I3 TxD frei
4 25 PD4 ICP1 ADC8 frei
-- 22 PD5 XCK !CTS frei
12 26 PD6 T1 !OC4D ADC9 frei
6 27 PD7 T0 OC4D ADC10 frei
-- 33 PE2 !HWB frei
7 1 PE6 I6 AIN0 Kontroll-LED
A5 41 PF0 ADC0 frei
A4 40 PF1 ADC1 frei
A3 39 PF4 TCK ADC4 frei
A2 38 PF5 TMS ADC5 frei
A1 37 PF6 TDO ADC6 frei
A0 36 PF7 TDI ADC7 frei
3 - D- USB Data-
4 - D+ USB Data+
7 - Ubus USB-Busspannung
RS 13 - !Reset Reset-Eingang
16 - XTAL2 16-MHz-Quarz
17 - XTAL1 16-MHz-Quarz
42 - AREF Kondensator
6 - UCAP Kondensator
2,34,14,44,24 UUcc,Ucc,AUcc 5P
5,15,23,35,43 UGND,GND GND
Verwendung der Timer:
0 1-ms-Abfragetakt
1 frei
3 frei
4 frei
*/
#include <avr/io.h>
#include <avr/wdt.h> // wdt_reset()
#include <avr/eeprom.h>
#include <avr/sleep.h>
#include <avr/interrupt.h> // sei()
#include <stdio.h> // sprintf_P
#include <stdlib.h> // strtoul
#include <avr/pgmspace.h> // PSTR
#include "usb.h"
#define N 10
struct{
unsigned long summa[N]; // Wasseruhren-Stände
word freqinterval; // Frequenzmess-Intervall (Standard: 1000 = 1 s)
word saveinterval; // Wasseruhrenstand-Speicherintervall (Standard: 60000 = 60 s)
}eedata;
word freq[N];
word prev[N];
word frtime,eetime,prevstate;
bool ee_update;
static word getstate() {return (PINC>>6&0x03)<<8|PINB;} // Alle 10 Eingänge
//Interruptroutine mit 1 kHz, die die 10 Eingänge abfragt
//und die Frequenz für 1 Sekunde ermittelt
ISR(TIMER0_COMPA_vect) {
word state=getstate(); // Eingänge abfragen
word edges=prevstate^state; // Flanken erkennen
prevstate=state; // Zustand merken
for (byte i=0; i<N; i++,edges>>=1) {
if (edges&1) ++eedata.summa[i]; // Flanken zählen (wie bei einer Wasseruhr)
}
if (++frtime>=eedata.freqinterval) { // 1 Sekunde vergangen: Frequenz ermitteln
frtime=0;
PINE|=0x40; // LED toggeln
for (byte i=0; i<N; i++) {
word s=word(eedata.summa[i]); // LoWord der Wasseruhr
freq[i]=s-prev[i]; // Subtraktionsüberläufe ignorieren
prev[i]=s; // Wasseruhrenstand merken für nächste Sekunde
}
}
if (++eetime>=eedata.saveinterval) {
eetime=0;
ee_update=true; // minutenweise Zählerstände speichern
}
}
// 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;
}
void bootstart() __attribute__((naked,noreturn));
void bootstart() {
cli();
WDTCSR=0x18;
WDTCSR=0; // Watchdog aus
PRR0=0;
PRR1=0;
SMCR=0;
asm("jmp 0x7000"); // Adresse des Urladers
}
/***********************************************
* Generelle Initialisierung und Hauptprogramm *
***********************************************/
static void setupHardware(void) {
MCUCR = 0x80; // JTAG deaktivieren (sonst gehen PF4..PF7 = ADC4..ADC7 nicht)
MCUSR = 0; // WDRF löschen, damit das nachfolgende Deaktivieren des Watchdogs klappt
WDTCSR|=0x18; // Watchdog aktivieren
WDTCSR= 0x0D; // auf 500 ms, dann Reset
wdt_reset();
CLKPR = 0x80;
CLKPR = 0x00; // CLKDIV8-Fuse ist ab Werk gesetzt, beim Arduino Micro gelöscht
SMCR = 0x01; // Sleep aktivieren
PRR0 = 0x8D; // I²C, Timer1, SPI, ADC aus
PRR1 = 0x19; // Timer4, Timer3, USART aus
PORTB = 0xFF; // Pullups an den Eingängen
PORTC = 0xFF;
PORTD = 0xFF;
PORTF = 0xFF;
DDRE = 0x44; // PE2 / !HWB = low
ACSR = 0x80; // Analogvergleicher deaktivieren
OCR0A = 249; // Zählumfang 0..249
TCCR0A= 0x02; // CTC-Modus
TIMSK0= 0x02; // mit Interrupt
TCCR0B= 0x03; // Vorteiler 64, ergibt 1 kHz
prevstate=getstate();
for (byte i=0; i<N; i++) prev[i]=word(eedata.summa[i]);
sei();
}
int main() {
eeprom_read_block(&eedata,0,sizeof eedata); // alles in einem Rutsch
if (eedata.freqinterval==0xFFFF) eedata.freqinterval=1000;
if (eedata.saveinterval==0xFFFF) eedata.saveinterval=60000;
setupHardware();
usbInit();
for(;;) {
ee_update=ee_update && eeprom_update(&eedata,0,sizeof eedata);
sleep_cpu();
wdt_reset();
usbPoll();
}
}
void handleQuery() {
switch (query[0]) {
case '*': { // "*IDN?"
answerlen=sprintf_P(answer,PSTR("Frequenzmesser FZ 180716 Haftmann\r\n"));
usbSend();
}return;
case '?': { // alle 10 Frequenzen abfragen (passt geradeso in ein 64 Byte-USB-Paket)
answerlen=0;
for (byte c=0; c<N; c++) {
answerlen+=sprintf_P(answer+answerlen,PSTR("%6u"),freq[c]);
}
answerlen+=sprintf_P(answer+answerlen,PSTR("\r\n"));
usbSend();
}return;
case 'B': { // "BL" = Urlader anspringen (für Firmware-Update)
if (query[1]=='L') bootstart();
}break;
case 'T': { // Integrationszeit für Frequenzmessung setzen/abfragen
if (query[1]=='?') {
answerlen=sprintf_P(answer,PSTR("%u\r\n"),eedata.freqinterval);
usbSend();
return;
}
word t=strtoul(query+1,NULL,10);
if (t>=50 && t<=60000) { // min. 50 ms, max. 1 Minute
eedata.freqinterval=t;
ee_update=true; // umgehend im EEPROM aktualisieren
return;
}
}break; // Fehler
default: {
char*p=query;
for (byte i=0; i<querylen; i++,p++) if ('0'<=*p && *p<='9') {
bool sumflag = p!=query && *(p-1)=='S';
byte channel=byte(strtoul(p,&p,10));
if (channel<N && *p=='?') {
answerlen=sumflag
?sprintf_P(answer,PSTR("%lu\r\n"),eedata.summa[channel])
:sprintf_P(answer,PSTR("%u\r\n"),freq[channel]);
usbSend();
return;
}
}
}
}
answerlen=sprintf_P(answer,PSTR("Error\r\n"));
usbSend();
}
Detected encoding: UTF-8 | 0
|