Source file: /~heha/enas/Mikroskop/RA2.zip/main.cpp

/* Firmware für den ATmega32U4 eines Arduino Pro Micro
   Zuständig: Susanne + Martin Hartmann, 32347
   Henrik Haftmann
 190412	erstellt
+19041?	Laserpointer dazu
+190506	A/D-Wandler für reflektiertes Licht (mit U/I-Wandler)
+190508	A/D-Wandler-Summation (×64) und Advise (75 Sa/s) — 10 Bit reichen nicht
Aufgaben:
1. Steuerung von 2 Servomotoren und 1 Laserpointer + 1 A/D-Wandler (mit Summation auf 16 Bit)
2. Steuerung und Report über serielle Schnittstelle; A/D-Werte auch automatisch
Keine Bedienelemente vorgesehen
Blöderweise wurde:
1. ein Pro Micro mit 8-MHz-Quarz gekauft
2. ein Umlaufservo gekauft — Änderung in normalen Servo

Hardware (nach Ports sortiert; 44 Pins, 18 von 25 I/Os herausgeführt):
--  8	PB0	SS			(!LED RX)
15  9	PB1	SCL			frei
16 10	PB2	MOSI			frei
14 11	PB3	MISO			frei
 8 28	PB4			ADC11	Lichtsensor am U/I-Wandler
 9 29	PB5	OC1A	!OC4B	ADC12	Servomotor (linke Stiftleiste orange Ader) für einklappbares Polarisationsfilter
10 30	PB6	OC1B	 OC4B	ADC13	Servomotor (rechte Stiftleiste orange Ader) — zurzeit ungenutzt
-- 12	PB7	OC1C	 OC0A	!RTS	-

 5 31	PC6	OC3A	!OC4A		frei
-- 32	PC7	ICP3	 OC4A	CLK0	-

 3 18	PD0	SCL	 OC0B		frei
 2 19	PD1	SDA			frei
RX 20	PD2	RxD			frei
TX 21	PD3	TxD			frei
 4 25	PD4	ICP1		ADC8	frei
-- 22	PD5	XCK		!CTS	(!LED TX)
-- 26	PD6	T1	!OC4D	ADC9	-
 6 27	PD7	T0	 OC4D	ADC10	Laserdiode, H-aktiv

-- 33	PE2	!HWB			-
 7  1	PE6			AIN0	frei

-- 41	PF0			ADC0	-
-- 40	PF1			ADC1	-
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			8-MHz-Quarz
-- 17	-	XTAL1			8-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	frei
1	PWM-Generator für Servomotoren, 50 Hz
3	frei
4	PWM-Generator für Laserdiode
*/

#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>		// strtol
#include <avr/pgmspace.h>	// PSTR
#include "usb.h"

// Diese beiden Konstanten müssen dem Matlab-Programm bekannt sein:
const int MAXSERVO=2200;	// Maximale (sinnvolle) Positionsangabe für Servo
// Die Werte für die Laserdiode sind 0=aus, 1000=ein, dazwischen gedimmt

const int MINPWM=550;	// Minimaler PWM-Wert, sonst dreht der hier verwendete Servomotor durch

static union{
 unsigned ee[3];	// persistente Daten (für EEPROM)
 unsigned pos[4];	// alle Daten (für Kommando '?')
 struct{
  unsigned servo[2];	// Position der beiden Servomotoren, 0 = linker Anschlag, MAXSERVO = rechter Anschlag
  unsigned laser;	// Laserdiode: 0 = aus, 1000 = voll ein
  unsigned adcval;	// A/D-Wander-Summationsergebnis 0..65472 (= 1023×64)
 };
}cfg;
bool ee_dirty;

// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
// Liefert FALSE wenn's nichts zu updaten gibt
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;
}

static void setservo0() {
 OCR1A = cfg.servo[0]+MINPWM;	// Minimaler Wert = 1 ms = 2000, maximaler Wert = 2 ms = 4000
}
static void setservo1() {
 OCR1B = cfg.servo[1]+MINPWM;	// Minimaler Wert = 1 ms = 2000, maximaler Wert = 2 ms = 4000
}
static void setlaser() {
 unsigned on=cfg.laser;
 if (on>1000) on=1000;	// begrenzen
 switch (on) {
  case 1000: PORTD|=0x80; goto timeraus;// voll ein
  case 0: PORTD&=~0x80;	timeraus:	// voll aus
  TCCR4B=0;		// Timer4 anhalten
  TCCR4C=0;		// Portpin freigeben
  break;
  default:
  TC4H=999>>8; OCR4C=999&0xFF;	// 10-Bit-Modus
  TC4H=on>>8;  OCR4D=on&0xFF;
  TCCR4C=0x09;		// COM4D = Fast-PWM
  TCCR4B=0x01;		// mit 8 MHz/1000 = 8 kHz Pulsfrequenz
 }
}

void bootstart() __attribute__((naked,noreturn));
void bootstart() {
 TCCR1B=0;	// Servomotoren stillsetzen
// cli();
 WDTCSR=0x18;
 WDTCSR=0;	// Watchdog aus
 PRR0=0;
 PRR1=0;
 SMCR=0;
 asm("jmp 0x7800");	// Adresse des Urladers (hier: Arduino)
}

/***********************************************
 * 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: Löschen!
 SMCR  = 0x01;	// Sleep aktivieren
 PRR0  = 0x84;	// I²C, SPI aus
 PRR1  = 0x09;	// Timer3, USART aus
 PORTB = 0x80;	// Pullups an Eingängen, Low an Ausgängen
 DDRB  = 0x6F;	// 6 Ausgänge + LED RX (permanent ein)
 PORTC = 0xFF;
 PORTD = 0x7F;
 PORTF = 0xFF;
 DDRD  = 0xA0;	// PD5 = LED TX, PD7 = Laserdiode
 ACSR  = 0x80;	// Analogvergleicher deaktivieren
 setservo0();	// Minimaler Wert = 1 ms = 2000, maximaler Wert = 2 ms = 4000
 setservo1();
 setlaser();
 ICR1  = 20000-1;
 TCCR1A= 0xA2;	// WGM-Modus: 14 = Fast PWM mit ICR1 als TOP-Wert
 TCCR1B= 0x1A;	// Vorteiler 8, Zählumfang 20000 = 20 ms = 50 Hz
 DIDR2 = 0x08;	// PB4 als reiner Analogeingang
 ADMUX = 0xC3;	// Referenzspannung 2,56 V, Eingang ADC11 (MUX=0b100011)
 ADCSRB= 0x20;	// MUX5 setzen
 ADCSRA= 0xE7;	// A/D-Wandler starten mit Abtastrate 8 MHz / 128 / 13 = 4,8 kSa/s
// sei();
}

static bool adviseOn;

static void advise() {
 if (!adviseOn) return;
 answerlen=sprintf_P(answer,PSTR("%u\r\n"),cfg.adcval);
 usbSend();		// abschicken
}

static void idle() {
 ee_dirty=ee_dirty && eeprom_update(&cfg.ee,0,sizeof cfg.ee);
 if (ADCSRA&0x10) {	// Neuer A/D-Wert verfügbar?
  static unsigned adcsum;
  static byte adccnt;
  ADCSRA=ADCSRA;	// Interruptanforderung löschen
  adcsum+=ADC;		// aufsummieren
  if (++adccnt==64) {	// Ergebnisrate 75 Hz
   cfg.adcval=adcsum;
   adcsum=0;
   adccnt=0;
   advise();	// PC benachrichtigen falls Funktion aktiviert
  }
 }
 //sleep_cpu();	// Keine Interrupts, daher auch kein Sleep!
 wdt_reset();
 usbPoll();
}

int main() {
 USBCON=0x20;	// erforderlich (nur) bei Arduino-Urlader, sonst klappt usbInit() nicht
 eeprom_read_block(&cfg.ee,0,sizeof cfg.ee);	// alles in einem Rutsch
 if (cfg.servo[0]>MAXSERVO) cfg.servo[0]=MAXSERVO/2;	// Servos: Mitte
 if (cfg.servo[1]>MAXSERVO) cfg.servo[1]=MAXSERVO/2;
 if (cfg.laser>1000) cfg.laser=0;	// Laser aus
 setupHardware();
 usbInit();
 for(;;) idle();
}

// Callback, wird von usb.cpp aufgerufen wenn der PC einen String gesendet hat
void handleQuery() {
 byte err=1;	// Fehlerkode: 1 = falscher Befehl
 switch (query[0]) {
  case '*': {	// "*IDN?"
   answerlen=sprintf_P(answer,PSTR("Motor- und Lasersteuerung RA2 190507 Haftmann\r\n"));
   usbSend();
  }return;
  case '?': {	// 2 Positionen, Laserleistung und A/D-Wert abfragen
   answerlen=0;
   for (byte c=0; c<4; c++) {	// Index 3 = akkumulierter Messwert
    answerlen+=sprintf_P(answer+answerlen,PSTR("%u,"),cfg.pos[c]);
   }
   answer[answerlen-1]='\r';	// letztes Komma überschreiben
   answer[answerlen++]='\n';	// DOS-Zeilenende generieren
   usbSend();			// abschicken
  }return;
  case 'A': adviseOn=true; return;	// A/D-Wandlungsergebnisse automatisch benachrichtigen (zeilenweise ASCII)
  case 'U': adviseOn=false; return;	// Stopp mit dem 75-Hz-Datenoutput („unadvise“)
  case 'B': {		// "BL" = Urlader anspringen (für Firmware-Update)
   if (query[1]=='L') bootstart();
  }break;
  case 'H': {	// Nothalt (ungenutzt)
  }return;
  case 'Z': {	// Nullsetzen (ungenutzt)
  }return;
  case 'W': {	// Warte bis fertig: Sende Antwort sofort
   answerlen=sprintf_P(answer,PSTR("OK\r\n"));
   usbSend();
  }return;
  case 'm':
  case 'M': {	// "Move" = Motor bewegen, Argument = Nummer,Zielposition[,Richtung+Geschwindigkeit]
   char*e;
   byte i=(byte)strtol(query+1,&e,0);
   if (i>2) {err=2;break;}	// Fehler: Falscher Index
   if (*e!=',') {err=3;break;}	// Fehler: Weiteres Argument erwartet
   bool rel=*++e=='+';	// Relativ-Modus („Bewegen um“, nicht „auf“)
   int p=(int)strtol(e,&e,0);
   if (p<0) rel=true;	// ebenfalls relativ bei negativem Vorzeichen
//Servomotoren, Laserdiode
   if (rel) p+=cfg.pos[i];
   if (i<2 && (word)p>(word)MAXSERVO) {err=5;break;} // Fehler: Unzulässige Position
   cfg.pos[i]=p;	//Speichern
   switch (i) {
    case 0: setservo0(); break; // Neue Position ausgeben
    case 1: setservo1(); break;
    default: setlaser(); break;
   }
   ee_dirty=true;
  }return;		// Erfolg ohne Antwort
 }
 answerlen=sprintf_P(answer,PSTR("Error %u\r\n"),err);
 usbSend();
}
Detected encoding: UTF-80