/* 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-8 | 0
|