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

/* Firmware für den ATmega32U4 eines Arduino Pro Micro
   Henrik Haftmann, 190228
Aufgaben:
1. Steuerung von 2 Servomotoren und 1 Getriebe-Schrittmotor 28BYJ-48
2. Steuerung und Report über serielle Schnittstelle
Keine Bedienelemente vorgesehen
Getriebe-Schrittmotor: 2048 Schritte pro Umlauf, > 1 kHz Schrittfrequenz
Internes Getriebe: 64:1 (d.h. der Motor hat 32 Pole)
Zusatzgetriebe: 80:16

Hardware (nach Ports sortiert; 44 Pins, 18 von 25 I/Os herausgeführt):
--  8	PB0	SS			(!LED RX)
15  9	PB1	SCL			Schrittmotor (rosa Ader, 2)
16 10	PB2	MOSI			Schrittmotor (orange Ader, 4)
14 11	PB3	MISO			Schrittmotor (gelbe Ader, 3)
 8 28	PB4			ADC11	Schrittmotor (blaue Ader, 1)
 9 29	PB5	OC1A	!OC4B	ADC12	Servomotor (linke Stiftleiste orange Ader)
10 30	PB6	OC1B	 OC4B	ADC13	Servomotor (rechte Stiftleiste orange Ader)
-- 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	frei

-- 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			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	Taktgenerator für Schrittmotor, variable Frequenz
1	PWM-Generator für Servomotoren, 50 Hz
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>		// strtol
#include <avr/pgmspace.h>	// PSTR
#include <avr/signature.h>
#include "usb.h"

// Diese beiden Konstanten müssen dem Matlab-Programm bekannt sein:
const int N=8*8*64*80L/16;	// Periodizität des Stellrades am Mikroskop (unter Einrechnung aller Zahnräder)
const int MAXSERVO=4400;	// Maximale (sinnvolle) Positionsangabe für Servo
// Außerdem, dass Index 0 = Servomotor vorn, Index 1 = Servomotor hinten, Index 2 = Schrittmotor ist.

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

static struct{
 unsigned pos[3];	// Position der beiden Servomotoren, 0 = linker Anschlag, MAXSERVO = rechter Anschlag
}eedata;	// und des Schrittmotors (0..N-1 weil umlaufender Antrieb)
static struct{
 char dir;	// Richtung der Drehung: ±1, wirkt nur auf eedata.pos[2]
 byte speed;	// Aktuelle Geschwindigkeit (Tabellenindex, 0..15)
 byte maxspeed;	// Maximale Verfahrgeschwindigkeit (bei Relativbewegung)
 word distance;	// (Verbleibende) Distanz für Bremsrampe
}step;
bool ee_dirty;

// Beschleunigungsrampe als Timer0-Endwerte im CTC-Modus mit Vorteiler 1024
// Werte ausprobieren für sin²-Kurve, je nachdem was der Motor kann
static const PROGMEM byte speedramp[]={
 255,192,128,100,75,60,50,45,41,37,35,32,31,30,29,28,27,27,26,25,24,23,22,21,21,20,20,19,19,18,18,17,17,17,
 16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12,
 11,11,11,11,11,11,11,11,11,11,10	// schneller geht's nicht (versuchsweise)
};

static void setcoilcurrent(byte pha) {
// byte pha=eedata.pos[2]&7;	// 4 Phasen bei Vollschrittbetrieb (8 bei Halbschritt)
 static const PROGMEM byte phatab[]={16,18,2,10,8,12,4,20};	// in der Reihenfolge der Adern
 PORTB=PORTB&~0x1E|pgm_read_byte(phatab+pha);	// Neue Bestromung setzen
}

//Interruptroutine (variable Frequenz): Schrittmotor bewegen
ISR(TIMER0_COMPA_vect) {
 if (step.distance) {
  if (PORTB&0x1E) {	// schon bestromt?
   int p=eedata.pos[2]+step.dir;
   setcoilcurrent(p&7);	// sofort umschalten, berechnungsbedingtes Jitter vermeiden
   if (p<0) p+=N; else if (p>=N) p-=N;	// in Vollkreis einschränken
   eedata.pos[2]=p;
   byte maxspeed=--step.distance<step.maxspeed?step.distance:step.maxspeed;
   if (++step.speed>maxspeed) step.speed=maxspeed;
   OCR0A=pgm_read_byte(speedramp+step.speed);	// Neue Zeitdistanz setzen
  }else{
   setcoilcurrent(eedata.pos[2]&7);	// je nach Phasenlage bestromen
   PORTD&=~0x20;	// LED TX ein
  }
 }else if (PORTB&0x1E) { // noch bestromt?
  ee_dirty=true;
  PORTB&=~0x1E;		// Bestromung auflösen
  TCCR0B=0;		// Timer0 anhalten
  PORTD|=0x20;		// LED TX aus
 }
}

// 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;
}

void bootstart() __attribute__((naked,noreturn));
void bootstart() {
 TCCR1B=0;	// Servomotoren stillsetzen
 DDRB=0;
 DDRD=0;
 cli();
 WDTCSR=0x18;
 WDTCSR=0;	// Watchdog aus
 PRR0=0;
 PRR1=0;
 SMCR=0;
 PLLCSR=0;	// PLL deaktivieren
 USBCON=0x20;
 UHWCON=0;
 asm("jmp 0x7E00");	// Adresse von ubaboot
}

/***********************************************
 * 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  = 0x85;	// I²C, SPI, ADC aus
 PRR1  = 0x19;	// Timer4, Timer3, USART aus
 PORTB = 0x80;	// Pullups an Eingängen, Low an Ausgängen
 DDRB  = 0x7F;	// 6 Ausgänge + LED RX (permanent ein)
 PORTC = 0xFF;
 PORTD = 0xFF;
 PORTF = 0xFF;
 DDRD  = 0x20;	// PD5 = LED TX (zeigt Schrittmotor-in-Bewegung an)
// DDRE  = 0x44;	// PE2 / !HWB = low
 ACSR  = 0x80;	// Analogvergleicher deaktivieren
 OCR0A = 255;	// Voller Zählumfang
 TCCR0A= 0x02;	// CTC-Modus
 TIMSK0= 0x02;	// mit Interrupt
 OCR1A = eedata.pos[0]+MINPWM;	// Minimaler Wert = 1 ms = 2000, maximaler Wert = 2 ms = 4000
 OCR1B = eedata.pos[1]+MINPWM;
 ICR1  = 40000-1;
 TCCR1A= 0xA2;	// WGM-Modus: 14 = Fast PWM mit ICR1 als TOP-Wert
 TCCR1B= 0x1A;	// Vorteiler 8, Zählumfang 40000 = 20 ms = 50 Hz
 sei();
}

static void idle() {
 ee_dirty=ee_dirty && eeprom_update(&eedata,0,sizeof eedata);
  //sleep_cpu();
 wdt_reset();
 usbPoll();
}

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

void handleQuery() {
 byte err=1;	// Fehlerkode: 1 = falscher Befehl
 switch (query[0]) {
  case '*': {	// "*IDN?"
   answerlen=sprintf_P(answer,PSTR("Raman-Spektrometersteuerung RA 190514 Haftmann\r\n"));
   usbSend();
  }return;
  case '?': {	// alle 3 Positionen abfragen
   answerlen=0;
   for (byte c=0; c<3; c++) {
    answerlen+=sprintf_P(answer+answerlen,PSTR("%u,"),eedata.pos[c]);
   }
   answer[answerlen-1]='\r';	// letztes Komma überschreiben
   answer[answerlen++]='\n';	// DOS-Zeilenende generieren
   usbSend();			// abschicken
  }return;
  case 'B': {		// "BL" = Urlader anspringen (für Firmware-Update)
   if (query[1]=='L') bootstart();
  }break;
  case 'H': {	// Schrittmotor (vorzeitig) anhalten = Nothalt
   TCCR0B=0;		// Timer0 anhalten
   PORTB&=~0x1E;	// Motor stromlos machen
  }return;
  case 'Z': {	// Schrittmotorposition (hier) nullsetzen
   eedata.pos[2]=0;
   ee_dirty=true;
  }return;
  case 'W': {	// Warte bis Schrittmotor fertig, dann sende Antwort
   while(PORTB&0x1E) idle();	// warten solange Schrittmotor bestromt (modale Schleife, kann Rekursion verursachen!)
   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>=3) {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
   if (i<2) {
//Servomotoren
    if (rel) p+=eedata.pos[i];
    if ((word)p>(word)MAXSERVO) {err=5;break;} // Fehler: Unzulässige Position
    eedata.pos[i]=p;	//Speichern
    p+=MINPWM;
    if (i) OCR1B=p; else OCR1A=p; // Neue Position ausgebebn
    ee_dirty=true;
    return;		// Erfolg ohne Antwort
   }
//Schrittmotor
   char dir=0;		// Optionale Drehrichtungsangabe
   byte speed=255;
   if (rel) {
    dir=p<0?-1:+1;	// Im Relativmodus ist jede Distanz zulässig, ggf. mehrere Umdrehungen vollführen
// Weiterer Parameter legt Maximalgeschwindigkeit fest, 0 = Minimum = 61 Hz pro Halbschritt
    if (*e==',') speed=(byte)strtol(e+1,NULL,0);
// Unendliches Drehen (±INF) ist vorerst nicht implementiert!
   }else{
    if (p>=N) {err=5;break;}	// Fehler: Unzulässige Position
    p-=eedata.pos[2];	// p ab hier gleich Distanz
    if (*e==',') dir=(char)strtol(e+1,NULL,0);
    if (dir<-1) {speed=-dir; dir=-1;}		// Bereich einschränken; Speed-Minimum = 2
    if (dir>1) {speed=dir; dir=1;}
    if (!dir) {	// Ohne Richtungsangabe Richtung aus kürzestem Weg berechnen (Um die Litfaßsäule herum)
     if (p>=N/2) {dir=-1; p-=N;}	// dist negativ machen
     else if (p>=0) dir=+1;
     else if (p>=-N/2) dir=-1;
     else {dir=+1; p+=N;}		// dist positiv machen
    // hier: |p| ≤ N/2 UND sgn(p) ≡ dir
    }
    if (dir>=0 && p<0) p+=N;	// langer Weg rechtsherum (nur bei Richtungsvorgabe möglich)
    if (dir<0 && p>=0) p-=N;	// langer Weg linksherum (nur bei Richtungsvorgabe möglich)
   }
   step.maxspeed=speed<sizeof speedramp-1?speed:sizeof speedramp-1;
   step.distance=abs(p);
   step.dir=dir;
   step.speed=0;	// Mit größtem Timerwert (f = 61 Hz) starten
   if (p) TCCR0B=0x05;	// Start Timer0 mit Vorteiler 1024 zum Bewegen des Schrittmotors per Interrupt
   return;		// okay ohne Rückmeldung
  }
 }
 answerlen=sprintf_P(answer,PSTR("Error %u\r\n"),err);
 usbSend();
}
Detected encoding: UTF-80