Hallo,
In een eerder topic ("Vreemd gedrag 16 bits berekening op AVR") heb ik een programma gepost waarmee ik via I2C een 3 fasen motor aanstuur. In verband met de variatie van pulslengte voor de motor heb ik ervoor gekozen om een 16 bits timer te gebruiken. Dit werkt perfect. Nu wil ik eigenlijk twee van deze motoren met een AVR aansturen die op verschillende toerentallen moeten kunnen draaien.
Helaas heeft de Attiny461 maar een 16 bit timer. En als de timer in 16 bits mode draait is er ook maar 1 output compare register. Heeft er iemand een idee of en hoe je toch met 1 timer en 1 compare register twee onafhankelijke frequenties kunt genereren? Of moet ik gewoon een andere (of tweede) AVR chip gebruiken?
Bedankt !!
Fredjuhh
"Ben nog een N00B, maar dat is al aan het veranderen ;)
FF kijken of deze noob kan helpen 
Met 16 bits timer bedoel je dus een timer die tot 65535 kan tellen?
Als je het verschil in frequentie weet, kun je daar toch een formule voor bedenken?
En dat e.v.t aan te vullen/gebruiken door een hulp byte die dient als geheugen?
Want rekenen kunnen die dingen volgens mij wel 
Misschien zeg ik wel wat stoms, maar de bedoeling is goed 
(E.e.a. gebaseerd op pic/proton/basic)
pros
Prosper, yop la boum, c'est le roi du macadam (aldus Maurice Chevalier)
Wat je ook kan doen, is een 8-bits timer zo instellen, dat hij enkel interrupts genereert. In de bijbehorende ISR voer je dan wat rekenwerk uit, en maak je uitgangspinnen hoog of laag (of de DDR, zoals in het voorbeeld). Een voorbeeldje (voor een ATmega88) dat een zestal verschillende audio-frequenties produceert:
volatile unsigned char F_cnt[6] = { 1, 20, 40, 80, 160, 0 };
volatile unsigned char F_set[6] = { 1, 20, 40, 80, 160, 0 };
void CTC_init(void)
{
TCNT0 = 0; // Timer/Counter 0
TCCR0A = _BV(WGM01);
TCCR0B = _BV(CS01); // Prescaler = 8
OCR0A = 40;
OCR0B = 2;
TIMSK0 = _BV(OCIE0A); // Enable interrupt on compare match
}
// Counter0 compare-match
ISR(TIMER0_COMPA_vect)
{
unsigned char cnt;
for (cnt = 0; cnt < 6; cnt++) {
F_cnt[cnt]--; // Teller verminderen
if (F_cnt[cnt] == 0) { // Staat hij op 0?
DDRC ^= _BV(cnt); // Pin-DDR toggelen
F_cnt[cnt] = F_set[cnt]; // En teller terug op zijn initiële waarde zetten
}
}
}
Wil je ook (of enkel) de puls/pauze-verhouding regelen, dan pas je de ISR-code aan.
Dit vergt uiteraard nogal wat processortijd. Een hoge systeem-klok is zeker aan te raden.
Fredjuhh
"Ben nog een N00B, maar dat is al aan het veranderen ;)
Ja pros, zoiets bedoelde ik ook ongeveer
en je kunt e.e.a. e.v.t. nog variabel maken met een pot,c'tje en weerstandje... Mits zo'n avr dat kan (verwacht ik wel), en er nog poorten over zijn 
Ok bedankt voor de ideeen. Dat zou kunnen, maar dan zou ik elke 3.2 uS (de hoogste resolutie die ik nodig heb) een interrupt moeten laten genereren. En in die 3.2 uS moet ik dan ook nog wat rekenwerk laten verrichten om te kijken of er output pins moeten veranderen. Kan wel, maar ik wil de AVR ook nog andere dingen laten doen (onder andere i2C afhandelen). Ik dacht dat er misschien een andere oplossing zou zijn waarbij er niet meer interrupts worden gegenereerd dan noodzakelijk (afhankelijk vna de gewenste puls lengte). Zie ook het eerder genoemde topic, daar heb ik de code die een frequentie genereert gepost.
Als je wilt dat de motor niet gaat haperen moet je daarvoor interrupts gebruiken, die worden immmers op een vast tijdstip uitgevoerd. In je main kan je de dingen afhandelen die minder belangrijk zijn en eventueel onderbroken mogen worden (wanneer een interrupt afgehandeld wordt). Het voorbeeldje kan ook nog wel wat korter:
volatile unsigned char F_set[6] = { 1, 20, 40, 80, 160, 0 };
#define F_MAX 160
void CTC_init(void)
{
TCNT0 = 0; // Timer/Counter 0
TCCR0A = _BV(WGM01);
TCCR0B = _BV(CS01); // Prescaler = 8
OCR0A = 40;
OCR0B = 2;
TIMSK0 = _BV(OCIE0A); // Enable interrupt on compare match
}
// Counter0 compare-match
ISR(TIMER0_COMPA_vect)
{
static unsigned char counter;
unsigned char cnt;
for (cnt = 0; cnt < 6; cnt++) {
if (F_set[cnt] == counter) {
DDRC ^= _BV(cnt);
}
}
if(++counter > F_MAX) {
counter = 0;
}
}
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Even: Als je een Frequentie wilt maken, en liefst "lineair" in de frequentie, dan is het het makkelijkst om:
pnew = p + freq;
if (pnew < p) toggle ();
p = pnew;
op gezette tijden te doen.
Gebaseerd op jullie ideeen heb ik een 8-bits timer gebruikt. Als de motor op de hoogste snelheid loopt (rpm=255), dan moet de pulstijd 2.22ms zijn. Als rpm 254 is, dan moet de pulstijd 2.23mS worden, dus slechts 10uS langer. Daarom heb ik de prescaler op 1 gezet zodat de timer met een 20Mhz klok elke 12.8uS afloopt. Eigenlijk nog niet vaak genoeg dus, maar hiermee heb ik het even geprobeerd. Dat werkt wel, de motor loopt op de gewenste snelheid, maar de (USI) I2C communicatie in het hoofdprogramma loopt vast. Waarschijnlijk omdat er veel te veel interrupts moeten worden afgehandeld, en daardoor de timing van de I2C in de soep loopt.
Dit is de interrupt routine (stuurt nog maar 1 motor aan) die ik nu heb, en die dus elke 12.8 us wordt aangeroepen:
// Timer interrupt
ISR (TIMER0_OVF_vect)
{
static unsigned int pulseCount = 0;
if ( ++pulseCount == rpmi )
{
PORTA = state[curState++]; // Set the outputs to the next state
if (curState > 5)
curState = 0;
pulseCount = 0;
}
}
Ik zal er dus voor moeten zorgen dat ik veel minder interrupts krijg, en toch de gewenste klokpulsen (varierend van ongeveer 220mS...2.2mS, met een nauwkeurigheid van 9uS). Nog geen idee hoe ik dat op kan lossen. Misschien de output compare register interrupts gebruiken met een hogere prescaler?
pros
Prosper, yop la boum, c'est le roi du macadam (aldus Maurice Chevalier)
Je zou de ISR korter kunnen maken, door de proloog en de epiloog weg te laten en er wat assembly-code in te plaatsen ( zie "Manually defined ISRs" in de manual). Maar ook dan vrees ik dat je doel softwarematig moeilijk of niet haalbaar is...
Ik ben nu een andere weg ingeslagen, namelijk terug naar een 16-bits timer met een resolutie van 3.2uS. Ik wil nu in de interrupt routine bij gaan houden welke van de twee motoren het eerste toe is aan een state verandering, en de counter dan laden met die waarde. Van de motor waarbij de tijd tot de volgende verandering groter is verminder ik de tijd met die waarde. Als de interrupt optreedt bekijk ik opnieuw welke van de twee motoren het eerste toe is aan een state verandering, laadt de timer opnieuw etc. etc.
Vergt nog enig denkwerk (en een uitzondering, want het zou voor kunnen komen dat de motoren tegelijk van state moeten veranderen), maar ik wil het toch eens gaan proberen. Wordt vervolgd...
Ik begrijp nog steeds niet wat je wilt ... je schrijft eerst dat je een resolutie wilt van 10us en later van 9us, verder schrijf je dat je een bereik wilt van 2,2ms tot 220ms en dat je een RPM waarde hebt van 1 byte (0 tot 255) ...
Wat voor schaalverdeling wil je dan gebruiken? Een resolutie van 9us met 255 stappen geeft je een bereik van maximaal 2295us, circa 2,3 ms. Als je van 2,2 tot 220 ms in 255 stappen wilt doen kom je op een resolutie van 854 us.
Bovenstaande waarden zijn bij een linieare schaal, misschien wil je logaritmisch?
In je andere post schrijf je in het commentaar van je code dat de 16bits timer met een prescaler van 64 ingesteld wordt. Tussen de interrupts houd je dus max 60 (dacht ik) ticks over rekenen met 16 bits waarden kost je teveel, dan heb je geen ruimte meer over om andere dingen te doen.
Stel je timer in dat met OCR0A als top waarde en schuif je RPM rechtstreeks in het hoge register van OCR0A, het lage register gebruik je niet. Dan heb je een resolutie van 819us met een bereik van 0 tot circa 209 ms. Voordeel is dat je geen 16bits berekeningen hoeft te doen.
Op 10 december 2009 00:07:27 schreef _Danny_:
Ik begrijp nog steeds niet wat je wilt ... je schrijft eerst dat je een resolutie wilt van 10us en later van 9us, verder schrijf je dat je een bereik wilt van 2,2ms tot 220ms en dat je een RPM waarde hebt van 1 byte (0 tot 255) ...Wat voor schaalverdeling wil je dan gebruiken? Een resolutie van 9us met 255 stappen geeft je een bereik van maximaal 2295us, circa 2,3 ms. Als je van 2,2 tot 220 ms in 255 stappen wilt doen kom je op een resolutie van 854 us.
Bovenstaande waarden zijn bij een linieare schaal, misschien wil je logaritmisch?
Excuses dat het allemaal wat verwarrend overkomt. Laat ik het nog eens een keer proberen. De schaal van de meter is lineair en loopt van 0..4500. De frequentie waarmee de motor draait is evenredig met de schaal. Bij maximaal toerental loopt de 3-fasen motor 4500 omw. per minuut. Een minuut is zo'n beetje 60000mS, dus 1 omwenteling duurt 60000/4500 is 13.33mS. Omdat ik per omwenteling 6x de fasen moet veranderen, moet ik om de 13.3 / 6 = 2.22mS een verandering laten plaatsvinden.
De inkomende waarde die het toerental aan moet geven is een byte, van 0..255 dus. Elke stap komt dan overeen met 17.64 toeren op de schaal. Ik gebruik integers, dus 18, en dan gaat een rpm waarde van 250 dus 4500 toeren aangeven. Een waarde van 250 moet dus een pulsbreedte geven van 2.22mS. Neem nu eens een rpm van 1 minder (249 dus). Het aan te geven toerental is dan 4500-18=4482. De pulslengte daarvoor moet dan zijn (60000/4482)/6 = 2.23mS. Dit is ergens tussen 9 en 10uS meer dan bij een rpm waarde van 250, vandaar de benodigde hoge resolutie van de timer.
En dit wil ik dus dubbel uitvoeren zodat ik twee van die drie fasen motoren, die op verschillende rpms kunnen lopen, aan kan sturen met een AVR controller. Uitdaging toch?
Ik hoop dat het een beetje duidelijk is zo. Laat maar horen als er een betere oplossing voor is dan dat ik hierboven voorstel. Liefst met 8-bits ipv 16 bits variabelen....
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
int t, u;
unsigned char rpm;
t = u = 0;
while (1) {
t += rpm;
if (t > MAXT) {
t -= MAXT;
PORTA = states[curstate++];
if (curstate >= 6) curstate = 0;
}
u += rpm2;
if (u > MAXU) {
u -= MAXU;
PORTB = states[curstate2++];
if (curstate2 >= 6) curstate2 = 0;
}
udelay (1);
}
Of niet meer dan een microseconde zoeken naar de volgende waar de van rpm...
Als jij zegt dat je ongeveer iedere 9 us omhoog moet, dan moet je volgens mij met 1 microseconde per loop MAXT ongeveer 9*256 maken. Maar volgens mij zeg je dat ie iedere 2ms omhoog moet. Hmm. volgens mij mag je zelfs 9 uS zoek maken. Anyway, als je het zo in de main doet, dan is het enigszins afhankelijk van de calibratie, en kan je niet zomaar iets in de mainloop toevoegen. Dit kan je ook van een komt-vaak-interrupt laten afhangen. Als het maar vaker dan ongeveer 10kHz loopt komt het volgens mij helemaal goed.
Het kan dan best zijn dat ie niet de resolutie heeft om 10us later het volgende pulsje te geven als ie 1 /255e langzamer draait, het gemiddelde komt WEL precies 1/255e langzamer. En daar gaat het om. De flanken van de pulsjes zullen niet perfect allemaal op dezelfde interval komen, maar dat wordt mechanisch wel uitgemiddeld.
Bedankt Rew, dit is ook een idee. Dus niet met interrupts, maar gewoon in main. Dat zou ook mijn probleem met I2C oplossen dat ik nu heb. Ik had namelijk een op interrupt gebaseerde oplossing, maar als de interrupts komen als ie net I2C aan het afhandelen is dan loopt het fout. Ik zal eens kijken of dit gaat werken.
Rew,
Ik ben nog even naar de code aan het kijken die je voorstelt, maar die doet toch niet hetgeen ik nodig heb. Kijk nog eens even naar de formule: de pulstijd die geleverd moet worden bij voor waarde rpm moet gelijk zijn aan (60000/rpm)/6 mS.
[edit: berekeningen gecorrigeerd]
Een rpm waarde van 4500 (byte waarde=250) geeft dus 2.22mS
Een rpm waarde van 4482 (byte waarde=249) geeft dus 2.23mS
..
Een rpm waarde van 2250 (byte waarde=125) geeft dus 4.44mS
..
Een rpm waarde van 450 (byte waarde=25) geeft dus 22.2mS
De code die je voorstelt doet volgens mij dit:
Een byte waarde van 250: 9.18uS
Een byte waarde van 249: 9.25uS
..
Een byte waarde van 125: 18.4uS
..
Een byte waarde van 25: 92.1uS
Volgens mij moet maxT iets van 555555 zijn.
Als ik zoals je zegt niet 1, maar 9uS pauzeer, dan kan MAXT 9x zo klein worden, dus 61728. Ik zal eens wat gaan proberen...
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Maar... het is dus raar dat je i2c niet werkt als je interrupts aan hebt/gebruikt. Dat hoort niet.
Het mooiste zou zijn als je dit dus op een interrupt kan draaien die bijvoorbeeld iedere 10µs draait. Dan moet je de maxT wel weer even opnieuw uitrekenen.
Als je het zoals ik opgeschreven had in main zou doen, dan moet je dat opnieuw uitrekenen doen iedere keer als je de mainloop iets verandert....
De aansturing in de mainloop en dan ook i2c afhandelen wordt een heel complex geheel denk ik. Dan moet je de tijd die je kwijt zou kunnen zijn aan i2c gaan meerekenen in je aansturing ...
Volgens mij komt het er op neer dat je pulsen een meervoud van 2,2ms zijn, de tijd tussen de pulsen is omgekeerd evenredig aan RPM (Naar mate RPM lager wordt liggen de pulsen verder uit elkaar). Ik denk dat de makkelijkste oplossing is de 16bits timer te gebruiken waarbij je de top afregelt op 2,2ms, in de interrupt functie gebruik je een static teller die naar beneden telt. Als de teller kleiner of gelijk is aan RPM zet je de nieuwe state en reset je de teller.
Je kunt deze constructie uitbreiden voor twee motoren door de top te halveren en in de interrupt een vlag te gebruiken die iedere keer omgezet wordt (0 = motor 1, 1 = motor 2).
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Danny, je eerste opmerking is geheel terecht.
Daarna klopt er minder van: Zoals al eerder aangegeven, als de RPM /iets/ minder dan de maximale is, moeten er om de 2.208 ms pulsjes gegeven worden. Dat is maar 1/250e meer dan 2.2ms. Niet een veelvoud.
Ook dat "iedere keer omzetten" werkt niet. De twee motoren kunnen best verschillende toerentallen draaien, dus als de ene harder gaat dan de andere, kan het best zo zijn dat je 2 keer achter mekaar de ene een pulsje moet geven voordat je weer die andere een zetje geeft.
Op 13 december 2009 21:30:16 schreef rew:
Maar... het is dus raar dat je i2c niet werkt als je interrupts aan hebt/gebruikt. Dat hoort niet.
De I2C werkt wel, maar er gebeuren af en toe (kan soms wel een minuut duren) vreemde dingen op de bus als ik deze 'motor-slave' er aan heb hangen. Dat zie ik doordat de tijd die ik van een I2C klok uitlees ineens op bijv. 85-78-75 of zo komt te staan. Als ik de motor-slave er af haal, of dat ik de timer interrupts uitschakel dan gebeurt dit niet.
Let wel dat deze chip geen echte hardware I2C heeft. Ik gebruik een library gebruik die I2C implementeert mbv de USI interface. Het I2C protocol is mbv twee interrupts geimplementeerd. Na het opstarten is alleen de interrupt ge-enabled die de I2C start situatie detecteert. Als die interrupt optreedt, dan wordt de overflow interrupt ge-enabled die optreedt als er een byte is ontvangen (1e byte is dan het I2C adres). Als een I2C commando is afgehandeld, dan wordt de overflow interrupt weer ge-disabled, de start interrupt weer ge-enabled, en begint het feest weer opnieuw.
Elders op het forum las ik dat interrupts op AVRs elkaar niet kunnen onderbreken, en de handler dus helemaal wordt afgehandeld. De enige problemen die ik me voor kan stellen zijn de volgende:
1) De I2C start komt net als er een motor-timer interrupt wordt afgehandeld. De I2C start interrupt wordt daardoor later afgehandeld, en omdat de I2C master het tempo bepaald kan er misschien een synchronisatie probleem optreden.
2) Er komt een motor-timer interrupt tussen de I2C start en de I2C overflow interrupts. Ook dit zou misschien een syncronisatie probleem kunnen veroorzaken.
Wat ik heb geprobeerd is in de I2C start interrupt handler de motor-timer interrupts te disabelen, en pas weer aan te zetten als de I2C communicatie is afgerond (gebeurt 1 keer per 50mS). Dat hielp echter niet.
Toen kwam rew met het idee om de motor aansturing vanuit main te doen. Ik dacht toen dat de I2C wel goed zou gaan omdat ik geen motor timer interrupts meer nodig heb. Helaas, ook dan gaat het fout (terwijl het niet fout gaat als ik niks in main doe). Dus er is iets anders aan de hand. Ik moet denk ik met m'n logic analyzer aan de slag om uit te vinden wat er precies fout gaat. Ik moet dan wel zien te detecteren wanneer de tijd op de RTC fout is, en dan ervoor zorgen dat ik de voorafgaande I2C commnuciatie met de motor-slave gebufferd heb.
Als ik dat op heb kunnen lossen, dan moet ik dus op een of andere manier de tijd die het kost om de i2c interrupts af te handelen meenemen in de berekening van motor timing...
@rew:
Oeps ... inderdaad geen veelvoud van 2,22ms. De constante factor is (2,22*250)/rpm. De enige manier die ik zo kan bedenken dat goed in een timer te krijgen is de oplossing die in het andere topic genoemd is. Ik heb wel het idee dat het eenvoudiger moet kunnen maar ik krijg mijn vinger er niet achter.
@maxva
Ik zou geen interrupt gebruiken voor de i2c communicatie, je kunt volgens mij beter een (kleine) lag in je updates hebben dan een fout in je motorsturing. Ik zou daarom de motorsturing in interrupts doen en je i2c volledig in je main loop. De i2c communicatie wordt dan hoogstwaarschijnlijk wel eens, of misschien wel meer dan eens, onderbroken door interrupts maar dat mag/kan niets uitmaken. Voor de aansturing van twee motoren kan ik op dit moment niet anders dan een veel lagere kloksnelheid aanraden ... het probleem is volgens mij niet dat 'ie het niet bij kan benen maar dat de benodigde pauzes (tussen de motorpulsen) te groot zijn.
Nog een ander puntje over de i2c code uit je eerdere topic. Het lijkt alsof je een byte waarde vertaald naar een hex-string over i2c verstuurd, waarom? Gewoon de byte zelf versturen, dan hoef je ook geen hex conversie toe te passen.
Op 14 december 2009 23:39:57 schreef _Danny_:
Ik zou geen interrupt gebruiken voor de i2c communicatie, je kunt volgens mij beter een (kleine) lag in je updates hebben dan een fout in je motorsturing.
Dat zou betekenen dat ik het wiel opnieuw zou moeten uitvinden. Degene die de, op interrupts gebaseerde, I2C library heeft gemaakt heeft een mooi stuk code geschreven dat in principe goed werkt. Zou je denken dat dit zomaar zonder interrupts te implementeren valt. En dat dus puls-breedtes zomaar mogen varieren? Ik ken de I2C spec niet in detail, maar denk je dat een salve z'n acknowledge pulsen mag uitstellen todat het hem uitkomt, of de lengte ervan mag varieren als ie toevallig tussendoor even wat anders moet doen?
Op 14 december 2009 23:39:57 schreef _Danny_:
Nog een ander puntje over de i2c code uit je eerdere topic. Het lijkt alsof je een byte waarde vertaald naar een hex-string over i2c verstuurd, waarom? Gewoon de byte zelf versturen, dan hoef je ook geen hex conversie toe te passen.
Da's een goeie. De waardes die binnenkomen op de I2C master, komen via een RS232 interface. Omdat ik niet weet waar een commando begint had ik daarvoor een protocolletje bedacht met een STX aan het begin, gevolgd door een commando en dan de data. De data mag dan natuurlijk geen STX bevatten, en vandaar dat ik die in hex overstuur.
Om de een of andere reden dacht ik dat dat voor de I2C commnunicatie ook nodig was, maar dat is natuurlijk niet zo. Synchronisatie wordt daar verzorgd door de I2C start, en kan ik dus gewoon binary versturen in plaats van hex. Bedankt voor de tip!!!
Je hoeft het wiel zeker niet opnieuw uit te vinden, het probleem is dat het wiel dat je ebt niet de juiste is
Ik ken je huidige i2c code niet maar de USI hardware heeft ondersteuning die je een heel eind op weg helpen.
In de Attiny datasheet staat op pagina 132 een beschrijving van de stappen die uitgevoerd worden als er data binnenkomt (slave mode). Je kunt de huidige i2c functies gewoon gebruiken alleen laat je ze niet door een interrupt aanroepen maar roep je ze aan vanuit main als de bijbehorende vlaggen gezet zijn.