/* 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();
}
Vorgefundene Kodierung: UTF-8 | 0
|