#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include "PMSM_tables.h"
#include <avr/fuse.h>
#include <avr/eeprom.h>
/* Hardware entsprechend AVR449
1 PB0 !OC1A UL Gate-Treiber U High-Side
2 PB1 OC1A UH Gate-Treiber U Low-Side
3 PB2 !OC1B VL Gate-Treiber V High-Side
4 PB3 OC1B VL Gate-Treiber V Low-Side
5 Ucc 5 V
6 GND
7 PB4 !OC1D WL Gate-Treiber W High-Side
8 PB5 OC1D WL Gate-Treiber W Low-Side
9 PB6 L/T V40511 transparent und dunkelgetastet wenn Low, Latch und aktiv wenn High
10 PB7 RESET Debug-Ausgang
11 PA7 AIN1 Shunt zur Strombegrenzung
12 PA6 D3 Datenbus
13 PA5 D2 Datenbus, Inkrementalgeber-Drehung (Low)
14 PA4 D1 Datenbus, Inkrementalgeber-Drehung (Low)
15 AUcc 5 V
16 AGND
17 PA3 D0 Datenbus, Inkrementalgeber-Taste (Low)
18 PA2 H3 Positionssignal vom Hallsensor
19 PA1 H2 Positionssignal vom Hallsensor
20 PA0 H1 Positionssignal vom Hallsensor
U,V,W = gängige Bezeichnung für Anschlüsse eines Dreiphasenmotors
Der 4-Bit-Datenbus füttert das Latch des Siebensegmentdekoders,
steuert die Anzeige-Stellen und fragt die 3 Kontakte des Inkrementalgebers ab.
Die vierstellige Siebensegmentanzeige hat gemeinsame Katoden.
Der V40511 entspricht CD4511 mit Hexadezimalausgabe.
Diese wird nur für die Ausgabe von " OFF" benutzt.
Dezimalpunkte werden nicht angesteuert.
*/
enum{
SOLL_MIN=30, // U/min
SOLL_MAX=1500
};
FUSES={
0b11000001, // HF PLL = 16 MHz, ungeteilt, schneller Hochlauf
0b01010100, // Kein RESET, EEPROM behalten, Brownout bei 4,3 V
0b11111111 // Kein SPM
};
typedef uint8_t byte;
typedef uint16_t word;
struct CFG{ // Persistente Daten
word soll; // Soll-Drehzahl in U/min, für Display
byte press; // Gezählte Tastendrücke, Run = Bit 1, Richtung = Bit 2
}cfg;
byte t0h; // Softwarezähler für Timer0, Bit 1:0 = aktuell angezeigtes Digit
byte nibbles[4]; // Auszugebende 4-Bit-Werte, von rechts nach links; Bit 4 = 1: Dunkel
byte blink; // !=0 wenn ganze Anzeige blinkt (Anzeige Sollwert), Abwärtszähler
byte igstate; // Bit 5 = Inkrementalgeber-Druckknopf-Schaltzustand
char igdiff; // vzb. Inkrementalgeber-Verdrehung, ±4 = 1 Rastung (bei diesem Geber)
word ist; // Ist-Drehzahl in U/min
// zur Drehzahlmessung
static volatile word puls; // Anzahl Impulse
static word tick; // Zeitpunkt des letzten Pulses der vorherigen Zeitnahme
static volatile word tack; // Zeitpunkt des letzten Pulses
EEMEM CFG eecfg/*={50,1}*/;
// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
static void eeprom_update() {
if (EECR&0x02) return;
const byte*src=(const byte*)&cfg;
byte*dst=(byte*)&eecfg, len=sizeof cfg;
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;
}
}
}
static void outOFF() { // wird (nur) beim Ausschalten des Rührers angezeigt
nibbles[0]=0x0F;
nibbles[1]=0x0F;
nibbles[2]=0;
nibbles[3]=0x10;
}
static void outErr() { // Überstrom-Fehler; " Err" kann nicht angezeigt werden
nibbles[0]=0x0E;
nibbles[1]=0x0E;
nibbles[2]=0x0E;
nibbles[3]=0x0E;
}
static void outdez(word n) {
if (n>9999) n=9999;
byte*p=nibbles;
do{
*p++=n%10U; // Dezimalzahl ausspucken
n/=10U;
}while(n);
while (p!=nibbles+4) *p++=0x10; // voreilende Stellen dunkeltasten
}
static word limit_soll(word soll) {
if ((int)soll<SOLL_MIN) soll=SOLL_MIN;
if ((int)soll>SOLL_MAX) soll=SOLL_MAX;
return soll;
}
static void outsoll() {
outdez(cfg.soll);
blink=2*30; // 2 s blinken
}
static void incsoll(char by) {
cfg.soll=limit_soll(cfg.soll+by);
outsoll();
}
// Aufruf erst mal mit lahmen 64 Hz
ISR(TIMER0_OVF_vect,ISR_NOBLOCK) {
PORTA = 0x7F; // Alle Katoden aus
PORTB&=~0x40; // V40511 dunkeltasten und transparent machen
DDRA = 0; // auf Einlesen schalten
byte k=++t0h&3;
byte n=nibbles[k]; // (Zeit vertun)
GPIOR0|=0x80; // Marker für Hauptprogramm
byte p=igstate; // Vorhergehenden Zustand merken
byte q=~PINA<<2; // Bit 7:6 = Drehzustand, Bit 5 = Taste (1 = gedrückt)
if (q&0x80) q^=0x40; // Graykode (00-01-11-10-00) in Binärkode (00-01-10-11-00) wandeln
// if ((p^q)&0x20) q|=0x10; // Veränderung des Tastenzustandes merken in Bit 4
igstate=q; // Als vorhergehenden Zustand speichern
igdiff+=(char((q&0xC0)-(p&0xC0)))>>6; // Verdrehung in Binärkodeschritten aufsummieren — das wird so prellfrei
DDRA = 0x78; // Datenbus auf Ausgabe
PORTA = byte((n&0x0F)<<3)|7; // neues Nibble ausgeben
k=n&0x10||blink&&t0h&0x20?0x7F:(~(8<<k))&0x7F; // Katode vorbereiten
PORTB|= 0x40; // V40511 Nibble übernehmen und (umgehend) helltasten
PORTA = k; // Katode aktivieren
}
register word sineTableIncrement asm("r10");
register word sineTableIndex asm("r8");
register word commutationTicks asm("r6");
register word amplitude asm("r4");
register byte sineTableNextSectorStart asm("r3");
register byte t1h asm("r2");
byte advanceCommutationSteps;
static void SpeedController(word speedReference) {
cli();
amplitude = speedReference;
sei();
}
static void DisablePWMOutputs() {DDRB=0xC0;PORTB|=0x15;}
static void EnablePWMOutputs() {DDRB=0xFF;}
static void TimerSetModeSinusoidal() {
//Set PWM pins to input (Hi-Z) while changing modes.
DisablePWMOutputs();
//Set Timer/counter1 in phase and frequency correct mode.
TCCR1A = 0xA3; // High-Side nicht steuern!!
TCCR1C = 0xA9;
TCCR1D = 0xE9; // Zentrierte PWM
OCR1D=OCR1B=OCR1A=TC1H=0;
GPIOR0|= 0x20;
GPIOR0&=~0x40; //WAVEFORM_SINUSOIDAL;
//Wait for the PWM cycle to complete.
TIFR = TIFR;
while (!(TIFR & 1<<TOV1));
//Change PWM pins to output again to allow PWM control.
EnablePWMOutputs();
}
static void TimerSetModeBlockComm() {
DisablePWMOutputs(); //Set PWM pins to input (Hi-Z) while changing modes.
TCCR1A= 0x03;
TCCR1C= 0x01;
TCCR1D= 0xE9;
OCR1D=OCR1B=OCR1A=TC1H=0; // Keine Bestromung
GPIOR0|= 0x20;
GPIOR0|= 0x40; //WAVEFORM_BLOCK_COMMUTATION;
EnablePWMOutputs();
}
static void TimerSetModeBrake() {
//Set PWM pins to input (Hi-Z) while changing modes.
DisablePWMOutputs();
//Set Timer/counter1 in phase and frequency correct mode.
//TCCR1A = 0x53;
//TCCR1C = 0x55;
//TCCR1D = 0xE9;
//All output duty cycles at 0 creates a braking force. (Low side braking)
OCR1D=OCR1B=OCR1A=TC1H=0;
GPIOR0&=~0x20;
GPIOR0|= 0x40; // WAVEFORM_BRAKING;
EnablePWMOutputs();
}
static void BlockCommutate(byte hall) {
byte t=pgm_read_byte((GPIOR0&WANTDIR_REVERSE
?blockCommutationTableReverse
:blockCommutationTableForward)+hall);
PORTB|=0x15; // keine High-Side-Treiber
TCCR1C=0x01; // keine Low-Side-Treiber
if (t&0x01) PORTB&=~0x01;
if (t&0x02) TCCR1C|=0x80;
if (t&0x04) PORTB&=~0x04;
if (t&0x08) TCCR1C|=0x20;
if (t&0x10) PORTB&=~0x10;
if (t&0x20) TCCR1C|=0x08;
}
static byte GetHall() { return PINA&7;}
static void DesiredDirectionUpdate() {
if (cfg.press&4) GPIOR0|=WANTDIR_REVERSE;
else GPIOR0&=~WANTDIR_REVERSE;
}
// Flanke zählen (in <puls>) und Zeitpunkt festhalten (in <tack>)
// Interrupts gesperrt bei Aufruf!
inline void countpuls() {
puls++;
union{
byte b[2];
word w;
}t={{TCNT0L,t0h}};
// Überlaufproblem droht! Ggf. 16-Bit-Zähler nutzen!
if (t.b[0]<0x80 && TIFR&2) t.b[1]++; // ISR nachbilden
tack=t.w;
}
static void ActualDirectionUpdate(byte lastHall, byte newHall) {
//Make sure that lastHall is within bounds of table. If not, set to an
//illegal hall value, but legal table index.
if (lastHall > 7) lastHall = 0;
if (pgm_read_byte(HallSequenceForward+lastHall) == newHall) {
GPIOR0&=~DIR_REVERSE;
GPIOR0|= DIR_KNOWN;
countpuls();
}else if (pgm_read_byte(HallSequenceReverse+lastHall) == newHall) {
GPIOR0|= DIR_REVERSE;
GPIOR0|= DIR_KNOWN;
countpuls();
}else{
GPIOR0&=~DIR_KNOWN;
GPIOR0&=~MOTOR_SYNCHRONIZED;
puls=0;
}
}
static word SineTableIncrementCalculate(word ticks) {
return word((SINE_TABLE_LENGTH/6 << 8) / ticks);
}
static void AdjustSineTableIndex(word increment) {
sineTableIndex += increment;
// If the table index is out of bounds, wrap the index around the table end
// to continue from the beginning. Also wrap the next sector start index.
if (sineTableIndex>>8 >= SINE_TABLE_LENGTH) {
sineTableIndex -= SINE_TABLE_LENGTH<<8;
sineTableNextSectorStart -= SINE_TABLE_LENGTH;
}
//Make copy of sineNextSectorStart to specify order of volatile access.
byte nextSectorStart = sineTableNextSectorStart;
if ((sineTableIndex >> 8) > nextSectorStart) {
sineTableIndex = (nextSectorStart << 8);
}
}
inline void CommutationTicksUpdate() {
if (commutationTicks < COMMUTATION_TICKS_STOPPED) commutationTicks++;
else{
GPIOR0|= MOTOR_STOPPED;
GPIOR0&=~MOTOR_SYNCHRONIZED;
sineTableIncrement = 0;
if ((GPIOR0&0x60)!=WF_BLOCK_COMMUTATION) {
TimerSetModeBlockComm();
BlockCommutate(GetHall());
}
}
}
inline void MotorSynchronizedUpdate() {
static uint8_t synchCount = 0;
if (GPIOR0&DIR_KNOWN
&& !((GPIOR0>>2^GPIOR0)&DIR_REVERSE) /*desiredDirection == actualDirection*/
&& !(GPIOR0&MOTOR_STOPPED)
&& !(GPIOR0&MOTOR_SYNCHRONIZED)) {
synchCount++;
if (synchCount >= SYNCHRONIZATION_COUNT) GPIOR0|=MOTOR_SYNCHRONIZED;
}else synchCount = 0;
}
inline byte getSine(byte index) {
//Im letzten Drittel 0 liefern
if (index>=128) return 0;
//Im zweiten Drittel gespiegelten Funktionswert liefern
if (index>=64) index = 127-index;
return pgm_read_byte(sineTable+index);
}
/*! Fast unsigned multiply of a 15 bit number with an 8 bit number with 15 bit result.
* 50 CPU clock cycles.
*/
inline word MultiplyUS15x8(const word m15, const byte m8) {
unsigned int result = 0;
if (m8 & 1<<0) result += m15; result >>= 1;
if (m8 & 1<<1) result += m15; result >>= 1;
if (m8 & 1<<2) result += m15; result >>= 1;
if (m8 & 1<<3) result += m15; result >>= 1;
if (m8 & 1<<4) result += m15; result >>= 1;
if (m8 & 1<<5) result += m15; result >>= 1;
if (m8 & 1<<6) result += m15; result >>= 1;
if (m8 & 1<<7) result += m15; result >>= 1;
return result;
}
inline void SineOutputUpdate() {
uint16_t temp;
uint8_t iU,iV,iW;
//Calculate U phase output duty cycle.
iU = (uint8_t)(sineTableIndex >> 8);
temp = getSine(iU);
if (temp) {
temp = MultiplyUS15x8(amplitude,temp);
PORTB|=0x01; // High-Side-Treiber ausschalten
}
TC1H = uint8_t(temp>>8);
OCR1A=uint8_t(temp);
if (!temp) PORTB&=~0x01; // High-Side-Treiber aktivieren
iV = iU+64; // Index für V
if (iV>=192) iV -= 192;
iW = iV+64; // Index für W
if (iW>=192) iW -= 192;
if (GPIOR0&WANTDIR_REVERSE) {iU=iV;iV=iW;iW=iU;}
temp = getSine(iV);
if (temp) {
temp = MultiplyUS15x8(amplitude,temp);
PORTB|=0x04;
}
TC1H = uint8_t(temp>>8);
OCR1B=uint8_t(temp);
if (!temp) PORTB&=~0x04;
temp = getSine(iW);
if (temp) {
temp = MultiplyUS15x8(amplitude,temp);
PORTB|=0x10;
}
TC1H = uint8_t(temp>>8);
OCR1D=uint8_t(temp);
if (!temp) PORTB&=~0x10;
}
/*! \brief Hall sensor change interrupt service routine.
*
* - Synchronize sine wave generation to hall sensors.
* - Block commutation.
* - Direction control.
* - Synchronization control.
* - Sine table increment calculation.
* - Stop detection.
*/
ISR(PCINT_vect) {
static uint8_t lastHall = 0xff;
uint8_t hall;
hall = GetHall();
if (hall == lastHall) return;
MotorSynchronizedUpdate();
if (((GPIOR0&0x60)!=WF_SINUSOIDAL) && GPIOR0&MOTOR_SYNCHRONIZED) {
TimerSetModeSinusoidal();
}
//If sinusoidal driving is used, synchronize sine wave generation to the
//current hall sensor value. Advance commutation (lead angle) is also
//added in the process.
switch (GPIOR0&0x60) {
case WF_SINUSOIDAL: {
uint16_t tempIndex;
tempIndex = (pgm_read_byte((GPIOR0&WANTDIR_REVERSE
?CSOffsetsReverse
:CSOffsetsForward)+hall)
+ advanceCommutationSteps) << 8;
sineTableIndex = tempIndex;
//Adjust next sector start index. It might be set to a value larger than
//SINE_TABLE_LENGTH at this point. This is adjusted in AdjustSineTableIndex
//and should not be done here, as it will cause problems when advance
//commutation is used.
sineTableNextSectorStart = (tempIndex>>8) + SINE_TABLE_LENGTH/6;
}break;
//If block commutation is used. Commutate according to hall signal.
case WF_BLOCK_COMMUTATION: {
BlockCommutate(hall);
}break;
}
//Update the actual direction flag.
ActualDirectionUpdate(lastHall, hall);
lastHall = hall;
//Calculate new step size for sine wave generation and reset commutation
//timer.
sineTableIncrement = SineTableIncrementCalculate(commutationTicks);
commutationTicks = 0;
//Since the hall sensors are changing, the motor can not be stopped.
GPIOR0&=~MOTOR_STOPPED;
}
/* 32 kHz, PWM-Ausgänge wurden aktualisiert
*
* The responsibilities of this ISR are:
* - Sine wave modulation update.
* - Direction command input.
* - Stop detection.
* - Speed controller timing.
*/
ISR(TIMER1_OVF_vect) {
PORTB|= 0x80;
switch (GPIOR0&0x60) {
case WF_SINUSOIDAL: {
AdjustSineTableIndex(sineTableIncrement);
SineOutputUpdate();
}break;
case WF_BLOCK_COMMUTATION: {
uint16_t blockCommutationDuty = amplitude * BLOCK_COMMUTATION_DUTY_MULTIPLIER;
if (blockCommutationDuty > 0x3FF) blockCommutationDuty = 0x3FF;
TC1H=byte(blockCommutationDuty>>8);
OCR1D=OCR1B=OCR1A=byte(blockCommutationDuty);
}break;
}
//Update desired direction flag.
DesiredDirectionUpdate();
static uint8_t lastDesiredDirection = 0xff;
if ((GPIOR0&WANTDIR_REVERSE)!=lastDesiredDirection) {
//Set motor in brake mode. The motor will automatically start once it is
//synchronized or stopped.
GPIOR0&=~MOTOR_SYNCHRONIZED;
if (GPIOR0&DIR_KNOWN)
TimerSetModeBrake(); // Automatically sets driveWaveform.
lastDesiredDirection = GPIOR0&WANTDIR_REVERSE;
}
CommutationTicksUpdate();
if (t1h) --t1h;
PORTB&=~0x80;
}
/*! \brief Hardware fault protection interrupt.
*
* This ISR will be run every time the hardware fault protection disables the
* PWM outputs. The required action to this event will be different from
* application to application. Here, a delay is inserted, before the PWM
* outputs are once again enabled.
*/
ISR(FAULT_PROTECTION_vect,ISR_NOBLOCK) {
outErr();
_delay_ms(1000);
TCCR1D |= 1<<FPEN1;
}
int main(void) {
PORTA = 0x7F; // überall Pullups außer an AIN1
PORTB = 0x15; // Invertierte Ausgänge standardmäßig High
DDRB = 0xC0;
eeprom_read_block(&cfg,&eecfg,sizeof cfg);
cfg.soll=limit_soll(cfg.soll);
cfg.press=cfg.press&~2|1; // Motor aus, Taste gelöst beim Einschalten
outdez(8888);
TCCR0B= 0x04; // Vorteiler 256, Überlauf 16 MHz/256/256 = 250 Hz
TIMSK = 0x06; // Überlaufinterrupts für beide Timer
ACSRA = 0x40; // 1,1 V am +, AIN1 an -: Fallende Flanke bei Überstrom
ACSRB = 0x80; // etwas Hysterese
while (!(PLLCSR & 1<<PLOCK)); //Warte bis PLL eingerastet
PLLCSR= 0x04; // Als Timer1-Taktquelle selektieren (64 MHz)
TCCR1B= 0x01; // Ohne Vorteiler
TCCR1D= 0xEC; // Überstromdetektor aktivieren via Analogvergleicher
// DT1 = DEAD_TIME<<4 | DEAD_TIME; // Beide Totzeiten einstellen
TC1H = 3;
OCR1C = 0xFF; // PWM top = 0x3FF, Überlauf bei 2×1023; ~ 32 kHz
PCMSK0= 0x07; // Pegelwechselinterrupt an Hall-Eingängen
PCMSK1= 0;
GIMSK = 0x20; // Pegelwechselinterrupt freigeben
DDRA = 0x07; // jetzt auslösen
DDRA = 0;
t1h = SPEED_CONTROLLER_TIME_BASE;
advanceCommutationSteps=0;
// Set motorStopped to FALSE at startup. This will make sure that the motor
// is not started if it is not really stopped. If it is stopped, this variable
// will quickly be updated.
// Set motorSyncronized to FALSE at startup. This will prevent the motor from being
// driven until the motor is in synch or stopped.
GPIOR0 = 0;
DesiredDirectionUpdate();
//Enable interrupts globally and let motor driver take over.
sei();
MCUCR=0x20; // Sleep Enable
_delay_ms(100); // 8888 anzeigen (Display-Test 0,1 s)
outsoll();
for (;;) {
sleep_cpu(); // Schlafen bis Interrupt: Timer1, Hall-Sensoren, Fehlereingang
if (GPIOR0&0x80) { // Timer0?
if (!(t0h&0x3F)) { // Alle 64 Überläufe, 3,81 Hz; 262144 µs (0x40000 µs)
cli();
word d=tack-tick; // Zeitdifferenz
tick=tack;
word n=puls; // Anzahl Flanken (12 pro Umlauf)
puls=0;
sei();
/* Berechnung der Istdrehzahl anhand der Flanken von den 3 Hall-Gebern, Terme:
n Anzahl detektierter Pulse in den 262 ms = 64*256 = 0x4000 Zählertakten
F_CPU Speisetakt für Timer0-Vorteiler
60 60 Sekunden pro Minute
256 Vorteiler Timer0
12 Flanken pro Umdrehung (Flanken aller 3 Hall-Geber)
() Dieser konstante Ausdruck ergibt 312500 = 0x4C4B4, eine 32-Bit-Zahl
d Zeit zwischen vergangenem letzten und jetzig letztem Impuls,
bei Synchronität 64*256 = 0x4000 ergebend
*/
if (n) n=n*uint32_t(F_CPU*60/256/12)/d;
ist=n;
if (!blink) outdez(ist);
}
if (!(t0h&7)) { // Inkremente in geringerer Frequenz (30 Hz) auswerten
char d=igdiff;
int s=cfg.soll;
if (d>=4) {
incsoll(s>200?s>500?s>1000?-50:-20:-10:-5);
d-=4;
igdiff=d;
}else if (d<=-4) {
incsoll(s>=200?s>=500?s>=1000?50:20:10:5);
d+=4;
igdiff=d;
}else{
if (blink&&!--blink) outdez(ist);
eeprom_update();
}
if (!((igstate>>5^cfg.press)&1) // Taste gedrückt oder losgelassen
&& !(++cfg.press&1)) { // Taste gedrückt
if (cfg.press&2) outsoll();
else outOFF();
blink=1*30; // Blinken nur 1 s
}
}
GPIOR0&=~0x80;
}
if (!t1h) {
t1h = SPEED_CONTROLLER_TIME_BASE;
SpeedController(cfg.press&2?cfg.soll:0);
}
}
}
Vorgefundene Kodierung: UTF-8 | 0
|