kan godt se at det bliver lidt mere besværligt end jeg troede.
Nu har du ihvertfald en mulighed for lidt hjælp herfra, så det skulle gerne lette det en del. ;)
Kan finde eksempler på servocontroller ved brug af ATTiny2313 og jeg kan nogenlunde c++ programering så jeg finder nok ud af det.
Hvis du kan C++, kan du også C. Der bruges ikke C++ i AVR, idét C++ er alt for kæmpestort til de små microcontrollere.
men kan jeg overføre principperne fra ATTiny2313 til 44'ern når de alle er 8 bit ?
Lige præcis. AVR er instruktions-sæt-typen. Dvs. de kan alle forstå samme assembler instruktioner.
Der er dog få variationer mellem nogle af chip-typerne, men de fleste ATtiny bruger samme instruktioner.
Nogle af IC'erne kan endda dele samme binære filer, men det er ikke altid tilfældet, for addresser på fx. I/O-porte kan være anderledes.
Skriver du koden i C, så vil du ikke behøve bekymre dig ret meget. Det hele checkes stort set når der compiles. Du skal bare sørge for at din CPU-frekvens i din Makefile er korrekt, og at din DEVICE er korrekt (fx. attiny44)
Nu må jeg selvfølgelig ikke glemme at Timere, ADC og PWM også sættes forskelligt op på mange microcontrollere.
Fx. har ATtiny2313 ingen ADC. Den har en comparator, hvilket er en mere skrabet 'fattigmands ADC'.
Jeg vil anbefale dig at lave 3-4 forskellige source-filer...
1: main.c + main.h
2: ADC.c + ADC.h
3: Timer.c + Timer.h
4: Servo.c + Servo.h
main.c kunne se nogenlunde sådan ud:
#include <avr/io.h>
#include "main.h"
#include "ADC.h"
#include "Timer.h"
#include "Servo.h"
void init()
{
cli(); /* disable all interrupts */
initADC();
initTimer();
initServo();
sei(); /* enable interrupts */
}
int main()
{
DDRA = 0xff; /* all outputs */
DDRB = 0xff; /* all outputs */
PORTA = 0xff; /* all high */
PORTB = 0xff; /* all high */
init();
while(1) /* loop forever; everything is handled by interrupts */
{
}
return(0);
}
...Så kunne de andre filer indeholde specifikke opgaver i deres område.
I ATtiny44's fulde datablad (http://www.atmel.com/dyn/resources/prod_documents/doc8183.pdf) kan du se hvordan Timer, ADC og PWM sættes op.
Tag én ting ad gangen, ellers bliver det for overvældende; dette er også grunden til jeg foreslår at dele filerne op i 4 bidder.
Aflæsning af ADC bør gøres via en funktion; dette vil jeg komme tilbage til senere.
En timer-interrupt funktion kunne se således ud (trigger på Timer-overflow):
/* Timer.h */
/* Accessor functions; you may find these useful... */
uint8_t getHz10000();
void setEvent1CountDown(uint16_t aCountDown);
void setEvent2CountDown(uint16_t aCountDown);
/* external functions; you must implement these... */
void handleEvent1();
void handleEvent2();
#include <avr/io.h>
#include <avr/interrupt.h>
static volatile uint16_t hz10000 = 0; /* *must* be volatile! */
static volatile uint8_t hz100 = 0;
static volatile uint8_t hz1 = 0;
static volatile uint8_t minutes = 0;
static volatile uint8_t hours = 0;
static volatile uint16_t event1CountDown = 0;
static volatile uint16_t event2CountDown = 0;
uint16_t getHz10000() /* accessor-function */
{
uint16_t result;
result = hz10000; /* read value */
if(0x00 == (result & 0xff)) /* value just wrapped, make sure we read it properly */
{
result = hz10000; /* read value again; this is the fastest way to do it safely */
}
return(result);
}
void setEvent1CountDown(uint16_t aCountDown)
{
event1CountDown = aCountDown;
}
void setEvent2CountDown(uint16_t aCountDown)
{
event2CountDown = aCountDown;
}
SIGNAL (SIG_OVERFLOW0)
{
static uint8_t hz100counter = 0;
static uint8_t hz1counter = 0;
static uint8_t minuteCounter = 0;
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.3 */
TCNT0 = 256 - ((uint8_t) ((F_CPU / 8) / 10000)); /* intterupt occurs 10000 times per second */
/* we get here 10000 times per second. */
hz10000++; /* increment counter */
/* let's have a 100 Hz counter, a 1 Hz counter and a 1-minute counter... */
if(0 == hz100counter--)
{
/* we get here 100 times per second. */
hz100counter = 100 - 1; /* restart countdown */
hz100++; /* increment counter */
if(0 == hz1counter--)
{
hz1counter = 100 - 1; /* restart countdown */
hz1++; /* increment counter */
if(0 == minuteCounter--)
{
/* we get here once every minute */
minuteCounter = 60 - 1; /* restart countdown */
minutes++; /* increment counter */
if(minutes >= 60)
{
minutes = 0;
hours++;
}
if(event1CountDown && 0 == event1CountDown) /* if nonzero, count down until it's zero */
{
handleEvent1();
}
if(event2CountDown && 0 == event2CountDown) /* if nonzero, count down until it's zero */
{
handleEvent2();
}
}
}
}
}
void initTimer()
{
cli(); /* disable interrupts if not already done */
/* clear our counter variables: */
hz10000 = 0;
hz100 = 0;
hz1 = 0;
minutes = 0;
hours = 0;
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
cbi(TIMSK0, TOIE0); /* clear 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
/* set up timer to run in normal mode: (WGM02:WGM01:WGM00 = 000 */
/* make timer use PRESCALER8: (CS02:CS01:CS00 = 000): */
/* ATtiny24/44/84 Preliminary, page 79; section 11.9.1 */
TCCR0A = (0 | (0 << COM0A1) | (0 << COM0A0) | (0 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (0 << WGM00));
/* ATtiny24/44/84 Preliminary, page 82; section 11.9.2 */
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00));
/* Clear any pending interrupts, so we don't get an accidental interrupt immediately after enabling interrupts... */
/* ATtiny24/44/84 Preliminary, page 84; section 11.9.7 */
sbi(TIFR0, TOV0); /* clear 'Timer OVerflow 0' bit in 'Timer Interrupt Flag Register 0' */
/* enable Timer OVerflow 0 interrupt: */
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
sbi(TIMSK0, TOIE0); /* set 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
}
-Ja, jeg har lavet kommentarerne på engelsk, for jeg kan ikke holde ud at der står danske kommentarer i engelsk kode. ;)
Timer-funktionen kalder her 2 funktioner, når event1CountDown og event2CountDown tæller ned til 0.
Hvis tællerne allerede er 0, foretages intet; men når fx. event1CountDown går fra værdi 1 til 0, vil handleEvent1() funktionen blive kaldt.
Denne funktion kan du så lave noget kode i, som bliver udført efter fx. 30 minutter når event1CountDown er sat til 30.
Der tillades så her helt op til 65535 minutter, hvilket er 45 dage. =)
Er dette 'for lang tid' eller 'for upræcist'; fx. hvis du vil have sekunder i stedet for minutter, kan du bare flytte de 2 funktions-kald med tilhørende 'if' op, så de står lige under 'hz1++';
Koden er ikke testet på en ATtiny44, der er muligvis nogle fejl, men grund-princippet virker; jeg bruger det ofte på andre ATtiny'er. Jeg har sådan set et par ATtiny44 på vej hertil, så jeg kommer nok snart til at afprøve det, da jeg skal bruge tilsvarende kode på en 44'er selv.
Her har jeg et opdateret diagram (farverne er bare for min egen skyld ha ha)
(http://img709.imageshack.us/img709/4903/servoh.jpg)
Må indrømme at jeg er rimelig rusten i kodning er næsten blank men kæmper mig der ud af. sender lige et eksempel op hvis du gider kommenterer om det er helt ude i skoven. (en meget simpel while, for og if procedure.
du skrev tidligere at jeg skulle dele det op i 4 men skal hele koden ikke ligge i en fil når der skal programeres ?
beklager hvis jeg er lidt tung på det område :-S
todo:
Setup servo hardware
set up potentiometer1 (pot1) reading zero placement. Convert to value betwene 0.8 to 1 ms high in a 20 ms cycle
set up potentiometer2(pot2) ml reading. Convert to value betwene 1 to 2.1 ms high in a 20 ms cycle
set up dipswitches for time (diptime) 4 possibilities converted to minute 1440- 720 - 360 - 120
setup dipswitches for repeats (diprep) 4 possibilities converted to value 1 -2 - 4 - 10
set up timer 0 for clock emulation (minutes)
set up timer1 for servo movement (pwm 20 ms) (16 bit timer da opløsningen ellers bliver alt for lille)
Variables:
Int servo_min = (calculation that converts input from pot1 to lenght of high pulse in a 20 ms cycle 0.8 to 1.1)
Int servo_max = (calculation that converts input from pot2 to lenght of high pulse in a 20 ms cycle 1 to 2.1)
Int dosering=(conversion of diptime to int 1440-720-360-120)
Char repeats = (conversion of diprep to int 1-2-4-10)
Pseudo:
int main()
{
Int servo_min;
Int servo_max;
Int dosering;
Char repeats;
Int servo_move=0; /* for movement controll and update
Int a=0; /* for repeat counter use
}
while timer0<=1440; /* one day count in minutes to minimize sync error
if (dosering = 120) /*Checks if dosing mode set by dip (repeats pr. Day)
{
If (timer0=120) /*start first dosing in a 12 times a day setup
{
For (a = repeats; a<=repeats) /*set counter for number of repeats set by dip
{
/* start dosing by moving servo from its zeropiont to the value set by pot1*/
for(servo_move = servo_min ; servo_move < servo_max ; servo_move +=0.1)
{
send servo pulse;
/*Checks if servo ras reached value set by pot1 starts to reverse servo to zero*/
if (servo_move==servo_max)
{
/*starts to reverse servo to zero*/
For (servo_move=servo_max; servo_move>servo_min; servo_move-=0.1)
{
send servo pulse;
}
A++ /* ads one to a and loops until a = repeats
}
}
}
}
}
If (timer0=240) and so on for the value 360, 480…1440 also the same if’s when dosering = 360, 720, 1440
Læser næsten alt hvad jeg kan finde men så er det i c og så er det pseudo og så er det i noget helt tredie som er helt sort så ved ikke hvor jeg skal begynde, men tygger videre på de tekster jeg kan finde :-D
Her har jeg et opdateret diagram...
Ser ikke så tosset ud. :)
Jeg vil dog anbefale følgende regler:
GND vender altid nedad, dvs. stregen er vandret, og ledningen går 'opad' fra stregen. Det skulle så ligne et 'T' der vender på hovedet. -Hvis GND ligner et retvendt 'T', vil man tro at den går op til VCC.
VCC vender altid opad, dvs. ligner et retvendt 'T'.
Signaler kan ligne et 'T' der ligger på siden. Bruges denne måde, skal signal-navnet altid skrives (på T'ets "top-side", dvs. er T'et drejet 90 grader med uret, vil signalet skulle stå på højre side).
Må indrømme at jeg er rimelig rusten i kodning er næsten blank men kæmper mig der ud af. sender lige et eksempel op hvis du gider kommenterer om det er helt ude i skoven. (en meget simpel while, for og if procedure.
Det skal nok komme tilbage..
[/quote]du skrev tidligere at jeg skulle dele det op i 4 men skal hele koden ikke ligge i en fil når der skal programeres ?[/quote]
Der er følgende trin når man laver kode til en microcontroller (de går vældig hurtigt, så man mærker det sådan set ikke:
1: compiling. Dette foretages normalt af gcc eller g++. Kan også foretages af compilere til andre sprog.
2: linking. Linkeren 'klistrer' binære filer sammen. Hvis du for eks. kalder rutinen 'test()' fra fil 1, så finder linkeren adressen på 'test' og sætter den ind, hvor referencen til 'test' var.
3: flashing (brænding) af chippen. Dette er noget, som avrdude tager sig af. Den kan klare stort set alle AVR chips.
Alt dette styres normalt af en file, der hedder 'Makefile'
-Men vær forsigtig, når du retter i denne fil, for der er forskel på betydningen af mellemrum og betydningen af tabuleringer (!)
I en Makefile, kan man definere en liste af filer, som linkeren skal lede efter interessante symboler i.
Ofte kalder man sin primære kildekode for 'main.c' (men kan også kalde den navnet på det færdige produkt). Kaldes den primære kildetekst 'main.c', vil den primære binære fil hedde 'main.o'.
Et sted i din Makefile står der således 'main.o', og dér kan du fx. skrive 'adc.o', 'timer.o', 'servo.o', osv.
Rettelser:
'Int' ændret til 'int16_t'.
'Char' ændret til 'int8_t'.
'Kommentar ...' ændret til '/* Kommentar ... */'
'while <udtryk>' ændret til 'while(<udtryk>)'
Derudover har jeg fjernet semikolon fra den første while, da programmet vil blive ved med at køre rundt dér, og aldrig komme ud derfra.
'while(timer0 <= 1440);' er det samme som 'while(timer0 <= 1440){}'
Jeg har følgende kodestil (bemærk rækkefølgen jeg nævner det i):
Ingen mellemrum mellem funktionsnavne og start-paranteser.
Ingen mellemrum mellem start-parantes og første parameter.
Ingen mellemrum mellem parameter og komma.
Mellemrum mellem komma og næste parameter.
Ingen mellemrum mellem sidste parameter og slut-parantes.
Disse regler gælder alt, både funktions-definitioner, funktions-kald, 'if', 'while', 'for', 'switch' osv...
Eks:
if ( a==10&&b<=100 ) {
if( c>1000){
}
}
ændrer jeg til...
if(a == 10 && b <= 100)
{
if(c > 1000)
{
}
}
-Bemærk jeg sætter også krølleparantes på en tom linie, så de står i samme kolonne.
Denne kodestil bruges af rigtig mange; jeg lærte den da jeg arbedede sammen med et team på Opera Software i Norge.
'If' ændret til 'if'.
Hvis du bruger '=' inde i en if-sætning...
if(a = 4)
{
/* denne kode vil altid udføres. */
/* a vil altid have værdien 4. */
}
I stedet, hvis du bruger '==' inde i en if-sætning...
if(a == 4)
{
/* denne kode vil udføres når a har værdien 4. */
}
-Så jeg har ændret 'if( ... = ...)' til 'if( ... == ...)'
'For (' er ændret til 'for('
'send servo pulse;' er ændret til 'send_servo_pulse();', dvs. jeg har lavet det til et funktions-kald.
'A++' er ændret til 'a++;', da der kendes forskel på store og små bogstaver, og semikolon skal afslutte denne linie.
'For (a = repeats; a<=repeats)' er ændret til 'for(a = repeats; a<=repeats;)' (har indsat et semikolon, da der skal være 3 statements).
Diverse 'ensartethed' er også indført...
/* Variables: */
int16_t servo_min = (calculation that converts input from pot1 to lenght of high pulse in a 20 ms cycle 0.8 to 1.1)
int16_t servo_max = (calculation that converts input from pot2 to lenght of high pulse in a 20 ms cycle 1 to 2.1)
int16_t dosering=(conversion of diptime to int 1440-720-360-120)
int8_t repeats = (conversion of diprep to int 1-2-4-10)
/* Pseudo: */
int main()
{
int16_t servo_min;
int16_t servo_max;
int16_t dosering;
int8_t repeats;
int16_t servo_move=0; /* For movement control and update */
int16_t a=0; /* for repeat counter use
while(1) /* Stay in this while-loop (never exit). */
{
while(timer0 <= 1440) /* One day count in minutes to minimize sync error */
{
if(dosering == 120) /* Checks if dosing mode set by dip (repeats pr. Day) */
{
if(timer0 == 120) /* Start first dosing in a 12 times a day setup */
{
for(a = repeats; a<=repeats;) /* Set counter for number of repeats set by dip */
{
/* Start dosing by moving servo from its zeropiont to the value set by pot1 */
for(servo_move = servo_min; servo_move < servo_max; servo_move += 0.1)
{
send_servo_pulse();
/* Checks if servo ras reached value set by pot1 starts to reverse servo to zero */
if(servo_move == servo_max)
{
/* Starts to reverse servo to zero */
for(servo_move = servo_max; servo_move > servo_min; servo_move -= 0.1)
{
send_servo_pulse();
}
a++; /* Adds one to a and loops until a = repeats
}
}
}
}
}
if(timer0 == 240) /* and so on for the value 360, 480…1440 also the same if’s when dosering = 360, 720, 1440 */
{
}
}
}
}
OK, nu, da jeg har rettet kodens udseende til... Du vil nok synes at koden vil opføre sig underligt.
Da microcontrolleren kører vældig hurtigt, vil du opdage at linien 'if(timer0 == 120)' køres mange gange og derved vil koden inde i denne if-sætning også køres mange gange, for timer0 tælles jo i minutter, og der er derfor masser af tid, til at gentage denne process.
Der er flere måder at løse det problem på. Mange vælger at lave en 'state-machine', men det har jeg altid syntes var noget rod at se på.
Du kan i stedet... vente på at timer0 skifter fra 120 til 121...
if(timer0 == 120)
{
/* Execute action, which happens when 120 minutes has passed */
for(a = repeats; a <= repeats;)
{
/* ...etc...*/
}
/* Wait until timer0 is no longer 120... */
while(timer0 == 120)
{
}
}
Kig også på 'for(a = repeats; a <= repeats;) sætningen, for den vil faktisk aldrig udføre denne kode, idét 'for' stopper så snart a er større end, eller har samme værdi som repeats.
Forslag:
for(a = 0; a < repeats; a++)
{
/* ...kode... */
/* fjern den kodelinie der hedder 'a++;' */
}
Derudover vil du også se noget andet, der vil drille...
1: Du har 2 stk. 'for(servo_move = ...)' Det er i orden, men den ene ligger inde i den anden.
2: Du venter på at 'servo_move' får værdien 'servo_max', og så snart den har nået 'servo_max', vil koden i 'for'-løkken ikke længere udføres. Derfor vil din 'if(servo_move == servo_max)' nok aldrig blive udført.
Forslag:
Lav en...
for(servo_move = servo_min; servo_move < servo_max; servo_move += 0.1)
{
send_servo_pulse();
}
...og efter den (dvs. udenfor), lav en...
for(servo_move = servo_max; servo_move > servo_min; servo_move -= 0.1)
{
send_servo_pulse();
}
...OK... Så er der en sidste ting...
servo_move er en int16_t, hvilket vil sige den er heltal.
Jeg anbefaler at du holder det i heltal, og derfor ændrer koden til ikke at bruge 0.1 (det bliver rundet ned til 0.0 alligevel; selv 0.999999 bliver rundet ned til 0.0).
Så koden kunne fx. se således ud:
for(servo_move = servo_min; servo_move < servo_max; servo_move++)
{
send_servo_pulse();
}
Hint: Du vil sikkert gerne kunne kende forskel på fremad og baglæns, mht. 'send_servo_pulse()'.
Læser næsten alt hvad jeg kan finde men så er det i c og så er det pseudo og så er det i noget helt tredie som er helt sort så ved ikke hvor jeg skal begynde, men tygger videre på de tekster jeg kan finde :-D
C kan du bruge til næsten alt. -Så jeg anbefaler C.
Uden C, kan du heller ikke C++. =)
Der er et andet alternativ, assembler, som der sjovt nok er mange begyndere, vælger at bruge, når de laver kode til microcontrollere.
Assembler har altid været noget, folk har undgået, fordi det 'lyder farligt og mystisk'.
-Men start bare med C, for det er lidt lettere at overføre til andre microcontrollere, også dem, som ikke er fra Atmel.
Mange tak :-[
Skal nok finde ud af det.
Vil arbejde videre med koden idag og håbe på at lyset pludselig viser sig he he
Har besluttet at bryde koden op i følgende dele:
main (initialisering)
timer_0 (hoved timer kontrol med funktionskald til repeats via if statements)
timer_1 (kaldes fra servo_move, 16 bit PWM)
repeats (antal repeats pr dosering og kalder funktion servo_move. Her kan jeg ikke bruge heltal da bevægelsen ligger i intervallet 0.8 til 2.1 hvor decimalerne bestemmer grader på servoen. Der skal til gengæld kun bruges 2 decimaler. Men kan jeg ikke f.eks få pot1 til at give en værdi fra 0-180 og så sætte f.eks 1=0.85, så slipper 44'ern for at skulle ud i et floating point regnestykke og så bare bruge 0.82ms high i 20 ms PWM cycle)
servo_move (styre servo movement)
Det spare mig for en del kode gentagelser.
ser det her rigtigt ud for repeats ? sveder og krydser mine fingre GG
Koden kræver fcpu 1 mhz så det er også en todo
/*
* repeat.c
*
* Created: 16-08-2011 01:45:43
* Author: jascore
*/
#include <avr/io.h>
int restart(void)
{
while(1)
{
int8_t repeats; /*number read from dip pos. 1 - 2 - 4 - 10*/
int8_t a; /*For counting number of repeats*/
uint16_t ElapsedSeconds;
/*todo conversion of dip pos. to int8_t repeats*/
void dosing() /*No value returned*/
{
for(a = 0; a < repeats; a++) /*count number of repeats*/
{
send_servo_pulse(); /*Calls function to move servo*/
TCCR1B |= ((1 << CS10) | (1 << CS11) | (1 << CS10)); /* Set up timer at prescaler8 (for timerdelay before next repeat*/
for (;;)
{
/* Check timer value in if statement, true when count matches 6 second*/
TCNT1 = 0; /* Reset timer value*/
if (TCNT1 >= 93744) /*Counts up to 6 sec.*/
{
}
}
}
if(a==repeats)
{
/*Timer for delay befor next pump starts*/
TCCR1B |= ((1 << CS10) | (1 << CS11) | (1 << CS11)); /* Set up timer at prescaler64*/
for (;;)
{
/* Check timer value in if statement, true when count matches 1 second*/
ElapsedSeconds = 0;
if (TCNT1 >= 15624)
{
TCNT1 = 0; /* Reset timer value*/
ElapsedSeconds++;
if (ElapsedSeconds >= 960) /* Check if 16 minute has elapsed*/
{
ElapsedSeconds = 0; /*Reset counter variable*/
DDRA=0b00000001
PORTA=0b00000001; /*Set I/0 PA0 (pin13) high sendt trigger to next pump to begin*/
}
}
}
}
}
}
}
De 6 sekunder pause jeg har inden næste repeat, vil jeg kunne erstatte dem med en returnvalue fra servo_move som giver besked om at servoen er tilbage i nul og klar til endnu en gentagelse? Det vil gøre systemet lidt sikre tror jeg at man ikke risikere at en gentagelse tager længere tid end forventet og der så startes en my inden første er færdig
Håber ikke jeg er heltt afsporet
her følger timer funktionen
/*
* timer_0.c
*
* Created: 16-08-2011 01:45:43
* Author: jascore
*/
#include <avr/io.h>
#include "repeate.h"
/*todo Convert dip pos. to int8_t = 1 or 2 or 3 or 4 */
int8_t dosing;
int main (void)
{
unsigned char ElapsedSeconds = 0; // Make a new counter variable and initialise to zero
unsigned char Elapsedminutes = 0;
DDRB |= (1 << 0); // Set LED as output
TCCR1B |= ((1 << CS10) | (1 << CS11) | (1 << CS11)); /* Set up timer at prescaler64*/
for (;;)
{
// Check timer value in if statement, true when count matches 1 second
if (TCNT1 >= 15624)
{
TCNT1 = 0; // Reset timer value
ElapsedSeconds++;
if (ElapsedSeconds >= 60) // Check if one minute has elapsed
{
Elapsedminutes++;
ElapsedSeconds = 0; // Reset counter variable
/*Dosing every 2nd hour*/
if(dosing == 1 && Elapsedminutes == 0)
{
repeate();
while(Elapsedminutes == 0)
{
}
}
if(dosing == 1 && Elapsedminutes == 120)
{
repeate();
while(Elapsedminutes == 120)
{
}
}
if(dosing == 1 && Elapsedminutes == 240)
{
repeate();
while(Elapsedminutes == 240)
{
}
}
if(dosing == 1 && Elapsedminutes == 360)
{
repeate();
while(Elapsedminutes == 360)
{
}
}
if(dosing == 1 && Elapsedminutes == 480)
{
repeate();
while(Elapsedminutes == 480)
{
}
}
if(dosing == 1 && Elapsedminutes == 600)
{
repeate();
while(Elapsedminutes == 600)
{
}
}
if(dosing == 1 && Elapsedminutes == 720)
{
repeate();
while(Elapsedminutes == 720)
{
}
}
if(dosing == 1 && Elapsedminutes == 840)
{
repeate();
while(Elapsedminutes == 840)
{
}
}
if(dosing == 1 && Elapsedminutes == 960)
{
repeate();
while(Elapsedminutes == 960)
{
}
}
if(dosing == 1 && Elapsedminutes == 1080)
{
repeate();
while(Elapsedminutes == 1080)
{
}
}
if(dosing == 1 && Elapsedminutes == 1200)
{
repeate();
while(Elapsedminutes == 1200)
{
}
}
if(dosing == 1 && Elapsedminutes == 1320)
{
repeate();
while(Elapsedminutes == 1320)
{
}
}
/* Dosing every 6th hour*/
if(dosing == 2 && Elapsedminutes == 0)
{
repeate();
while(Elapsedminutes == 0)
{
}
}
if(dosing == 2 && Elapsedminutes == 360)
{
repeate();
while(Elapsedminutes == 360)
{
}
}
if(dosing == 2 && Elapsedminutes == 720)
{
repeate();
while(Elapsedminutes == 720)
{
}
}
if(dosing == 2 && Elapsedminutes == 1080)
{
repeate();
while(Elapsedminutes == 1080)
{
}
}
/*Dosing every 12th hour*/
if(dosing == 3 && Elapsedminutes == 0)
{
repeate();
while(Elapsedminutes == 0)
{
}
}
if(dosing == 3 && Elapsedminutes == 720)
{
repeate();
while(Elapsedminutes == 720)
{
}
}
/*Dosing every 24th hour*/
if(dosing == 4 && Elapsedminutes == 0)
{
repeate();
while(Elapsedminutes == 0)
{
}
}
if(Elapsedminutes>=1440)
{
Elapsedminutes=0;
}
}
}
}
}
Jeg er meget i tvivl om jeg får sat prescaler rigtigt og har endnu ikke fundet ud af hvordan jeg sikre mig den løre 1 mhz
Det var vist en af de længere posts
Vil arbejde videre med koden idag og håbe på at lyset pludselig viser sig he he
Du skal næsten have en compiler, så du kan trykke på "compile" knappen, og se hvor der er fejl; compileren vil afsløre mange ting (men selvfølgelig ikke alt).
Jeg anbefaler WinAVR (http://winavr.sourceforge.net/download.html) med GCC og avrdude (http://www.nongnu.org/avrdude/) (jeg ved ikke om gcc skal downloades separat, eller om den er med i WinAVR; det er muligt at avrdude også er med i WinAVR.
Jeg venter stadig på microcontrollerne. Jeg blev lidt utålmodig, og ringede så til RS.
Det ser ud til at DHL har klokket lidt i det, for de sendte dem til Tyskland. Tyskerne fandt så ud af at de var sendt forkert, og rettede fejlen. Nu er de ankommet til Peru!
RS sender mig en ny pakke med det samme; det synes jeg er mægtig flinkt af dem.
Har besluttet at bryde koden op i følgende dele:
main (initialisering)
timer_0 (hoved timer kontrol med funktionskald til repeats via if statements)
timer_1 (kaldes fra servo_move, 16 bit PWM)
repeats (antal repeats pr dosering og kalder funktion servo_move. Her kan jeg ikke bruge heltal da bevægelsen ligger i intervallet 0.8 til 2.1 hvor decimalerne bestemmer grader på servoen.
Jojo, så går du bare fra 8 til 21 i stedet.
Det er den måde man undgår floating point.
Skal du bruge 'finere opløsning', så går du fra 80 til 210. Det kan endda stadig ligge i en byte. =)
Der skal til gengæld kun bruges 2 decimaler. Men kan jeg ikke f.eks få pot1 til at give en værdi fra 0-180 og så sætte f.eks 1=0.85, så slipper 44'ern for at skulle ud i et floating point regnestykke og så bare bruge 0.82ms high i 20 ms PWM cycle) servo_move (styre servo movement)
ADC'en afleverer en værdi til dig, der ligger på 0...255 (hvis du bruger 8-bit), eller 0...1023 (hvis du bruger 10-bit).
Der er forskellige måder du kan omdanne disse værdier til hvad du skal bruge.
Har du masser af program-plads, kan du bruge en tabel. Da du ikke har så meget program-plads, kan det være en bedre idé at regne det om, men lav kun udregningen én gang, hvis du kan komme til det; dvs. når potmetret har en ny værdi, laver du udregningen, og husker hvad den nye værdi er.
eks.
potmeterValue = getADC2Value(); /* aflæs ADC værdien for ADC-kanal 2 (eller hvilken kanal du nu har valgt) */
if(potmeterValue != oldPotmeterValue)
{
oldPotmeterValue = potmeterValue;
degrees = (potmeterValue * 180) >> 8; /* hvis 8-bit */
/* eller... */
degrees = (potmeterValue * 180) >> 10; /* hvis 10-bit */
}
Ovenstående udregning foretages så kun når der er ændring på potmetret. Vælg enten 8-bit linien, eller 10-bit linien, alt efter hvilken ADC opløsning du kører.
potmeterValue bør være en uint16_t, da getADC2Value() vil returnere en 16-bit værdi, uanset om du bruger 8-bit eller 10-bit ADC.
Således vil det være lettere for dig at ændre mellem 8-bit og 10-bit, uden at koden skal ændres.
Det spare mig for en del kode gentagelser.
Det er altid godt at gøre kode kort. Ofte bliver koden også hurtigere, nemmere at overskue, og der kan jo ikke være lige så mange fejl i en kort stump kode, som en kæmpe stor omgang kode. ;)
ser det her rigtigt ud for repeats ?
MMmjj-nej. ;)
Du har en funktion, der hedder 'restart'.
Inde i denne funktion, har du en funktion der hedder 'dosering'.
Ded får du kun bøvl ud af; i teorien burde det kunne lade sig gøre, men i praksis, vil compileren nok blive noget snerpet.
Derudover... Inde i while(1){ ... }, har du variable-deklarationer ('int8_t repeats', mm.).
Disse bør du lægge lige i toppen af din funktion; dvs. før 'while(1)'-linien i dette tilfælde.
Men din 'for(a = 0; a < repeats; a++)' er helt rigtig, hvis det var dét spørgsmålet gik ud på. :)
Derudover bruger du også binære tal; det er ikke sikkert at compileren vil godtage dette (men prøv da)..
0b11001001 er bedre at skrive som hexadecimal 0xc9.
I de fleste tilfælde er der variabel-navne for AVR's benforbindelser.
-så 0b00000001 vil nok i dette tilfælde være mere praktisk som...
DDRA = (1 << PA0); /* Pin PA0 is output, rest is input */
PORTA |= (1 << PA0); /* set PA0 high */
PORTA &= ~(1 << PA0); /* set PA0 low */
PINA |= (1 << PA0); /* toggle PA0 value, so it gets the oposite value of what it currently is */
Ovenstående sætter først PA0 til output, derefter er der 3 eksempler på hvordan man...
1: Tænder et ben uden at ændre de andre bens værdi.
2: Slukker et ben, uden at ændre de andre bens værdi.
3: Toggler værdien så den får modsat værdi, uden at ændre de andre bens værdi.
Du kan stadig sætte hele porten's værdi med...
-Da vil PA0 være tændt, mens PA1 til PA7 bliver slukket (hvis de er input, vil deres pull-up modstand blive slået fra).
De 6 sekunder pause jeg har inden næste repeat, vil jeg kunne erstatte dem med en returnvalue fra servo_move som giver besked om at servoen er tilbage i nul og klar til endnu en gentagelse? Det vil gøre systemet lidt sikre tror jeg at man ikke risikere at en gentagelse tager længere tid end forventet og der så startes en my inden første er færdig.
Håber ikke jeg er heltt afsporet
Nej, du kommer selvfølgelig tættere og tættere på; selvom der kommer fejl hist og her. Fejl vil du ikke kunne undgå.
Selv engang jeg skrev et program, der kun fyldte 9 linier, og jeg havde gennemlæst det, og var *sikker* på der ingen fejl var... Ja, så brokkede compileren sig. *nedtur*.
her følger timer funktionen
Ser ikke helt tosset ud.
Jeg har rodet lidt i koden, lavet nogle ændringer (forslag til hvordan du kan gøre det lidt kortere).
(Lige en lille bemærkning: 'for(;;)' er det samme som 'while(1)', så du ikke bliver i tvivl om du skal bruge den ene eller den anden).
For en god ordens skyld, bruger jeg altid /* Kommentarer */ i C-kode, mens // Kommentarer ryger i C++ kode.
-Det er ikke alle C-compilere der kan lide '//'.
Jeg vil gerne rette på disse ting, inden du får nogle vaner der ikke er til at rette op på... :)
Det er også en god idé at alle variabel-navne starter med lille forbogstav.
Typer, såsom strukturer er en god idé at have med stort forbogstav, fx.
typedef struct Rectangle Rectangle;
struct Rectangle
{
int16_t x;
int16_t y;
int16_t width;
int16_t height;
};
Ovenstående er en struktur, der indeholder 4 16-bit værdier. Man kan bruge den på følgende måde, hvis man har typedef linien med:
Rectangle myCanvas;
myCanvas.x = 10;
myCanvas.y = 20;
myCanvas.width = 75;
myCanvas.height = 53;
/*
* timer_0.c
*
* Created: 16-08-2011 01:45:43
* Author: jascore
*/
#include <avr/io.h>
#include "repeate.h"
/* todo Convert dip pos. to int8_t = 1 or 2 or 3 or 4 */
int8_t dosing;
int main (void)
{
unsigned char ElapsedSeconds = 0; /* Make a new counter variable and initialise to zero */
unsigned char Elapsedminutes = 0;
unsigned char frozenMinutes;
DDRB |= (1 << 0); /* Set LED as output */
TCCR1B |= ((1 << CS10) | (1 << CS11) | (1 << CS11)); /* Set up timer at prescaler64 */
for(;;)
{
/* Check timer value in if statement, true when count matches 1 second */
if(TCNT1 >= 15624)
{
TCNT1 = 0; /* Reset timer value */
ElapsedSeconds++;
if(ElapsedSeconds >= 60) /* Check if one minute has elapsed */
{
Elapsedminutes++;
ElapsedSeconds = 0; /* Reset counter variable */
frozenMinutes = Elapsedminutes;
if(dosing == 1) /* Dosing every 2nd hour */
{
switch(frozenMinutes)
{
case 0:
case 120:
case 240:
case 360:
case 480:
case 600:
case 720:
case 840:
case 960:
case 1080:
case 1200:
case 1320:
repeate();
break;
}
}
else if(dosing == 2) /* Dosing every 6th hour */
{
switch(frozenMinutes)
{
case 0:
case 360:
case 720:
case 1080:
repeate();
break;
}
}
else if(dosing == 3) /* Dosing every 12th hour */
{
switch(frozenMinutes)
{
case 0:
case 720:
case 0:
case 0:
repeate();
break;
}
}
else if(dosing == 4 && Elapsedminutes == 0) /* Dosing every 24th hour */
{
repeate();
}
while(Elapsedminutes == frozenMinutes) /* Wait until Elapsedminutes changes from the value we had above */
{
}
if(Elapsedminutes>=1440)
{
Elapsedminutes=0;
}
}
}
}
}
Jeg er meget i tvivl om jeg får sat prescaler rigtigt og har endnu ikke fundet ud af hvordan jeg sikre mig den løre 1 mhz
Det var vist en af de længere posts
På et eller andet tidspunkt, vil du kunne se en fejl ved koden som den er nu. Det er blevet lidt lettere at se fejlen, nu da koden er kortet ned (den gør nøjagtig det samme som tidligere).
Der er faktisk mere end én fejl.
Første fejl er:
Tallene over 255 (dvs. 360, 480, ... 1200, 1320) kan ikke være i en unsigned char.
Dér er du nødt til at gå op i en short.
Jeg anbefaler at bruge uint8_t i stedet for unsigned char og uint16_t i stedet for unsigned short; disse typer bruges normalt med AVR.
Men hvad værre er, er at programmet vil fryse fast.
Dette er fordi Elapsedminutes ikke ændrer sig mens du er i while-loopet.
Jeg anbefaler at du i stedet for at kigge på TCNT hele tiden, laver et interrupt (som nævnt for nogen tid siden); dette interrupt kan så tælle både minutter og sekunder for dig.
-Du kan evt. lave en nedtælling, og når tælleren når nul, kan timer-interruptet kalde en handlings-rutine, som udfører arbejdet.
Derfor vil det være muligt for dig at korte det yderligere ned. :)
Bare tag én ting ad gangen.
Det er en god idé at lave en prøve-kode først, som kun indeholder 'timer' kode, og intet andet.
Prøve-koden skal så bare blinke en lysdiode med 1 sekund tændt, 1 sekund slukket inde i timer-funktionen. :)
Når du har den til at gøre dette, så vil du se, at du nemt kan styre tiden rimelig nøjagtigt.
Brug meget gerne variablen F_CPU til at udregne hvor ofte timeren skal køre, derved kan du justere clock-frekvensen ét sted, og resten af din kode vil bruge denne værdi.
Indstillingen af F_CPU ligger så i din Makefile, så den variabel/definition vil være tilgængelig i alle dine filer. :)
PS: Jeg synes allerede på nuværende tidspunkt, at din kode-stil er god; bedre end mange på nettet. Det lønner sig senere, for hvis din kode så rodet ud, ville der kunne skjule sig mange fejl, og de ville tilmed være svære at finde. Pæn kode er lettere at finde/rette fejl i, og ofte bliver koden mere fejlfri, når den er pæn. Ensartethed er her et godt og vigtigt nøgleord.
Jeg faldt over at bruge CTC i timeren men er i tvivl om det vil konflikte med den 16 bit timer jeg skal bruge til at styre servoen.
Men prøvede alligevel :-D
Grunden til at jeg prøver at holde mig uden om det tcnt eksempel du kom med helt i starten er at jeg ikke syntes at kunne finde hoved og hale i det :o
her er mit eksempel på CTC :-X
#include <avr/io.h>
int8_t sec=0;
int8_t minutes=0;
int8_t dose; /*=2, 6, 12 eller 24 ( ser by pot1)*/
int8_t hours=dose; /*ER I TVIVL OM DEN HER. Det er for at dosere første gang med det samme*/
int main (void)
{
TCCR1B |= (1 << WGM12); /* Configure timer 1 for CTC mode*/
OCR1A = 15624; /* Set CTC compare value to 1Hz at 1MHz AVR clock, no prescale*/
TCCR1B |= ((1 << CS10)); /* Start timer at Fcpu/64*/
for (;;)
{
if (TIFR1 & (1 << OCF1A)) /*Check CTC flag*/
{
TIFR1 = (1 << OCF1A); /* clear the CTC flag*/
sec++; /*second counter*/
if (sec>=60)
{
sec=0;
minutes++; /*minute counter*/
}
if (minutes>=60)
{
minutes=0;
hours++; /*hour counter*/
}
if (hours==dose) /*checks if hour = dosing and then doses*/
{
/*repeats();*/ /*call function repeat*/
hours=0; /*resets hours*/
}
}
}
}
Noget kun kortere end den tidligere timer funktion
Jeg faldt over at bruge CTC i timeren men er i tvivl om det vil konflikte med den 16 bit timer jeg skal bruge til at styre servoen.
Hvis du er skrap, kan du bruge samme interrupt til begge ting, men det kan være en balancegang.
Jeg anbefaler at du bruger 8-bit timeren til at "tælle sekunder" med, da den alt rigeligt kan klare den opgave.
Ellers ser det ud til at din timing er rigtig i dit eksempel.
Bemærk at du kunne skrive...
OCR1A = (F_CPU / 64) - 1;
Og hvis du så sætter F_CPU til 1000000, skulle det gerne passe. Prøv at regne efter.
Ovennævnte kodestump vil compileren regne om til ét konstant tal, idét vi kun har konstanter. Dvs. compileren kan reducere udregningen for os, så der bliver ikke regnet run-time.
Grunden til at jeg prøver at holde mig uden om det tcnt eksempel du kom med helt i starten er at jeg ikke syntes at kunne finde hoved og hale i det :o
-Du er der alligevel næsten.
Det eneste du mangler at klistre på, er interruptet. :)
Jeg har lavet en ny omgang eksempel-kode. Det er nok lidt mere simpelt end det første jeg lavede; og koden skulle kunne håndtere fuld dosering på tid. ADC koden er ikke inkluderet.
Bemærk: Koden er slet ikke afprøvet, så der er sikkert masser af fejl. ;)
Prøv at starte med at kigge på timer interrupt funktionen (den hedder 'SIG_OVERFLOW0'). Du skulle kunne forstå det meste af denne. Den køres hver gang TCNT0 ændrer værdi fra 255 til 0; dette sker én gang i sekundet.
Interrupt funktionen kalder en rutine ('timer0Elapsed'), som ligger i Dosing.c; dette sker når minut-nedtællingen (minuteCountdown) ændres fra 1 til 0.
/*
* File: Timer0.h
*
* (8-bit timer with simple event handling)
*
*/
#ifndef __Timer0_h__
#define __Timer0_h__
void setMinuteCountdown(uint16_t aMinuteCountdown); /* this routine controls how often the interrupt will occur */
void timer0Elapsed(); /* this is a routine that you implement */
void initTimer0();
#endif /* __Timer0_h__ */
/*
* File: Timer0.c
*
* (8-bit timer with simple event handling)
*
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include "Timer0.h"
static volatile uint8_t seconds = 0; /* variables that are changed by an interrupt, *MUST, MUST* be volatile! */
static volatile uint16_t minuteCountdown = 0; /* variables that are changed by an interrupt, *MUST, MUST* be volatile! */
static uint16_t minuteCountdownRestart = 0;
void setMinuteCountdown(uint16_t aMinuteCountdown)
{
cli(); /* make sure interrupt is not occurring while writing the 16-bit value */
minuteCountdown = aMinuteCountdown; /* (that could cause an incorrect reading by the interrupt) */
minuteCountdownRestart = aMinuteCountdown; /* set our restart-value as well */
sei();
}
SIGNAL (SIG_OVERFLOW0) /* This interrupt occurs when TCNT0 wraps to 0 */
{
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.3 */
TCNT0 = 256 - ((uint8_t) ((F_CPU / 64) / 125)); /* interrupt occurs once per second. */
seconds++; /* increment our number of seconds elapsed */
if(seconds >= 60) /* when we've reached 60 seconds */
{
seconds = 0; /* wrap to zero */
if(minuteCountdown) /* is our minuteCountdown nonzero ? */
{
if(0 == --minuteCountdown) /* if minuteCountdown reaches 0 when it counts down (eg going from 1 to 0) */
{
minuteCountdown = minuteCountdownRestart; /* restart countdown */
timer0Elapsed(); /* it's time for applying another dose */
}
}
}
}
void initTimer0()
{
cli(); /* disable interrupts if not already done */
seconds = 0;
minuteCountdown = 0;
minuteCountdownRestart = 0;
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
TIMSK0 &= (1 << TOIE0); /* clear 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
/* set up timer to run in normal mode: (WGM02:WGM01:WGM00 = 000 */
/* make timer use PRESCALER8: (CS02:CS01:CS00 = 000): */
/* ATtiny24/44/84 Preliminary, page 79; section 11.9.1 */
TCCR0A = (0 | (0 << COM0A1) | (0 << COM0A0) | (0 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (0 << WGM00));
/* ATtiny24/44/84 Preliminary, page 82; section 11.9.2 */
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00));
/* Clear any pending interrupts, so we don't get an accidental interrupt immediately after enabling interrupts... */
/* ATtiny24/44/84 Preliminary, page 84; section 11.9.7 */
TIFR0 |= (1 << TOV0); /* clear 'Timer OVerflow 0' bit in 'Timer Interrupt Flag Register 0' */
/* enable Timer OVerflow 0 interrupt: */
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
TIMSK0 |= (1 << TOIE0); /* set 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
}
/*
* File: ADC.h
*
* (Controls when the dosing occurs, but also holds the code to do the actual dosing)
*
*/
#ifndef __ADC_h__
#define __ADC_h__
uint16_t getADC3Value(); /* this routine returns the low 10 bits of the ADC3's value, no matter if you use 8-bit or 10-bit ADC */
void initADC(uint8_t aBits);
#endif /* __ADC_h__ */
/*
* File: Dosing.h
*
* (Controls when the dosing occurs, but also holds the code to do the actual dosing)
*
*/
#ifndef __Dosing_h__
#define __Dosing_h__
void startDosing(); /* this routine should be called when the 'start' button is pressed */
void setDosing(int8_t aDosing); /* usually you wouldn't call this routine (call startDosing instead) */
void initDosing();
#endif /* __Dosing_h__ */
/*
* File: Dosing.c
*
* (Controls when the dosing occurs, but also holds the code to do the actual dosing)
*
*/
#include <avr/io.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#define ADC3_CALIBRATION 0 /* I'll let you know later, how to find this calibration value */
void setDosing(int8_t aDosing)
{
switch(aDosing)
{
case 1:
setMinuteCountdown(2 * 60); /* every 2nd hour */
break;
case 2:
setMinuteCountdown(6 * 60); /* every 6th hour */
break;
case 3:
setMinuteCountdown(12 * 60); /* every 12th hour */
break;
case 4:
setMinuteCountdown(24 * 60); /* every 24th hour */
break;
}
}
void startDosing() /* this routine should be called when the 'start' button is pressed */
{
int16_t adcValue;
int8_t dosing;
adcValue = getADC3Value(); /* read value of ADC channel 3 */
adcValue = adcValue + ADC3_CALIBRATION; /* add calibration value */
dosing = ((adcValue * 4) >> 10) + 1; /* calculate the dosing (we'll get a value from 1 to 4) */
setDosing(dosing); /* when this routine is called, the dosing countdown starts */
}
void timer0Elapsed()
{
repeate(); /* well, the code from inside repeate could go directly here, if desired */
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
/*
* File: main.c
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include "main.h"
#include "ADC.h"
#include "Timer0.h"
#include "Dosing.h"
int main()
{
initADC(8); /* initialize ADC, we'll use the 8-bit precision for now */
initTimer0();
initDosing();
sei(); /* globally enable interrupts */
while(1)
{
/* we don't really need to do anything here. Everything is handled by the interrupts */
}
return(0); /* (never reached) */
}
-Måske er det lidt mere forståeligt, når det er kortere... Bemærk: Det jeg gør, er at jeg ikke tæller minutterne 'opad', men i stedet sætter et startpunkt og tæller nedad derfra. Når minutterne så når 0, så startes doseringen, og minut-tælleren sættes tilbage til dens start-værdi.
Jo ok tror jeg har forstået koden nu.
Inde i sig_overflow0, som trigger på en countdown) kalder jeg så rutinen repeats hvor gentagelser bliver defineret og brugt i ADC.c
aMinuteCountdown bliver defineret i dosing's set_dosing fouktionen og brugt i sig_overflow0, og det hele med en 8 bit timer/counter så tror sq jeg har forstået det nu - næsten
Lavede selv main.h udfra hvordan jeg kunne se at du havde lavet de andre header filer
#ifndef __MAIN_H__
#define __MAIN_H__
void initTimer0();
void initDosing();
#endif /* MAIN_H_ */
Men her ville jeg jo også gerne have haft void initADC( 8 ); med men det kan jeg jo ikke, da den allerede er som void initADC(uint8_t aBits) i ADC.h
Gør jeg det alligevel bliver de conflicting. Gør jeg det ikke bliver initADC( 8 ) undefined (ved godt der er mellemrum i ( 8 ) men det er for at undgå ham her (8)
det driller mig lidt at gennemskue.
Er lidt træt nu så må hellere holde en pause.
Inde i sig_overflow0, som trigger på en countdown) kalder jeg så rutinen repeats hvor gentagelser bliver defineret og brugt i ADC.c
ADC.c skal sådan set kun aflæse ADC'en. Der vil være en rutine, der hedder getADC3Value(), som returnerer værdien af ADC'en til den kaldende rutine. Noget a'la...
/*
* File : ADC.c
*
*/
#include <avr/io.h>
#include "ADC.h"
static uint8_t adcIndex = 0;
static uint16_t adc3Value[2];
static uint16_t adc2Value[2];
uint16_t getADC3Value()
{
return(adc3Value[adcIndex]);
}
void initADC(uint8_t aBits)
{
/* not yet implemented */
}
-Ovenstående er stort set tomt, og det er fordi jeg ikke er gået i dybden med den endnu.
For at den skal aflevere et rimeligt stabilt tal til dig, bør den aflæse - lad os sige - 4 gange, og aflevere gennemsnittet af disse 4 værdier til dig. Jeg vil kigge lidt videre på dette.
aMinuteCountdown bliver defineret i dosing's set_dosing fouktionen og brugt i sig_overflow0, og det hele med en 8 bit timer/counter så tror sq jeg har forstået det nu - næsten
-Ja, aMinuteCountdown er en parameter (et argument), som bliver givet, når setMinuteCountdown funktionen kaldes. a'et i aMinuteCountdown står for "argument", hvilket vil sige, at man så nemt kan se, at det er en variabel der kommer som argument.
Lavede selv main.h udfra hvordan jeg kunne se at du havde lavet de andre header filer
initDosing er defineret i Dosing.h, initTimer0 er defineret i Timer0.h, så main.h burde se således ud:
#ifndef __main_h__
#define __main_h__
#endif /* _main_h_ */
-Altså ganske tom indtil videre.
Men her ville jeg jo også gerne have haft void initADC( 8 ); med men det kan jeg jo ikke, da den allerede er som void initADC(uint8_t aBits) i ADC.h
Gør jeg det alligevel bliver de conflicting. Gør jeg det ikke bliver initADC( 8 ) undefined.
Grunden til at initADC er undefined, er at du ikke har en ADC.c. Prøv at lægge ovennævnte ADC.c ind (som test), så tror jeg fejlen skulle forsvinde.
Bemærk: Det er linkeren der brokker sig over at initADC mangler.
...Interrupts er smarte; de kan spare mange CPU-kræfter, det er derfor jeg gerne vil have dig til at lære dem. Nogle ting kan drille ved interrupts, men det vil du nok ikke opleve lige med det første, og på en microcontroller bliver den slags fejl ikke så katastrofale som på en computer.
Vil det her virke som repeats ?
(en del af koden i dosing.c
void timer0Elapsed()
{
for(a = 0; a <= repeats; a++)
{
for(Is looking for a way to implement seconds from the timer rutine to make the for last for 10 seconds befor continue)
{
rotation = adc3Value; /*TOP, (20ms) load*/
ServoMove()
}
for(Is looking for a way to implement seconds from the timer rutine to make the for last for 10 seconds befor continue)
{
rotation = adc2Value; /*TOP, (20ms) unload*/
ServoMove()
}
}
}
void ServoMove()
{
TCCR1A = 0; /* disable all PWM on Timer1 whilst we set it up*/
ICR1 = 20000; /*frequency is every 20ms/*
/*sets fast PWM (mode 14) and no prescaler*/
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
DDRB |= (1<<PA7); /*Sets OC0B as output*/
OCR1A = ICR1 * rotation /20; /*pulse to load or unload servo on pa7 depending on the variable rotation*/
}
Jeg er lidt i tvivl om der vil blive pumpet signal ud i 20 ms cycler så længe funktionen ServoMove() bliver kaldt i "for" løkken i timer0elapsed eller om det kun sker 1 gang for hvert ++ der vil være, for det skal helst være kontinuerligt
Er ved at forstå at interrupts er vejen frem. :o
Ja, interrupts er ret gode, for de bruger et minimum af CPU-tid, og er ret præcise.
ville sådan ønske at jeg kunne gøre et eller andet til gengæld :-[
HVis du nogensinde starter et saltvandsakvarie op så må du sige til he he.
Tja, det kan være, når jeg en gang skal flytte i hus; men dér hvor jeg bor i øjeblikket, er der slet ikke plads. ;)
Har lige et spørgsmål. Hvorfor ikke bruge CTC? kræver det ikke mindre regnekraft.
"polling" (dvs. hvor man hele tiden kigger på en værdi, som ændrer sig) har en del ulemper:
- Der går lang tid (i alt) med at se på værdien.
- Nogle værdier når man ikke at se, fordi CTC ændrer sig.
Ved godt at lige i dette tilfælde her skal jeg bruge 16 bit timeren til PWM men generelt er det så ikke bedst at komme så tæt på ren hardware counter/timers og så bruge flags? (det er vel også en form for interrupts) ???
Du er tæt på. Timeren sætter nogle flags, og interrupts bliver udført når disse flags sættes. Dvs. du kan faktisk (hvor det er muligt) trigge et interrupt, ved at sætte det flag som timeren sætter.
Men igen... hvis man laver et loop, hvor man bliver ved med at kigge på én ting, kan det være man går glip af en anden, plus at man bruger meget CPU-tid på at kigge på den ene ting.
eks:
while(TCNT <= 19998)
{
/* here we're only waiting, no code. */
}
/* do something here */
Ovenstående har et par fejl...
1: Vi spenderer op til 1 sekund med at vente på at TCNT kommer op på 19999.
2: Er vi uheldige, er TCNT lig med 19998 lige når den aflæses, hvilket vil sige at while-løkken udføres igen. Når while så hopper tilbage, bruges 2 clock-cycles, hvilket vil sige at TCNT skifter 2 gange, og den kommer op på 20000, hvilket vil sige at den nul-stilles, så den er 0, når while igen tester værdien. Derved bruger vi mere end 1 sekund, hvis vi er uheldige, dvs. vi skipper en aflæsning, hvis vi er uheldige.
-Endnu værre havde det været, hvis vi skulle lave noget imens vi ventede på at TCNT nåede op på 19999; der ville være flere muligheder for at TCNT 'kappede over' og røg på 0, før vi kunne aflæse den.
...ADC.h, ADC.c...
Nu har jeg kigget lidt på ATtiny44's ADC håndtering.
Den er næsten helt magen til de ADC'er jeg har brugt tidligere.
Der er dog en forskel; dens reference spænding er 1.1V eller VCC.
De andre's reference-spændinger er normalt 2.56V.
Men det, at ATtiny44's reference-spænding er VCC, gør tingene lettere for dig! :)
1: Du behøver ikke at have en modstand i serie med dit 10K potmeter!!
2: Din omregning bliver en lille smule simplere end ellers.
Her er først en header-fil, som kan bruges til begge de efterfølgende kode-stumper...
/*
* File: ADC.h
*
* (Controls when the dosing occurs, but also holds the code to do the actual dosing)
*
*/
#ifndef __ADC_h__
#define __ADC_h__
void waitUntilADCStable();
uint16_t getADC2Value(); /* this routine returns the low 10 bits of the ADC2's value, no matter if you use 8-bit or 10-bit ADC */
uint16_t getADC3Value(); /* this routine returns the low 10 bits of the ADC3's value, no matter if you use 8-bit or 10-bit ADC */
void initADC(uint8_t aBits);
#endif /* __ADC_h__ */
I din main() rutine, bør du kalde initADC(8); eller initADC(10); og lige efter din 'sei();' instruktion (før 'while(1)'), bør du kalde...
-Denne rutine venter på at ADC'en afgiver fornuftige resultater du kan stole nogenlunde på.
OK nu til selve koden.. Først en forholdsvis enkel stump kode. Dette er ikke koden du bør implementere, men denne kode er primært fordi du så lettere kan overskue hvad der sker.
Koden her aflæser et par ADC'ers værdi (ADC2 og ADC3), og gemmer dem til senere videregivelse.
Der gemmes kun en enkelt værdi. Det, som er specielt ved denne kode, er at den ud over at køre flere gange i sekundet (prescaler 128), skriver et andet sted end den læser.
Du undgår således at læsning kommer i kambolage med skrivningen.
/*
* File: ADC.c - simple
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include "ADC.h"
#ifndef inb
#define inb(sfr) _SFR_BYTE(sfr)
#endif
#define ADC_STABLE 4 /* ADC should be stable after 4 complete read cycles. You may need to adjust this number */
#define ADC_FIRST 2 /* first ADC channel to read */
#define ADC_LAST 3 /* last ADC channel to read */
static volatile uint16_t adcValue[ADC_LAST][2]; /* this buffer holds our conversion results. Write to one place, read from another. */
static volatile uint8_t adcIndex = 0; /* this is the index we write values to. */
static volatile uint8_t adcCounter = 0; /* just a counter, that counts how many times we've read all the ADC channels */
static volatile uint8_t adcChannel = 0; /* current channel we're reading the ADC value from */
void waitUntilADCStable()
{
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
}
uint16_t getADC2Value()
{
return(adcValue[2][adcIndex]);
}
uint16_t getADC3Value()
{
return(adcValue[3][adcIndex]);
}
SIGNAL (SIG_ADC)
{
uint8_t adLo;
uint8_t adHi;
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
adcValue[adcChannel][adcIndex ^ 1] = (adHi << 8) | adLo; /* save the value we've read above */
ADCSRA |= (1 << ADSC); /* start another conversion */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* start over */
adcIndex ^= 1; /* swap index, so we write to one place and read from another */
adcCounter++; /* increment counter */
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
}
void initADC(uint8_t aBits) /* in this simple example, we ignore the precision bits; we always use 10-bit conversion */
{
adcCounter = 0; /* (initialization of adcCounter is not really needed) */
adcIndex = 0; /* read at index 0, write at index 1 */
adcChannel = ADC_FIRST; /* initialize ADC channel number to read */
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* VCC is used as analog reference, first channel */
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0); /* free running mode */
ADCSRA |= (1 << ADSC); /* start conversion */
}
Og nu den mere komplicerede kode. Denne stump kode aflæser nogle af ADC'ens kanaler, og gemmer de aflæste resultater i en buffer. Når du så vil have værdierne ud, tages gennemsnittet af de sidste (4) alæsninger...
/*
* File: ADC.c - complex version
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include "ADC.h"
#ifndef inb
#define inb(sfr) _SFR_BYTE(sfr)
#endif
#define ADC_SIZE 5 /* number of reads to average the ADC channel (we write one, and average over last 4) */
#define ADC_STABLE (ADC_SIZE + 3)
#define ADC_FIRST 2 /* first ADC channel to read */
#define ADC_LAST 3 /* last ADC channel to read */
static volatile uint16_t adcValue[ADC_LAST - ADC_FIRST][ADC_SIZE]; /* this buffer holds a lot of conversion results, enough for us to average the values */
static volatile uint8_t adcReadIndex = 0; /* this is the index we start reading values from (up to, but excluding writeIndex) */
static volatile uint8_t adcWriteIndex = 0; /* this is the index we write values to */
static volatile uint8_t adcCounter = 0; /* just a counter, that counts how many times we've read all the ADC channels */
static volatile uint8_t adcChannel = 0; /* current channel we're reading the ADC value from */
static uint8_t adcBits = 8;
void waitUntilADCStable()
{
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
}
uint16_t getADCValue(uint8_t aADC)
{
uint16_t result;
uint8_t i;
uint8_t idx;
idx = adcReadIndex; /* read from this index; avoiding reading at the 'write position' */
i = ADC_SIZE - 1; /* number of values to read (eg. if ADC_SIZE is 10, we only read 9 values) */
result = 0; /* zero our result */
while(i--)
{
result += adcValue[aADC - ADC_FIRST][idx]; /* add the one value we've just read from the index */
idx = idx >= (ADC_SIZE - 1) ? 0 : idx + 1; /* next index */
}
return(result / (ADC_SIZE - 1)); /* return the averaged result */
}
uint16_t getADC2Value()
{
return(getADCValue(2));
}
uint16_t getADC3Value()
{
return(getADCValue(3));
}
SIGNAL (SIG_ADC)
{
uint8_t adLo;
uint8_t adHi;
if(adcBits <= 8) /* using 8-bit precision */
{
if(ADCSRB & (1 << ADLAR)) /* check hardware alignment-configuration and act accordingly */
{
adLo = 0;
adHi = inb(ADCH); /* (read highbyte only) */
}
else
{
adHi = 0;
adLo = inb(ADCH); /* (read highbyte only) */
}
}
else /* using 10-bit precision */
{
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
}
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
ADCSRA |= (1 << ADSC); /* start another conversion */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : adcWriteIndex + 1; /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : adcReadIndex + 1; /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
}
void initADC(uint8_t aBits)
{
adcCounter = 0; /* this is so we can see when the ADC conversions are stable */
adcWriteIndex = 0; /* start writing at index 0 */
adcReadIndex = adcWriteIndex + 1; /* read right after the write-index */
adcChannel = ADC_FIRST; /* initialize ADC channel number to read */
adcBits = aBits;
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* VCC used as analog reference, first channel */
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); /* free running mode, result is right aligned */
ADCSRA |= (1 << ADSC); /* start conversion */
}
Hvorvidt koden fungerer, kan jeg ikke sige med sikkerhed.
Men her lægger jeg mere vægt på at du forstår systemet, end at det faktisk virker.
Hvis det virker, så har vi det som bonus. ;)
Oki har lavet lidt ændringer og ellers sat modstande i serier og paralelt.
Havde ikke til at lave en 100 ohm så satte en 4k7 på R8 plads, men ved ikke om den er for stor.
100K var korrekt (ikke 100 ohm). 4K7 kan bruges, men det vil være lettere at lave aflæsningen, hvis den er 4K præcis - ellers brug 100K.
-Sidder 4K7 der allerede, så prøv at lade den sidde, og se hvordan det går med aflæsnings-koden.
Idéen er at man "halverer spændingen" for hver dip-switch der er "slået til".
På denne måde kan man lave alle typer kombinationer, og stadig finde ud af hvordan kontakterne er indstillet uden for megen bøvl.
Bruges 'halveringer', bliver det forholdsvis nemt:
Hvis R8 har værdien 4K, 8K, 16K, 32K eller 64K, vil jeg gætte på at værdierne nedenfor skulle være rimelig tætte på den angivne værdi...
Kontakt 1 (R7) har værdi 512
Kontakt 2 (R6) har værdi 256
Kontakt 3 (R5) har værdi 128
Kontakt 4 (R4) har værdi 64
Er kontakt 2 og 4 tændt mens 1 og 3 er slukket, skulle ADC'en give dig en værdi meget tæt på 320.
Aflæsningen bør så foregå på nogenlunde denne måde:
switches = (getADC1Value() + 3) & ~7; /* '3' er for tolerance +/- 3. ~7 ignorerer de 3 laveste bits. */
har fjernet det ene pot (det til at stille nulpunkt) og sat en trykknap på den ledige ADC (startknap)
Start-knappen har en fejl. Den vil enten blive aflæst som værende 'trykket ned hele tiden', eller også vil den stå og flik-flakke mellem tændt/slukket, fordi den ikke har en pull-up eller pull-down modstand.
Oftest sætter man en pull-up modstand til +5V og selve knappen 'kortslutter' så til GND når den trykkes ned.
Hvis du hellere vil have det omvendt, så i/o-værdien er 1, når knappen trykkes ned, så behold det som det er nu og sæt en pulldown modstand fra ben 10 til GND med værdien 4K7...10K.
Så længe du ikke sætter en 'stærk' modstand på, kan du sagtens sætte start-knappen på fx. MISO, MOSI eller SCK benet, så har du stadig et ledigt ben til ADC. (Stærk modstand = lav værdi, fx. 100 ohm, svag modstand = fx. 10K..100K) 10K vil nok være godt i dette tilfælde.
Kønt blev det ikke
-Det er ligegyldigt når det er en prototype. :)
, må nok ud og invistere i noget værktøj der er lidt finere end alm elektrikker grej og en ordentlig kolbe :-[
Det vigtigste du har brug for, er et multimeter...
Jeg vil anbefale et, som kan måle så meget af følgende, som muligt, højeste prioritet nævnt først:
1: Modstand (ohm)
2: Gennemgang (gerne med bip)
3: Jævnspænding
4: Kapacitet (kondensator-værdi)
5: Vekselspænding
6: Frekvens (Hz)
7: hFE (transistor)
8: Spoler (Henry, faktisk microHenry)
9: Ampere (ja, de fleste multimetre har Ampere sammen med spænding)
For ca. 200 kr, bør du kunne finde noget der kan klare fra 1 til 6 + ampere.
Et Branford (http://www.harald-nyborg.dk/merinfo.asp?varenr=2286&n=10) fra Harald Nybørge er faktisk ikke helt tosset. Det har gennemgangstester med bip, ohmmeter, vekselspændings voltmeter, jævnspædnings voltmeter, hFE, jævnstrøms amperemeter og vekselstrøms amperemeter (ja, hvorfor de er separate forstår jeg ikke!)
Desværre har Nybørge's multimeter ikke frekvens-måling, hvilket er meget rart at have, når man arbejder med microcontrollere. Der er et rimelig godt multimeter (http://el-supply.dk/?Gid=324&VNr=7730.8) hos El-Supply. det kan også måle kapacitans, men det koster også over 3 gange så meget.
Derudover, vil jeg anbefale Weller's WS81 (http://dk.rs-online.com/web/p/products/4310367/) loddestation. Det er en professionel station, som er værd at investere i.
Har du ikke råd til denne, er det laveste du bør gå ned på, nok en Unavngiven (http://www.butik4281.dk/butik/index.php?main_page=product_info&products_id=293) loddestation, den kan findes forskellige steder i landet, også hos Biltema; men den laveste pris finder du uden tvivl hos Hallenslev Radio/TV.
De fleste sætter deres eget navn på loddestationen, men det er samme producent.
"For alt i verden:" undgå 'loddekolbe på ledning'; de er intet værd.
Hvis du skal lave professionelle lodninger (senere hen), vil jeg anbefale WDC Tørrens (http://dk.rs-online.com/web/p/products/517-8546/). Loddespidsernes levetid forlænges markant, plus lodningerne bliver mange gange flottere, end hvis du bruger 'svamp med vand'. Bruger du svamp med vand, bør du bruge vand, der har været kogt, hvis du ikke har demineraliseret vand.
Før jeg fik en modstands-bukkelære (http://el-supply.dk/?Gid=&VNr=8177KB), brugte jeg ikke den slags. Men nu jeg har den, bruger jeg den rimelig ofte.
(Søg hos PC-Elektronik efter "Bukkeklods", han har også en udemærket billig elektronik-skævbider, den hedder "TANG 570" og koster 20 kr.)
Skævbideren kan klippe 'fladt ned til printet', hvilket jeg selv har fundet nyttigt mange gange.
En Olfa-kniv er også blandt mit favorit-værktøj, jeg bruger den nærmest hver eneste dag; jeg bruger også denne, når jeg skal "knække hulprint", da skærer jeg lige et par gange på hver side af printet før jeg knækker det, for at få et nogenlunde pænt knæk.
Og så selvfølgelig et finmekanisk værktøjssæt (http://el-supply.dk/?Gid=505&VNr=8150). Har du et sæt med 6 skruetrækkere allerede, så burde det være tilstrækkeligt. Disse skruetrækkere er også gode til at "løfte IC'er ud af sokler" med.
The target microcontroller must provide it's own power.
Skal jeg tilslutte 5 volt til programmeren på 5 volt og gnd og så også 5 volt til 44'ern?
1: Der skal strøm (5V) på kredsløbet.
2: Programmerings-enheden får sin strøm (5V) fra kredsløbet.
3: GND er fælles for computer, programmerings-enhed og kredsløb.
-Dvs. Hvis der ikke er strøm på kredsløbet, vil intet fungere. ;)
Det kan være en god idé, først at sætte strøm på kredsløbet, derefter måle med multimeter, om GND og +5V er iorden.
Derefter sætte programmerings-enheden til (uden computer), for at checke de sidste ting, og om der evt. er kortslutninger, eller andet bøvl.
Når du er rimelig sikker på at der ikke er nogen katastrofe, så kobl strømmen fra, og tilslut programmerings-enheden til computeren, kobl kredsløb og 5V på, og prøv med din programmerings-software at kommunikere med chippen (dvs. aflæs eller send en hex-fil til ATtiny44'eren).
Bruger du AVRDUDE, kunne kommando-linien se således ud:
avrdude -c avrispmkii -P usb -p attiny44 -U flash:w:MyHexFile.hex:i
...Jeg håber det fungerer ved første forsøg. =)
Det kan være du får en fejl som denne:
avrdude: stk500v2_command(): command failed
avrdude: stk500v2_command(): unknown status 0xc9
avrdude: stk500v2_program_enable(): cannot get connection status
avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override
this check.
avrdude done. Thank you.
...Da betyder det at der er en eller anden forbindelse der mangler, eller er forkert.
(Hvis der ikke er strøm på kredsløbet, får du også denne fejl).
Nu prøver jeg det hvertfald!
Jeg håber det virker (jeg selv har brugt Windows 98 til programmering på mit arbejde, men det var med AVRISPmkII; den burde vel også virke under 64-bit Windows).
det kan jeg da godt få grå hår af :o. hvilken -c skal jeg bruge med den programmer jeg har lavet ? er det dapa ?
Bare rolig, når du har fået det til at virke første gang, så ved du at det kan fungere.
Her er min linie:
avrdude -c avrispmkii -P usb -p attiny44 -U flash:w:main.hex:i
-Men som du allerede er inde på, bør parametret til -c skiftes.
Hvis det er denne (http://avrprogrammers.com/bld-par2.php) du har lavet, så får du et hint oppe i fanebladet; der står: "STK200".
Søger jeg med Goole efter avrdude stk200 (http://www.google.com/search?q=avrdude+stk200), får jeg et resultat på 3. søgeresultat, som kan læses direkte på søge-resultat siden:
(PDF) C:\>avrdude -p m644 -c stk200 -P lpt1 -v avrdude: Version 5.5 ...
home.arcor.de/chlercher/elektronik/fuse.pdfFile Format: PDF/Adobe Acrobat - Quick View
3 Jan 2008 – System wide configuration file is "C:\Programme\winAVR\bin\avrdude.conf". Using Port. : lpt1. Using Programmer. : stk200. AVR Part ...
Derfor: Prøv denne linie:
avrdude -c stk200 -P lpt1 -p attiny44 -U flash:w:main.hex:i
desværre hjalp det ikke ingen kontakt prøver at være systematisk i morgen men syntes jeg kan måle det jeg skal de rigtige steder :'(
OK. Husk at sende mig de fejl-koder som du får fra avrdude.
Men mest sansynligt er det dog at du får denne...
initialization failed, rc=-1
Double check connections and try again, or use -F to override this check.
Brug aldrig -F til at forsøge at løse problemet; det vil ikke løse noget, denne option er sådan set kun til beta-testere.
-Har du husket at ændre '-P usb' til '-P lpt1' ?
14 har gennemgang til ben 1 på tiny men også men 11
Dette vil jeg tro er pga. at potmetret er skruet helt ned. Hvis du skruer lidt på potmetret, vil gennemgangen forsvinde, korrekt?
der er ingen ændring i avrdude så nu smider jeg en xp på en anden partition og ser om det hjælper
der er en del stk200 diagrammer der viser gnd på ben 20-25 samt kappe
Jeg har lige kigget efter her (http://pinouts.ru/ParallelPorts/ParallelPC_pinout.shtml), og det burde ikke betyde noget, da ben 18...25 er koblet sammen internt på LPT porten.
Prøv forresten at skifte til...
avrdude -c pony-stk200 -P lpt1 -p attiny44 -U flash:w:main.hex:i
-Hvis du har sat transistor, lysdiode og de 2 ekstra modstande på, burde lysdioden nu lyse/blinke, når du forsøger at køre programmet.
Hvis du kan give mig en ledetråd til hvor jeg bør kigge ville det være dejligt.
Den tutorial du fandt, er faktisk rigtig god. Det kan godt være den ikke passer direkte til den microcontroller du bruger, men principperne er de samme.
Lige for tiden (hvor min arbejdscomputer trænger til hvile - strømforsyningen brændte af), har jeg begrænset adgang til mine ressourcer, så min mulighed for at være til hjælp er skåret kraftigt ned. :(
Dog vil jeg sige at stort set er det kun det praktiske du mangler.
Når du kan sende programmet til chippen, vil du kunne justere værdierne.
Bare tag én ting ad gangen, for ellers bliver det for forvirrende.
Det kan være en god idé at lave et 'debug-værktøj', fx. sæt en lysdiode på et af microcontrollerens ben.
Lad os antage at du har en variabel, du kalder 'count'.
Du aner ikke hvad værdien af 'count' er, men hvis du laver en lille rutine, som kan blinke med lysdioden, kan du finde ud af hvilken værdi 'count' har. Fx.
void blinkLED(uint8_t aTimes)
{
while(aTimes--) /* decrement aTimes until it reaches 0 */
{
LED_PORT |= (1 << LED_PIN); /* turn LED on */
/* wait half a second */
_delay_ms(250);
_delay_ms(250);
LED_PORT &= ~(1 << LED_PIN); /* turn LED off */
/* wait half a second */
_delay_ms(250);
_delay_ms(250);
}
/* Finally wait one second, to create a clear delay: */
for(aTimes = 0; aTimes < 4; aTimes++)
{
_wait_ms(250);
}
}
Kan kaldes på denne måde...
...Selvfølgelig gider du ikke sidde og tælle værdier der når for langt over 10. I sådanne tilfælde kunne du evt...
blinkLED(count / 10);
blinkLED(count % 10);
Så vil du først få antal 'tiere', dernæst antal 'enere'.
Skal du helt op på tal over 100...
blinkLED(count / 100);
blinkLED((count / 10) % 10);
blinkLED(count % 10);
Alt kun teori, da jeg ikke lige kan afprøve det.
Nå men nu har jeg læst koden igennem. tillader mig lige at poste den her
Main.h
#ifndef __MAIN_H__
#define __MAIN_H__
#define F_CPU 1000000
void initTimer0();
void initDosing();
#endif /* __MAIN_H__ */
Main.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "main.h"
#include "ADC.h"
#include "Timer0.h"
#include "Dosing.h"
int main()
{
initADC(8); /* initialize ADC, we'll use the 8-bit precision for now */
initTimer0();
initDosing();
sei(); /* globally enable interrupts */
while(1)
{
/* we don't really need to do anything here. Everything is handled by the interrupts */
}
return(0); /* (never reached) */
}
Kan ikke se nogle problemer med de to main
Timer0.h
#ifndef __Timer0_h__
#define __Timer0_h__
void setMinuteCountdown(uint16_t aMinuteCountdown); /* this routine controls how often the interrupt will occur */
void timer0Elapsed(); /* this is a routine that you implement */
void initTimer0();
#endif /* __Timer0_h__ */
Timer0.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "Timer0.h"
#include "Main.h"
static volatile uint8_t seconds = 0; /* variables that are changed by an interrupt, *MUST, MUST* be volatile! */
static volatile uint16_t minuteCountdown = 0; /* variables that are changed by an interrupt, *MUST, MUST* be volatile! */
static uint16_t minuteCountdownRestart = 0;
void waitSeconds(uint8_t aSeconds)
{
aSeconds = (aSeconds + seconds); /* add current second value to aSeconds */
if(aSeconds >= 60) /* we can't wait more than 59 seconds, so if the result is larger than 59... */
{
aSeconds -= 60; /* ...then subtract 60 */
}
while(aSeconds != seconds) /* wait until seconds has the same value as aSeconds */
{
}
}
void setMinuteCountdown(uint16_t aMinuteCountdown)
{
cli(); /* make sure interrupt is not occurring while writing the 16-bit value */
minuteCountdown = aMinuteCountdown; /* (that could cause an incorrect reading by the interrupt) */
minuteCountdownRestart = aMinuteCountdown; /* set our restart-value as well */
sei();
}
SIGNAL (SIG_OVERFLOW0) /* This interrupt occurs when TCNT0 wraps to 0 */
{
TCNT0 = 256 - ((uint8_t) ((F_CPU / 64) / 125)); /* interrupt occurs once per second. */
seconds++; /* increment our number of seconds elapsed */
if(seconds >= 60) /* when we've reached 60 seconds */
{
seconds = 0; /* wrap to zero */
if(minuteCountdown) /* is our minuteCountdown nonzero ? */
{
if(0 == --minuteCountdown) /* if minuteCountdown reaches 0 when it counts down (eg going from 1 to 0) */
{
minuteCountdown = minuteCountdownRestart; /* restart countdown */
timer0Elapsed(); /* it's time for applying another dose */
}
}
}
}
void initTimer0()
{
cli(); /* disable interrupts if not already done */
seconds = 0;
minuteCountdown = 0;
minuteCountdownRestart = 0;
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
TIMSK0 &= (1 << TOIE0); /* clear 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
/* set up timer to run in normal mode: (WGM02:WGM01:WGM00 = 000 */
/* make timer use PRESCALER8: (CS02:CS01:CS00 = 000): */
/* ATtiny24/44/84 Preliminary, page 79; section 11.9.1 */
TCCR0A = (0 | (0 << COM0A1) | (0 << COM0A0) | (0 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (0 << WGM00));
/* ATtiny24/44/84 Preliminary, page 82; section 11.9.2 */
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00));
/* Clear any pending interrupts, so we don't get an accidental interrupt immediately after enabling interrupts... */
/* ATtiny24/44/84 Preliminary, page 84; section 11.9.7 */
TIFR0 |= (1 << TOV0); /* clear 'Timer OVerflow 0' bit in 'Timer Interrupt Flag Register 0' */
/* enable Timer OVerflow 0 interrupt: */
/* ATtiny24/44/84 Preliminary, page 83; section 11.9.6 */
TIMSK0 |= (1 << TOIE0); /* set 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
}
Kan ikke se nogle problemer med de to timer0
ADC.h
#ifndef __ADC_h__
#define __ADC_h__
void waitUntilADCStable();
uint16_t getADC2Value(); /* this routine returns the low 10 bits of the ADC2's value, no matter if you use 8-bit or 10-bit ADC */
uint16_t getADC3Value(); /* this routine returns the low 10 bits of the ADC3's value, no matter if you use 8-bit or 10-bit ADC */
void initADC(uint8_t aBits);
#endif /* __ADC_h__ */
ADC.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "ADC.h"
#ifndef inb
#define inb(sfr) _SFR_BYTE(sfr)
#endif
#define ADC_SIZE 5 /* number of reads to average the ADC channel (we write one, and average over last 4) */
#define ADC_STABLE (ADC_SIZE + 3)
#define ADC_FIRST 2 /* first ADC channel to read */
#define ADC_LAST 3 /* last ADC channel to read */
static volatile uint16_t adcValue[ADC_LAST - ADC_FIRST][ADC_SIZE]; /* this buffer holds a lot of conversion results, enough for us to average the values */
static volatile uint8_t adcReadIndex = 0; /* this is the index we start reading values from (up to, but excluding writeIndex) */
static volatile uint8_t adcWriteIndex = 0; /* this is the index we write values to */
static volatile uint8_t adcCounter = 0; /* just a counter, that counts how many times we've read all the ADC channels */
static volatile uint8_t adcChannel = 0; /* current channel we're reading the ADC value from */
static uint8_t adcBits = 8;
void waitUntilADCStable()
{
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
}
uint16_t getADCValue(uint8_t aADC)
{
uint16_t result;
uint8_t i;
uint8_t idx;
idx = adcReadIndex; /* read from this index; avoiding reading at the 'write position' */
i = ADC_SIZE - 1; /* number of values to read (eg. if ADC_SIZE is 10, we only read 9 values) */
result = 0; /* zero our result */
while(i--)
{
result += adcValue[aADC - ADC_FIRST][idx]; /* add the one value we've just read from the index */
idx = idx >= (ADC_SIZE - 1) ? 0 : idx + 1; /* next index */
}
return(result / (ADC_SIZE - 1)); /* return the averaged result */
}
uint16_t getADC2Value()
{
return(getADCValue(2));
}
/* getADC3Value slettes*/
uint16_t getADC3Value()
{
return(getADCValue(3));
}
SIGNAL (SIG_ADC)
{
uint8_t adLo;
uint8_t adHi;
if(adcBits <= 8) /* using 8-bit precision */
{
if(ADCSRB & (1 << ADLAR)) /* check hardware alignment-configuration and act accordingly */
{
adLo = 0;
adHi = inb(ADCH); /* (read highbyte only) */
}
else
{
adHi = 0;
adLo = inb(ADCH); /* (read highbyte only) */
}
}
else /* using 10-bit precision */
{
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
}
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
ADCSRA |= (1 << ADSC); /* start another conversion */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : adcWriteIndex + 1; /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : adcReadIndex + 1; /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
}
void initADC(uint8_t aBits)
{
adcCounter = 0; /* this is so we can see when the ADC conversions are stable */
adcWriteIndex = 0; /* start writing at index 0 */
adcReadIndex = adcWriteIndex + 1; /* read right after the write-index */
adcChannel = ADC_FIRST; /* initialize ADC channel number to read */
adcBits = aBits;
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* VCC used as analog reference, first channel */
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); /* free running mode, result is right aligned */
ADCSRA |= (1 << ADSC); /* start conversion */
}
Kan ikke sige at jeg helt har forstået ADC.c så har svært ved at gennemskue om den er rigtig, men alt hvad der har med adc3 slettes da dette potmeter er sløjfet og defineres af software
dosing.h
#ifndef __Dosing_h__
#define __Dosing_h__
void startDosing(); /* this routine should be called when the 'start' button is pressed */
void setDosing(int8_t aDosing); /* usually you wouldn't call this routine (call startDosing instead) */
void initDosing();
#endif /* __Dosing_h__ */
dosing.c
#include <avr/io.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "Timer1.h"
#define ADC3_CALIBRATION 0 /* I'll let you know later, how to find this calibration value */
int8_t adc1;
int8_t adc2;
int8_t a;
int16_t rotation;
int8_t repeats=1; /*Must be altered to represent the dip for repeats*/
void setDosing(int8_t aDosing)
{
switch(aDosing)
{
case 1:
setMinuteCountdown(2 * 60); /* every 2nd hour */
break;
case 2:
setMinuteCountdown(6 * 60); /* every 6th hour */
break;
case 3:
setMinuteCountdown(12 * 60); /* every 12th hour */
break;
case 4:
setMinuteCountdown(24 * 60); /* every 24th hour */
break;
}
}
void startDosing() /* this routine should be called when the 'start' button is pressed */
{
int16_t adcValue;
int8_t dosing;
adcValue = getADC3Value(); /* read value of ADC channel 3 */
adcValue = adcValue + ADC3_CALIBRATION; /* add calibration value */
dosing = ((adcValue * 4) >> 10) + 1; /* calculate the dosing (we'll get a value from 1 to 4) */
setDosing(dosing); /* when this routine is called, the dosing countdown starts */
}
void timer0Elapsed()
{
uint8_t a;
uint16_t adc2Value;
uint16_t adc3Value;
adc2Value = getADC2Value();
adc3Value =0; /* Value 0 - 256 since the pot2 is removed and unload is defined by software*/
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
startServo(200, adc3Value);
/* unload: */
waitSeconds(10);
startServo(200, adc2Value);
}
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
Her har jeg et par spørgsmål
ADC3_CALIBRATION bliver dette ikke gjort i ADC.c (getADCValue)
Repeats funktionen kan jeg lave den på samme måde som set dosing?
men mangler jeg ikke at vide hvilke værdier compared til vcc, der afgører om det er case 1...4 ?
Timer1.h
#ifndef __Timer1_h__
#define __Timer1_h__
uint8_t isServoRunning();
void stopServo();
void startServo(uint16_t aServoCountdown, uint16_t aRotation);
void initTimer1();
void waitSeconds(uint8_t aSeconds);
#endif /* __Timer1_h__ */
Timer1.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "Timer1.h"
#include "Main.h"
static volatile uint16_t servoCountdown = 0; /* variables that are changed by an interrupt, *MUST, MUST* be volatile! */
const uint16_t servoFrequency = F_CPU / 50; /* frequency is every 20ms = 50 times per second (50Hz) */
uint8_t isServoRunning()
{
return(servoCountdown != 0);
}
SIGNAL (SIG_OUTPUT_COMPARE1B)
{
if(servoCountdown)
{
if(0 == --servoCountdown)
{
stopServo(); /* turn off servo-timer (including interrupt) */
}
}
}
void stopServo()
{
TCCR1B = 0; /* CS12 : CS11 : CS10 = 0, no clock source (Timer/Counter stopped) */
TCCR1A = 0;
TCNT1 = 0;
OCR1A = 0;
OCR1B = 0;
ICR1 = 0;
TIMSK1 = 0;
TIFR1 = 0xff;
}
void startServo(uint16_t aServoCountdown, uint16_t aRotation)
{
servoCountdown = aServoCountdown; /* number of pulses to send to servo, before turning it off */
TCCR1A = 0; /* disable all PWM on Timer1 whilst we set it up */
TCCR1B = 0; /* (disable clock-source, so the timer is frozen) */
TIMSK1 = 0; /* disable all Timer1 interrupts */
ICR1 = servoFrequency; /* frequency is every 20ms, 50 times per second (50Hz) */
/* select fast PWM (mode 14) and no prescaler */
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); /* enable output COMpare 1 A, output COMpare 1 B, set WGM to Fast PWM */
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); /* set WGM to fast PWM, choose clock source CS12:CS11:CS10 = 1, clk/1 (no prescaling) */
OCR1A = servoFrequency * aRotation / 20; /* pulse to load or unload servo on PA7 depending on the argument aRotation */
TIMSK1 |= (1 << OCIE1B); /* enable Output Compare Interrupt 1 B */
}
void initTimer1()
{
cli(); /* disable interrupts if not already done */
DDRB |= (1 << PA7); /* set OC0B as output */
stopServo(); /* initially, the servo is not running */
}
Kan heller ikke se nogle problemer i Timer1
undskyld for den lange post, håber du har tid og lyst til at se på det :o
Jeg har lidt svært ved at se hvordan jeg skal gribe debugging an, da alle delene er vævet ind i hinnanden og afhængige af hinnanden. Min plan var at speede timer0 funktionen op så 24 timer blev til eks 24 minutter. Har testet timerfunktionerne inde i avrstudio vha breaks der retunerer aktuelle værdier og det ser ud til at timer0 tæller og agerer som den skal.
Her har jeg et par spørgsmål
ADC3_CALIBRATION bliver dette ikke gjort i ADC.c (getADCValue)
Nej, getADCValue aflæser kun spændingen. Den laver dog en lille smule stabilisering af tallet, så det ikke flik-flakker for meget frem og tilbage - det er det, while-løkken gør.
Fx. har vi valgt ADC_SIZE=5, vil de sidste 4 resultater blive lagt sammen og divideret med 4.
Det er praktisk at have ADC_SIZE en af følgende værdier:
3: 1+2
5: 1+2*2
9: 1+2*2*2
17: 1+2*2*2*2
(og så fremdeles; men ikke for stor, for så er der ikke plads i microcontrolleren's RAM til resultaterne)
Repeats funktionen kan jeg lave den på samme måde som set dosing?
Er den ikke 'indbygget' i timer0Elapsed ?
men mangler jeg ikke at vide hvilke værdier compared til vcc, der afgører om det er case 1...4 ?
Jo. Her er du nødt til at 'tillade' nabo-værdierne også.
Lad os antage at du får følgende resultater ved 4 indstillinger:
Indstilling 1: værdi 0
Indstilling 2: værdi 127
Indstilling 3: værdi 191
Indstilling 4: værdi 223
(Ovenstående er bare vilde gæt)
Så kan du...
if(value >= 0 && value < ((0 + 127) / 2))
{
/* setting 1 */
}
else if(value >= ((0 + 127) / 2) && value < ((128 + 191) / 2))
{
/* setting 2 */
}
else if(value >= ((128 + 191) / 2) && value < ((192 + 223) / 2))
{
/* setting 3 */
}
else if(value >= ((192 + 223) / 2) && value < ((224 + 255) / 2))
{
/* setting 4 */
}
Igen: Ovenstående er kun skud/idéer til hvordan du har en rimelig god/nem chance for at ramme rigtigt.
-Grunden til at du bør have lidt tolerance, er fordi vi har med modstande at gøre. Tolerancen på disse, gør at resultatet ikke altid bliver nøjagtig det samme.
Jeg har lidt svært ved at se hvordan jeg skal gribe debugging an, da alle delene er vævet ind i hinnanden og afhængige af hinnanden. Min plan var at speede timer0 funktionen op så 24 timer blev til eks 24 minutter. Har testet timerfunktionerne inde i avrstudio vha breaks der retunerer aktuelle værdier og det ser ud til at timer0 tæller og agerer som den skal.
Debugging er smartest/nemmest, hvis du tester ét modul ad gangen.
Lad os antage at du vil teste ADC-aflæsningen. Umiddelbart lyder det som noget, der er svært, for man kan 'ikke se noget resultat'.
Men hvis du har opstillingen på breadboard, kan du bruge nogle lysdioder (husk en modstand i serie der er større end 330 ohm, fx. 1k er udemærket)
Da kan du sige...
val = getADC2Value();
if(val >= 128)
{
setLED1(1); /* turn LED1 on */
val = val - 128;
}
else
{
setLED1(0); /* turn LED1 off */
}
if(val >= 64)
{
setLED2(1); /* turn LED2 on */
val = val - 64;
}
else
{
setLED2(0); /* turn LED2 off */
}
Her er et eksempel på hvis man sætter lysdiodens negative ben fra PA0-benet på IC'en og dens positive ben til gennem en 1k modstand og videre til +5V:
void setLED1(uint8_t aState)
{
PORTA = aState ? (PORTA & ~(1 << PA0)) : (PORTA | (1 << PA0));
}
Samme måde for setLED2, setLED3, osv. (du må selv bestemme hvilken port du vil bruge; PA0...PA7 hører til PORTA, PB0...PB7 hører til PORTB)
Du kan så prøve dig lidt frem. Jo flere lysdioder du sætter på, desto nemmere/hurtigere vil du kunne finde ud af hvad resultaterne er.
kan jeg ikke splitte dippen op i 2 og så køre antal doseringer pr dag ind på pa1 og repeats ind på pa4 hvis jeg så bruger 300 k og 150k samt en 150k fra pb2 og en fra pa1 til gnd, så får jeg da en foreskel i værdier fra 1.6 volt til 3 og kan nøjes med 4 cases for hver funktion istedet for 16 cases på 1 funktion ??
er dog ikke sikker på om jeg kan bruge pa4 til formålet, da det også er et programmerings ben
syntes godt nok det sidste kode der mangler er temmeligt hardcore
har 3 eller 4 adc kanaler der skal aflæses (alt efter om dippen bliver splittet op i to)
ADC 1 er til dip (antal doseringer pr dag)
ADC 2 er til potmeter (antal ml pr repeat. Længden på high ( 0.9 til 2.2 ms) i 20 ms cyclen)
ADC 3 er til start knap
ADC 4 er til dip (antal repeats)
sådan her ser min adc.c ud, men som skrevet tidligere har jeg mere end svært ved at gennemskue den. det eneste jeg tror jeg kan se er at define adc first og last nok skal ændres til 1 og 4.
(jeg ville gerne have en DDRa = 0x61; og så også en getADCxValue for hver af de fire ADC, hvor x er 1-4) ind men kan ikke gennemskue om nuværende kode sætter nogle input pins
og så dit eksempel med indstillinger for hver af de 4 ADC'er, hvis de ikke skal i dosing.c filen. Jeg håber du kan give mig et hint for jeg er godt nok helt blank lige her og nu.
Den tutorial jeg skrev om tidligere beskriver kun et potmeter der aflæses, men der er der ikke noget om kalibrering eller values. er mere end forvirret :-[
#include <avr/io.h>
#include <avr/interrupt.h>
#include "ADC.h"
#ifndef inb
#define inb(sfr) _SFR_BYTE(sfr)
#endif
#define ADC_SIZE 5 /* number of reads to average the ADC channel (we write one, and average over last 4) */
#define ADC_STABLE (ADC_SIZE + 3)
#define ADC_FIRST 2 /* first ADC channel to read */
#define ADC_LAST 3 /* last ADC channel to read */
static volatile uint16_t adcValue[ADC_LAST - ADC_FIRST][ADC_SIZE]; /* this buffer holds a lot of conversion results, enough for us to average the values */
static volatile uint8_t adcReadIndex = 0; /* this is the index we start reading values from (up to, but excluding writeIndex) */
static volatile uint8_t adcWriteIndex = 0; /* this is the index we write values to */
static volatile uint8_t adcCounter = 0; /* just a counter, that counts how many times we've read all the ADC channels */
static volatile uint8_t adcChannel = 0; /* current channel we're reading the ADC value from */
static uint8_t adcBits = 8;
void waitUntilADCStable()
{
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
}
uint16_t getADCValue(uint8_t aADC)
{
uint16_t result;
uint8_t i;
uint8_t idx;
idx = adcReadIndex; /* read from this index; avoiding reading at the 'write position' */
i = ADC_SIZE - 1; /* number of values to read (eg. if ADC_SIZE is 10, we only read 9 values) */
result = 0; /* zero our result */
while(i--)
{
result += adcValue[aADC - ADC_FIRST][idx]; /* add the one value we've just read from the index */
idx = idx >= (ADC_SIZE - 1) ? 0 : idx + 1; /* next index */
}
return(result / (ADC_SIZE - 1)); /* return the averaged result */
}
uint16_t getADC2Value()
{
return(getADCValue(2));
}
uint16_t getADC3Value()
{
return(getADCValue(3));
}
SIGNAL (SIG_ADC)
{
uint8_t adLo;
uint8_t adHi;
if(adcBits <= 8) /* using 8-bit precision */
{
if(ADCSRB & (1 << ADLAR)) /* check hardware alignment-configuration and act accordingly */
{
adLo = 0;
adHi = inb(ADCH); /* (read highbyte only) */
}
else
{
adHi = 0;
adLo = inb(ADCH); /* (read highbyte only) */
}
}
else
{
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
}
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
ADCSRA |= (1 << ADSC); /* start another conversion */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : adcWriteIndex + 1; /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : adcReadIndex + 1; /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
}
void initADC(uint8_t aBits)
{
adcCounter = 0; /* this is so we can see when the ADC conversions are stable */
adcWriteIndex = 0; /* start writing at index 0 */
adcReadIndex = adcWriteIndex + 1; /* read right after the write-index */
adcChannel = ADC_FIRST; /* initialize ADC channel number to read */
adcBits = aBits;
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* VCC used as analog reference, first channel */
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); /* free running mode, result is right aligned */
ADCSRA |= (1 << ADSC); /* start conversion */
}
Giver det bedre mening nu ? :)
Jeg forstår godt metoden med at finde værdien (eks 71), men aner ikke hvordan jeg skal kode det, og får jeg ikke en masse floating med alle de divisioner?
værdien i nedenstående er så konstanten #define ADC2_CALIBRATION; eller hva?
eks.
if(value >= 0 && value < ((0 + 127) / 2))
{
/* setting 1 */
}
else if(value >= ((0 + 127) / 2) && value < ((128 + 191) / 2))
{
/* setting 2 */
}
else if(value >= ((128 + 191) / 2) && value < ((192 + 223) / 2))
{
/* setting 3 */
}
else if(value >= ((192 + 223) / 2) && value < ((224 + 255) / 2))
{
/* setting 4 */
}
Denne kode er til kalibrering af eks adc2
værdien value hvor kommer den fra? for hvis getADCvalue er en aflæsning af spænding (som du skrev for nogle posts siden) så skal jeg jo have en omregning til 10 bit værdien et eller andet sted.
Skal det så gøres ved en beregning der f.eks ser således ud
void calculation (int16_t value)
{
calc = 0,2048 * getADC2Value;
return(calc);
}
(de 0,204 er en omregning af 1024 / 5000 til en konstant, for at undgå divisioner i programmet til gengæld er det et komma tal som jo også er noget skidt)
og i tilfælde af at det skulle være så heldigt at være denne måde hvorpå jeg skal gøre det. bør det så gøres i min adc.c eller i min dosing.c
Jeg beklager og er ked af hvis jeg er lidt tung i det her lige nu (hader at føle mig så tabt bag vognen) :o he he.
og lige et spørgsmål mere, jeg er ikke sikker på om du så det jeg skrev i min tidligere post så tillader mig lige at gentage spørgsmålet her
kan jeg ikke splitte dippen op i 2 og så køre antal doseringer pr dag ind på pa1 og repeats ind på pa4 hvis jeg så bruger 300 k og 150k samt en 150k fra pb2 og en fra pa1 til gnd, så får jeg da en foreskel i værdier fra 1.6 volt til 3 og kan nøjes med 4 cases for hver funktion istedet for 16 cases på 1 funktion ??
Advarsel: Meget langt indlæg.
Giver det bedre mening nu ? :)
Jeg forstår godt metoden med at finde værdien (eks 71), men aner ikke hvordan jeg skal kode det, og får jeg ikke en masse floating med alle de divisioner?
Hvis du får floating point kode med de viste divisioner, har du en dårlig compiler, for den skal for det første selv reducere konstante tal, dvs. tal, som den kan regne værdien ud af på forhånd.
Compileren kan fx. reducere udtrykket
uint16_t plads;
plads = ((4 + 12) / 2) * 3.
Dette kan du nemlig også selv reducere.
Så uanset om du skriver ovenstående, eller...
...vil resultat-koden være nøjagtig den samme.
Men compileren kan ikke reducere udtrykket
uint16_t plads;
plads = ((kaniner + elefanter) / marsvin) * salater.
Dog kan intelligente compilere reducere følgende:
uint16_t kaniner;
uint16_t elefanter;
uint16_t marsvin;
uint16_t salater;
uint16_t plads;
plads = ((kaniner + elefanter) / marsvin) * salater;
Det kan være svært i starten, at gennemskue hvad der foregår i en compiler, men efterhånden som du bruger den, vil du få en bedre fornemmelse af 'hvordan de tænker'. ;)
Bemærk: Koden til at finde disse konstante tal er kun beregnet til dip-switches og knapper med modstande, ikke potmetrene.
værdien i nedenstående er så konstanten #define ADC2_CALIBRATION; eller hva?
Denne kode er til kalibrering af eks adc2
værdien value hvor kommer den fra? for hvis getADCvalue er en aflæsning af spænding (som du skrev for nogle posts siden) så skal jeg jo have en omregning til 10 bit værdien et eller andet sted.
Inden nævnte kode, skal du bare aflæse ADC'en på denne måde...
value = getADC#value();
if(value >= 0 && value < ((0 + 127) / 2))
{
/* setting 1 */
}
else ...
Skal det så gøres ved en beregning der f.eks ser således ud
void calculation (int16_t value)
{
calc = 0,2048 * getADC2Value;
return(calc);
}
(de 0,204 er en omregning af 1024 / 5000 til en konstant, for at undgå divisioner i programmet til gengæld er det et komma tal som jo også er noget skidt)
I ovenstående kode returnerer du void (ingenting), hvilket vil sige at du kan ikke returnere et tal.
calc er ikke defineret. Hvis calc er en float, så vil du få floating-point kode, hvilket du ikke ønsker.
value bliver ikke brugt til noget, den kan udelades fra argumenterne.
getADC2Value skal have paranteser på, for ellers får du adressen på funktionen (dvs. du får at vide hvor i hukommelsen funktionen ligger).
Derimod, hvis calc er et heltal, vil du altid få resultatet 0, da 0.2048 vil blive rundet ned af compileren før der ganges med getADC2Value().
Denne stump burde kunne virke:
uint16_t calculation()
{
uint32_t calc;
/* simpel: */
calc = (5000 * getADC2Value()) >> 10;
/* eller med op/nedrunding: */
/* calc = (((5000 * getADC2Value()) >> 9) + 1) >> 1; */
return(calc);
}
Desværre kommer vi helt op på 32 bits midlertidigt, men det må så være det, det er.
Her får du så resultatet i millivolt.
Men du kunne også gøre det omvendte, nemlig regne millivolt om til ADC-værdi:
if(value > (1023 * 1500 / 5000))
{
/* value is 1.5V */
}
På den måde vil din kode blive mindre og hurtigere, fordi compileren kan reducere de konstante tal.
Bemærk: Vi ganger før vi dividerer, for at vores resultat skal blive så præcist som muligt.
Dette gør vi pga. at vi regner med heltal.
1023 / 5000 = 0.
1023 * 1500 = 1534500
1534500 / 5000 = 306
Man kan lave et hjælpeværktøj til dette...
#define MILLIVOLT2ADC(a) (1023 * (a) / 5000)
Eller endnu bedre, et, som kan runde op eller ned til nærmeste heltal:
#define MILLIVOLT2ADC(a) (((1023 * (a) / 2500) + 1) / 2)
Her lægger jeg 1 til, og dividerer med 2; det er det samme som hvis man kunne lægge 0.5 til det færdige resultat.
Man kan bruge bitskift til at dividere med 2:
#define MILLIVOLT2ADC(a) (((1023 * (a) / 2500) + 1) >> 1)
...Så de sidste 2 MILLIVOLT2ADC vil give de samme resultater.
og i tilfælde af at det skulle være så heldigt at være denne måde hvorpå jeg skal gøre det. bør det så gøres i min adc.c eller i min dosing.c
Jeg anbefaler at lave det i dosing.c, for adc.c er stort set kun aflæsning. Prøv at forestil dig det som om adc.c bruges i 10 andre programmer der har med ADC at gøre.
-På den måde vil det være lettere for dig at se hvor du bør lægge tingene.
Jeg beklager og er ked af hvis jeg er lidt tung i det her lige nu (hader at føle mig så tabt bag vognen)
Helt iorden. ;)
og lige et spørgsmål mere, jeg er ikke sikker på om du så det jeg skrev i min tidligere post så tillader mig lige at gentage spørgsmålet her
kan jeg ikke splitte dippen op i 2 og så køre antal doseringer pr dag ind på pa1 og repeats ind på pa4 hvis jeg så bruger 300 k og 150k samt en 150k fra pb2 og en fra pa1 til gnd, så får jeg da en foreskel i værdier fra 1.6 volt til 3 og kan nøjes med 4 cases for hver funktion istedet for 16 cases på 1 funktion ??
Det kan du godt, men modstandenes værdier bør ikke ændres. 150K og 300K er nok for store.
Du bør normalt bruge modstande under 10K, da støj ellers kan påvirke dit kredsløb.
Jeg anbefaler stadig at køre med de nævnte værdier: 0.5K, 1K, 2K, 4K.
Jeg vil tro at hvis du sætter 1K på den ene switch og 2K på den anden switch og 2K på output (istedet for 3k9), så ville det kunne fungere. -Og så lave to stk af dem i alt.
Jeg tror du har fejl i dine værdier (0 + 127), (128 + 191), osv.
Grunden er at vores modstands-netværk ikke leverer linære værdier.
Derfor kan vi ikke bare 'dividere med 2' hele vejen; ellers havde der været en simplere metode. ;)
Men i stedet for den lange 'if/else if' kode, kan du gøre koden kortere, ved at aflæse en tabel.
Udførslen tager et par ekstra clock-cycles (hvilket ikke betyder noget i dette tilfælde).
Mit skud er at denne kode vil fylde lidt over 48 bytes, hvilket er en hel del kortere end tidligere nævnte kode:
Følgende #include bør indsættes i toppen af dosing.c, fx. nede under #include <avr/io.h>:
#include <avr/pgmspace.h>
Dernæst, et eller andet sted, du synes er passende:
uint8_t calculateSetting(uint16_t aValue)
{
const uint16_t convTab[] PROGMEM = { ((1023 + 523) / 2), ((523 + 349) / 2), ((349 + 262) / 2), ((262 + 209) / 2),
((209 + 174) / 2), ((174 + 149) / 2), ((149 + 131) / 2), ((131 + 116) / 2), ((116 + 105) / 2), ((105 + 95) / 2),
((95 + 87) / 2), ((87 + 80) / 2), ((80 + 75) / 2), ((75 + 70) / 2), ((70 + 65) / 2), 0 }; /* 3K9, 5% */
uint8_t i;
i = 0;
while(aValue <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
Tabellen er beregnet efter VCC=5.00V, men den er checket op ad VCC=5.05V med en 3K9, 5% modstand (3k86), og værdierne er ikke langt fra hinanden.
Include-filen pgmspace.h indeholder macroer, som gør det nemt at læse tabeller der ligger i Flash-memory, frem for at de først lægges over i RAM. Dette sparer både RAM og reducerer kode-størrelsen.
... Koden er afprøvet som program på min Mac, og giver korrekte resultater.
Tabellen er baseret på en 3K9 modstand.
Her er en tabel, som er baseret på en 4K/0% modstand. Du kan fx. bruge 2 stk. 2K modstande på 1% ved udgangen:
const uint16_t convTab[] PROGMEM = { ((1023 + 512) / 2), ((512 + 341) / 2), ((341 + 256) / 2), ((256 + 205) / 2),
((205 + 171) / 2), ((171 + 146) / 2), ((146 + 128) / 2), ((128 + 114) / 2), ((114 + 102) / 2), ((102 + 93) / 2),
((93 + 85) / 2), ((85 + 79) / 2), ((79 + 73) / 2), ((73 + 68) / 2), ((68 + 65) / 2), 0 }; /* 4K, 0% */
Tabellen er beregnet efter VCC=5.00V, men den er checket op ad en 4K, 1% modstand.
Det er bedst at bruge de beregnede tabeller, frem for en tabel baseret på målte værdier, fordi hvis du har en modstand der ligger på -5%, og du baserer værdien på denne, vil du få fejl, den dag du bruger en modstand på +5%. Er tabellen baseret på +0%, vil du undgå fejlen.
Her er et par små formler, som du kan bruge til at beregne tabellerne:
voltage = VCC * 1000 / ((r1 + r2) / 8) / (setting + 1)
* 1000 er for at lave volt om til millivolt
r1 er din udgangs-modstand i ohm, fx. 3900.
r2 er værdien på den største modstand der sidder til en switch i ohm, fx. 4000.
setting er et tal mellem 0 og 15.
Her går jeg selvfølgelig ud fra, at modstandsværdierne bliver halveret efterhånden som der sættes flere switches på.
Formlen kan skrives om til en lidt mere lommeregner-venlig udgave:
voltage = VCC * 1000 * 8 / (r1 + r2) / (setting + 1)
Du kan så omregne voltage til ADC værdi:
adcValue = 1023 * voltage / 5
Formlerne kan reduceres yderligere og de kan også kombineres til én formel der giver dig adcValue.
og tusind tak for arbejdet med dip readings
Velbekomme. :)
Jeg ved ikke hvorfor jeg ikke kan få metoden ind i hovedet.
Jeg forstår godt de enkelte elementer men kan ikke se hvordan jeg får dem til at arbejde sammen.
skriver lige et eksempel som jeg vil have det til at hænge sammen i mit hoved
Først laver jeg en linje der hedder
#define ADC1_CALIBRATION;
jeg går ud fra at der skal stå et eller andet efter ADC1_CALIBRATION der på en eller anden måde skal kædes en værdi mellem 0 og 1023 til dette
denne værdi kan jeg finde ved eks den value der beregnes på nedenstående linje.
int16_t value = ((1023 * getADC1Value()) / 5000)
meeen så giver det ikke mening for mig at skulle bruge #define ADC1_CALIBRATION for den er vel ikke mere konstant end Int16_t value
nå men videre med mine tanker nu har jeg så fundet en værdi mellem 0 og 1023 og så mener jeg at kunne forstå at den så skal kædes til en case
som eks
uint8_t calculateSetting(uint16_t aValue)
{
const uint16_t convTab[] PROGMEM = { ((1023 + 523) / 2), ((523 + 349) / 2), ((349 + 262) / 2), ((262 + 209) / 2),
((209 + 174) / 2), ((174 + 149) / 2), ((149 + 131) / 2), ((131 + 116) / 2), ((116 + 105) / 2), ((105 + 95) / 2),
((95 + 87) / 2), ((87 + 80) / 2), ((80 + 75) / 2), ((75 + 70) / 2), ((70 + 65) / 2), 0 }; /* 3K9, 5% */
uint8_t i;
i = 0;
while(aValue <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
uden at væer sikker på det skulle jeg så mene at uint16_t aValue er et tal mellem 0 og 15 (har husket #include <avr/pgmspace.h> i starten )
her efter må jeg så skulle have nogle if/else sætninger der forbinder uint16_t aValue til antal doseringer og antal repeats og dem kan jeg godt finde ud af at lave
er det helt ude i skoven ?? jeg har svært ved at se hvad jeg skal bruge linjen #define ADC1_CALIBRATION; til
Jeg ved ikke hvorfor jeg ikke kan få metoden ind i hovedet.
Vi prøver da lige at kigge på det. :)
Først får du lige to #defines
#define ADC_TO_MILLIVOLT(a) ((5000 * (a)) / 1023)
#define MILLIVOLT_TO_ADC(a) ((1023 * (a)) / 5000)
Den første tager en værdi du får fra ADC'en og konverterer den til millivolt.
Nummer to tager en millivolt-værdi og konverterer den til en ADC-værdi (denne kan fx. bruges til sammenligninger).
Fx. hvis du vil se om ADC'en måler over 2500 mV:
if(getADC#Value() >= MILLIVOLT_TO_ADC(2500))
{
/* 2500 mV or more */
}
else
{
/* less than 2500 mV */
}
Jeg forstår godt de enkelte elementer men kan ikke se hvordan jeg får dem til at arbejde sammen.
skriver lige et eksempel som jeg vil have det til at hænge sammen i mit hoved
Jeg tror du er kommet ind i lidt forvirring, derfor springer jeg lidt frem.
nå men videre med mine tanker nu har jeg så fundet en værdi mellem 0 og 1023 og så mener jeg at kunne forstå at den så skal kædes til en case
getADC#Value() giver dig en værdi mellem 0 og 1023. Denne kan du overføre direkte til calculateSetting uden at skulle regne den om.
som eks
uint8_t calculateSetting(uint16_t aValue)
{
const uint16_t convTab[] PROGMEM = { ((1023 + 523) / 2), ((523 + 349) / 2), ((349 + 262) / 2), ((262 + 209) / 2),
((209 + 174) / 2), ((174 + 149) / 2), ((149 + 131) / 2), ((131 + 116) / 2), ((116 + 105) / 2), ((105 + 95) / 2),
((95 + 87) / 2), ((87 + 80) / 2), ((80 + 75) / 2), ((75 + 70) / 2), ((70 + 65) / 2), 0 }; /* 3K9, 5% */
uint8_t i;
i = 0;
while(aValue <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
uden at væer sikker på det skulle jeg så mene at uint16_t aValue er et tal mellem 0 og 15 (har husket #include <avr/pgmspace.h> i starten )
Rettelse:
Du giver et tal til calculateSetting, som er mellem 0 og 1023. Den giver dig et stabilt resultat som ligger mellem 0 og 15.
her efter må jeg så skulle have nogle if/else sætninger der forbinder uint16_t aValue til antal doseringer og antal repeats og dem kan jeg godt finde ud af at lave
Fint. :)
er det helt ude i skoven ?? jeg har svært ved at se hvad jeg skal bruge linjen #define ADC1_CALIBRATION; til
ADC1_CALIBRATION har jeg erstattet med MILLIVOLT_TO_ADC og ADC_TO_MILLIVOLT, i tilfælde at du vil bruge dem til potmetret.
-Men faktisk vil det nok være en god idé, at lave en funktion der ligner calculateSetting, bare med andre værdier, som passer til potmetret, så nej, du behøver ikke de to macroer.
Hvis du har sat 2 switches på ADC1, burde du kunne gøre noget i stil med:
uint8_t setting;
setting = calculateSetting(getADC1Value());
switch(setting)
{
case 0:
...
break;
case 1:
...
break;
case 2:
...
break;
case 3:
...
break;
}
Hvis du har de 4 switches på en og samme ADC, burde du kunne gøre følgende:
uint8_t setting;
uint8_t repeats;
uint8_t dosingCount;
setting = calculateSetting(getADC1Value());
dosingCount = (setting >> 2) % 0x03;
repeats = setting & 0x03;
...
...
Oki så prøver jeg.
kan jeg gøre det på denne måde (kun lavet for dips og StartDosint skal også laves om) ?
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "Timer1.h"
#define ADC_TO_MILLIVOLT(a) ((5000 * (a)) / 1023)
#define MILLIVOLT_TO_ADC(a) ((1023 * (a)) / 5000)
int8_t a;
int8_t repeats=1; /*Must be altered to represent the dip for repeats*/
uint8_t setting;
int16_t starting = getADC3Value(); /* controlls if startbutton is pushed*/
uint16_t aValue = getADC1Value();
/*ADC TABELS START*/
uint8_t calculateSettingDip(uint16_t aValue)
{
const uint16_t convTab[] PROGMEM = { ((1023 + 512) / 2), ((512 + 341) / 2), ((341 + 256) / 2), ((256 + 205) / 2),
((205 + 171) / 2), ((171 + 146) / 2), ((146 + 128) / 2), ((128 + 114) / 2), ((114 + 102) / 2), ((102 + 93) / 2),
((93 + 85) / 2), ((85 + 79) / 2), ((79 + 73) / 2), ((73 + 68) / 2), ((68 + 65) / 2), 0 }; /* 4K, 0% */
uint8_t i;
i = 0;
while(aValue <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
/*ADC TABELS END*/
/* CONDITIONS START*/
setting = calculateSetting(getADC1Value());
/* REPEATS */
if (setting == 1)||(setting == 5)||(setting == 9)||(setting == 13)
{
repeats = 10
}
else if (setting == 2)||(setting == 6)||(setting == 10)||(setting == 14)
{
repeats = 4
}
else if (setting == 3)||(setting == 7)||(setting == 11)||(setting == 15)
{
repeats = 2
}
else if (setting == 4)||(setting == 8)||(setting == 12)
{
repeats = 1
}
/* DOSINGS */
if (setting == 4)||(setting == 5)||(setting == 6)||(setting == 7)
{
setMinuteCountdown(24 * 60);
}
else if (setting == 8)||(setting == 9)||(setting == 10)||(setting == 11)
{
setMinuteCountdown(12 * 60);
}
else if (setting == 12)||(setting == 13)||(setting == 14)||(setting == 15)
{
setMinuteCountdown(6 * 60);
}
else if (setting == 1)||(setting == 2)||(setting == 3)
{
setMinuteCountdown(2 * 60);
}
/* CONDITIONS END*/
/*FUNCTIONS START*/
void startDosing() /* this routine should be called when the 'start' button is pressed */
{
int16_t adcValue;
int8_t dosing;
adcValue = getADC2Value(); /* read value of ADC channel 2 */
adcValue = adcValue + ADC2_CALIBRATION; /* add calibration value */
dosing = ((adcValue * 4) >> 10) + 1; /* calculate the dosing (we'll get a value from 1 to 4) */
setDosing(dosing); /* when this routine is called, the dosing countdown starts */
}
void timer0Elapsed()
{
uint8_t a;
uint16_t adc2Value;
uint8_t Unload;
Unload =0; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
startServo(200, adc2Value);
/* unload: */
waitSeconds(10);
startServo(200, Unload);
}
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
Men faktisk vil det nok være en god idé, at lave en funktion der ligner calculateSetting
også når opløsningen skal være minimum 256, så bliver det jo noget af en tabel
(jeg ved godt at kommentarene ikke er alignet, men det er de i avrstudio) :P
Oki så prøver jeg.
kan jeg gøre det på denne måde (kun lavet for dips og StartDosint skal også laves om) ?
Det ser rimelig fornuftigt ud, du er på rette kurs.
Du har dog ikke 'if(setting == 0)' med, hvilket du nok gerne vil have, så du er sikker på at have en indstilling for alle de mulige dip-switch kombinationer der er.
Selvfølgelig skal du også have conditions lagt ind i de rigtige funktioner, men det regner jeg med at du har styr på.
Hvis du vil have variabler liggende udenfor funktioner, vil jeg anbefale at du skriver 'volatile' foran dem, for ellers kan du få bøvl med at 'de ikke altid virker'. Dette har med at gøre, at de er cached, og hvis de bliver ændret af et interrupt, så vil cachen ikke blive opdateret.
Så...
int8_t a;
int8_t repeats=1;
uint8_t setting;
int16_t starting = getADC3Value();
uint16_t aValue = getADC1Value();
bør så ændres til...
volatile int8_t a;
volatile int8_t repeats=1;
volatile uint8_t setting;
volatile int16_t starting = getADC3Value();
volatile uint16_t aValue = getADC1Value();
Men jeg regner med, at du egentlig ikke ønsker at have disse som globale variabler.
Grunden til at jeg et sted har kaldt en variable for 'aValue' i stedet for 'value', er at A'et står for "Argument".
Dette er en kode-standard, som jeg lærte da jeg arbejdede på samme kode som omkring 20 andre programmører, så jeg vil anbefale at du starter alle argumenter med lille 'a' og derefter laver 'kamel-pukler' med uppercase for hver gang der starter et nyt ord, fx.
int16_t myFunction(uint8_t *aMessageText, uint16_t aMessageLength)
{
...
}
Og derfor også undgår at variabler, der ikke er argumenter, ikke starter med lille a efterfulgt af stort bogstav, fx...
int16_t myFunction(uint8_t *aMessageText, uint16_t aMessageLength)
{
uint8_t apples = 5; /* good naming convention */
uint8_t aLotOfTrees = 1; /* bad naming convention */
}
Men faktisk vil det nok være en god idé, at lave en funktion der ligner calculateSetting
også når opløsningen skal være minimum 256, så bliver det jo noget af en tabel
Uhm, jeg regnede ikke med at dit potmeter skulle have så fin indstilling. Men det er muligt at det stadig vil være en god idé.
Du vil finde ud af om det er nødvendigt eller ej, for jeg husker ikke om potmetret er linært eller logaritmisk.
(jeg ved godt at kommentarene ikke er alignet, men det er de i avrstudio) :P
Gør ikke mig noget. Jeg alignede dem i mine eksempler, så du kan se at det bliver pænt læsbart, når de er det. :)
Husk: Hvis du får compile-fejl, så er du nødt til at rette den første fejl, som bliver rapporteret først. For fejl, som følger efter første fejl, kan være forårsaget af netop den første fejl.
Det kan være en god idé, at ændre 5-10 linier ad gangen, for ikke at blive overfuset af fejl-meddelelser.
Lige et hurtigt spørgsmål
kan lokale variabler godt erklæres ude i starten så?
eks int8_t a;
virker den så godt nok i den rutine hvor den skal bruges (og der hvor værdien tillægges)?
eller med andre ord er det kun når værdien af variabler skal bruges i rutiner at det er et problem
eks int8_t a = 10;
så bør det være volatile int8_t a = 10;
Prøver lige igen :P
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "Timer1.h"
/* #define ADC_TO_MILLIVOLT(a) ((5000 * (a)) / 1023)*/ /* maby use tabel instead*/
/* #define MILLIVOLT_TO_ADC(a) ((1023 * (a)) / 5000)*/ /* maby use tabel instead*/
int8_t a;
int16_t starting = getADC3Value(); /* controlls if startbutton is pushed*/
volatile uint16_t aValue = getADC1Value(); /* value assigned to aValue for use in calculateSettingDip*/
volatile int8_t setting = calculateSetting(getADC1Value()); /*Declare the global var setting for use in start dosing and timer0elapsed*/
if (starting > 0) /* if start button is pressed the system starts by calling startDosing*/
{
startDosing();
}
/*ADC TABELS START*/
uint8_t calculateSettingDip(uint16_t aValue)
{
const uint16_t convTab[] PROGMEM = { ((1023 + 512) / 2), ((512 + 341) / 2), ((341 + 256) / 2), ((256 + 205) / 2),
((205 + 171) / 2), ((171 + 146) / 2), ((146 + 128) / 2), ((128 + 114) / 2), ((114 + 102) / 2), ((102 + 93) / 2),
((93 + 85) / 2), ((85 + 79) / 2), ((79 + 73) / 2), ((73 + 68) / 2), ((68 + 65) / 2), 0 }; /* 4K, 0% */
uint8_t i;
i = 0;
while(aValue <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
/*ADC TABELS END*/
/*FUNCTIONS START*/
void startDosing() /* this routine starts when the 'start' button is pressed */
{ /* setMinuteCountdown initiated corrosponding to dip setting*/
if (setting == 4)||(setting == 5)||(setting == 6)||(setting == 7)
{
setMinuteCountdown(24 * 60);
}
else if (setting == 8)||(setting == 9)||(setting == 10)||(setting == 11)
{
setMinuteCountdown(12 * 60);
}
else if (setting == 12)||(setting == 13)||(setting == 14)||(setting == 15)
{
setMinuteCountdown(6 * 60);
}
else if (setting == 1)||(setting == 2)||(setting == 3)
{
setMinuteCountdown(2 * 60);
}
timer0Elapsed(); /* Calls timer0Elapsed to perform first dosing */
setDosing(dosing); /* when this routine is called, the dosing countdown starts */
}
void timer0Elapsed() /* the dosing rutine*/
{
if (setting == 1)||(setting == 5)||(setting == 9)||(setting == 13)
{
repeats = 10
}
else if (setting == 2)||(setting == 6)||(setting == 10)||(setting == 14)
{
repeats = 4
}
else if (setting == 3)||(setting == 7)||(setting == 11)||(setting == 15)
{
repeats = 2
}
else if (setting == 4)||(setting == 8)||(setting == 12)||(setting == 0)
{
repeats = 1
}
uint8_t a;
uint16_t adc2Value;
uint8_t Unload;
Unload =0; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
startServo(200, adc2Value);
/* unload: */
waitSeconds(10);
startServo(200, Unload);
}
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
/*FUNCTIONS END*/
er i tvivl om værdien setMinuteCountdown bliver sendt til Timer0.c
og så mangler jeg self lige pot reading men den tygger jeg lige på for 256 er måske nok i overkanten.
skal maks dosere 5 ml så det må være riligt med en opløsning på 20 svarende til ca 0,25 ml og med en tabel er det jo let at sætte range (de ms svarende til at rykke servoen) med andre ord så kan jeg sætte range på potten til kun at inkluderer værdier der giver mening i forhold til de grader der er til rådighed for den begrænset bevægelse af servoen i forhold til fuldt udsving på 180o jeg skal jo kun bruge en bevægelse på max 90o Håber det gav mening :P
Lige et hurtigt spørgsmål
kan lokale variabler godt erklæres ude i starten så?
eks int8_t a;
virker den så godt nok i den rutine hvor den skal bruges (og der hvor værdien tillægges)?
Lokale variabler erklæres inde i den funktion de skal bruges.
Erklæres variabler udenfor en funktion er de altid globale.
eller med andre ord er det kun når værdien af variabler skal bruges i rutiner at det er et problem
eks int8_t a = 10;
så bør det være volatile int8_t a = 10;
Hvis en variabel er erklæret global, og den bruges af 2 forskellige rutiner, er det en god idé at have den volatile.
Hvis den kun bruges af én rutine, er der normalt ikke grund til at have den som global.
Men hvis du vil have den tekniske forklaring, vil jeg bruge dette program som eksempel:
uint8_t seconds;
uint8_t minutes;
uint8_t hours;
void clock()
{
if(seconds++ >= 60)
{
seconds = 0;
if(minutes++ >= 60)
{
hours++;
}
}
}
void wait()
{
uint8_t oldSeconds;
oldSeconds = seconds;
while(oldSeconds == seconds)
{
}
}
Vores rutine 'clock' er kaldt under udførsel af interrupt. Om den er kaldt direkte af interruptet, eller af interrupt-funktionen er ligegyldigt, resultatet vil blive det samme.
Vores 'wait' rutine læser værdien 'seconds' og gemmer værdien i 'oldSeconds', for at vi kan se hvornår der er sket en ændring.
Næste ting, er at vi venter til seconds har ændret sig.
Compileren er så smart, at den optimerer vores program.
Da den kan se, at vi aflæser 'seconds' to gange lige efter hinanden, vil den for det første sige: Fint, dette tager jeg som én aflæsning, så det vil være det samme som:
uint8_t temp;
temp = seconds;
oldSeconds = temp;
while(oldSeconds != temp)
{
}
-Fordi compileren ved ikke at der nu og da sker et interrupt, som ændre i seconds.
Men hvis vi nu ovenover erklærer seconds som volatile, vil koden virke som forventet.
Dvs. hvis en variabel ændrer værdi udenfor den funktion, som den aflæses i, imens denne funktion køres, bør den være volatile.
Dette sker normalt kun i interrupts, men der er faktisk et andet eksempel.
Alle microcontrollerens porte er erklæret volatile, fordi ellers ville første aflæsning blive cached, og så har vi et input-ben som ikke ser ud til at virke. ;)
Sidebemærkning:
Du har sikkert set at der står...
Og måske undret dig lidt over hvornår 'seconds' bliver sammenlignet med 60.
Når ++ står efter variablen, hedder det post-increment.
Når -- står efter variablen, hedder det post-decrement.
Når ++ står før variablen, hedder det pre-increment.
Når -- står før variablen, hedder det pre-decrement.
(increment = tælle opad, decrement = tælle nedad)
I while og for-løkker bruger man normal post-increment eller post-decrement.
Ved pointere bruger man normal post-increment og pre-decrement; det er sjældent at det er logisk/smart at gøre andet; men der kan komme situationer, hvor det er praktisk at bruge pre-increment eller post-decrement.
Konklusion. Vi kunne have lavet koden ovenover anderledes, fx.
eller
...men personligt synes jeg det andet giver bedre læsbarhed/pænere kode.
Et eksempel på post-decrement og pre-decrement i en while-løkke:
i = 10;
while(i--) /* post-decrement */
{
/* Here, i has the values 9, 8, 7, 6, 5, 4, 3, 2, 1, 0. */
}
i = 10;
while(--i) /* pre-decrement */
{
/* Here, i has the values 9, 8, 7, 6, 5, 4, 3, 2, 1. */
}
Prøver lige igen :P
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "Timer1.h"
/* #define ADC_TO_MILLIVOLT(a) ((5000 * (a)) / 1023)*/ /* maby use tabel instead*/
/* #define MILLIVOLT_TO_ADC(a) ((1023 * (a)) / 5000)*/ /* maby use tabel instead*/
int8_t a;
int16_t starting = getADC3Value(); /* controlls if startbutton is pushed*/
volatile uint16_t aValue = getADC1Value(); /* value assigned to aValue for use in calculateSettingDip*/
volatile int8_t setting = calculateSetting(getADC1Value()); /*Declare the global var setting for use in start dosing and timer0elapsed*/
if (starting > 0) /* if start button is pressed the system starts by calling startDosing*/
{
startDosing();
}
Her er et par ting, som du måske ikke har lagt mærke til:
1: Hvornår skal 'starting' aflæses ?
2: Hvornår skal 'aValue' aflæses ?
3: Hvornår skal koden 'if(starting > 0)' bruges ?
Hvis du kunne få lov til at lave ovenstående kode, ville starting blive indstillet én gang, aValue ville blive indstillet én gang, og disse ville så begge være indstillet i starten af dit program, og derefter vil de kun ændre værdi, hvis du har en funktion, som ændrer værdien.
Koden 'if(starting > 0)' ville (igen, hvis du fik lov til det), kun udføres én gang, nemlig under opstart.
Hvis du har globale variabler, som skal tilskrives en startværdi (initialiseres), bør det være en konstant, for at du ikke kommer ud i noget skidt.
Altså, en god idé ville være enten:
uint16_t gStarting = 0; /* uint16_t, since getADC1Value() returns an uint16_t */
uint16_t gValue = 0;
Læg mærke til at jeg har ændret 'prefix', dvs. starten på 'starting' og 'aValue'. g står for 'global'.
Fordi: Hvis du nu havde en funktion, fx. 'calculateSettingDip(uint16_t aValue)', og du skulle bruge ovenstående 'aValue', ville du ikke kunne 'få fat i den', da argumentet har samme navn som din globale variabel.
void timer0Elapsed() /* the dosing rutine*/
{
if (setting == 1)||(setting == 5)||(setting == 9)||(setting == 13)
{
repeats = 10
}
else if (setting == 2)||(setting == 6)||(setting == 10)||(setting == 14)
{
repeats = 4
}
else if (setting == 3)||(setting == 7)||(setting == 11)||(setting == 15)
{
repeats = 2
}
else if (setting == 4)||(setting == 8)||(setting == 12)||(setting == 0)
{
repeats = 1
}
uint8_t a;
uint16_t adc2Value;
uint8_t Unload;
Unload =0; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
startServo(200, adc2Value);
/* unload: */
waitSeconds(10);
startServo(200, Unload);
}
}
Compileren vil sikkert brokke sig her.
Prøv at holde alle erklæringer i toppen af dine rutiner/funktioner.
Fx.
void timer0Elapsed() /* the dosing rutine*/
{
uint8_t a;
uint16_t adc2Value;
uint8_t Unload;
if (setting == 1)||(setting == 5)||(setting == 9)||(setting == 13)
{
repeats = 10
}
else if (setting == 2)||(setting == 6)||(setting == 10)||(setting == 14)
{
repeats = 4
}
else if (setting == 3)||(setting == 7)||(setting == 11)||(setting == 15)
{
repeats = 2
}
else if (setting == 4)||(setting == 8)||(setting == 12)||(setting == 0)
{
repeats = 1
}
Unload =0; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
startServo(200, adc2Value);
/* unload: */
waitSeconds(10);
startServo(200, Unload);
}
}
-For så er du sikker på at det vil virke med alle compilere.
Dette er fordi der gøres plads til variabler når funktionen starter.
Enkelte compilere tillader dette som standard, men de fleste compilere skal have særlige indstillinger til at tillade dette, hvis de er i stand til at håndtere det.
er i tvivl om værdien setMinuteCountdown bliver sendt til Timer0.c
Tarveligt spørgsmål: Hvorfor er du i tvivl om dette ? :)
og så mangler jeg self lige pot reading men den tygger jeg lige på for 256 er måske nok i overkanten.
skal maks dosere 5 ml så det må være riligt med en opløsning på 20 svarende til ca 0,25 ml og med en tabel er det jo let at sætte range (de ms svarende til at rykke servoen) med andre ord så kan jeg sætte range på potten til kun at inkluderer værdier der giver mening i forhold til de grader der er til rådighed for den begrænset bevægelse af servoen i forhold til fuldt udsving på 180o jeg skal jo kun bruge en bevægelse på max 90o Håber det gav mening :P
Jep. :)
Desuden skal du lige have en forskrækkelse
havde regnet med noget i den stil men var ikke klar over om den gik ind i og udførte rutiner også. regnede bare med at den holdt sig uden for disse for det går jo ikke at den laver en eller anden tilfældig dosering eller for den sags skyld trækker et par tilfældige antal ml op i sprøjten, inden programmet "starter"
Du kan selvfølgelig også være heldig at den nogle gange vil starte op i noget junk-kode som ikke har med din kode at gøre. :)
(dvs. resten af Flash-hukommelsen, hvor din kode ikke ligger), men hver gang microcontrolleren startes, udføres kode et tilfældigt sted i Flash-hukommelsen.
Chancen for at den laver tilfældige doseringer er nu ikke så stor.
Men der er en løsning, en rigtig god og sikker løsning, endda.
I dokumentationen for ATtiny24/44/84 står at det aller første som sker, når en ATtiny starter/resettes, er at alle I/O-registre bliver indstillet til en fast start-værdi. (Fx. alle i/o-porte bliver sat til input, så de ikke pludselig 'lægger arm' med noget andet hardware).
Kigger vi i ATtiny24/44/84's I/O-register liste, finder vi lidt forskellige registre, som ikke skal bruges i dit tilfælde.
Dem kunne vi selvfølgelig bruge, men der er noget endnu bedre, for Atmel har nemlig reserveret 3 registre helt til dig.
Disse 3 registre hedder GPIOR0, GPIOR1 og GPIOR2. Det er således dig selv, som bestemmer hvad disse skal bruges til.
Til at starte med, sættes disse til værdien 0 af microcontrolleren, før noget kode køres.
I Servo.h indsætter du følgende #define:
#define SERVO_ENABLED 0xa8 /* our lucky number or something else, just not zero! */
I main.c, lige under #include "main.h", bør du så have...
void startButtonPushed()
{
gSettings = calculateSettings(getADC1Value()); /* update our settings */
/* ... perform other needed actions as needed ... */
}
int main()
{
/* ... declared variables ... */
GPIOR2 = SERVO_ENABLED; /* This must be the first code executed; no code goes before it, and GPIOR2 is not to be changed from now on. */
/* initialize global variables after the above line, but before the main loop, and ofcourse, before they're used. */
/* ... initialization code: initADC(); initServo(); initTimer0(); etc... */
/* Now we can use the ADC to get our initial settings: */
startButtonPushed(); /* this line must be placed after the ADC is initialized, otherwise you won't get the right value. */
/* ...main loop... */
}
Og så i rutinen startServo (som jeg regner med ligger i Servo.c), finder du den linie, som er årsag til at servoen starter (servoen må ikke kunne starte uden den linie), og sætter følgende kode rundt om:
if(GPIOR2 == SERVO_ENABLED)
{
/* the line of code that starts the servo, and nothing else. */
}
Lidt forklaring:
Lad os antage at tilfældig kode bliver udført.
Rammer vi ind i den allerførste linie, som udføres, kan skaden ikke blive ret stor, fordi så bliver alt initialiseret som det skal, og alting kører som vi forventer.
Det kan være en rigtig god idé, at undgå at tilegne globale variabler en start-værdi, og så i stedet indstille denne start-værdi *efter* GPIOR2 = SERVO_ENABLED; - derved er du 100% sikker på at intet går galt.
Men hvis nu GPIOR2 = SERVO_ENABLED; står efter al initialisering, så er der mulighed for at kun denne linie bliver udført, og så har vi faktisk ikke fået initialiseret vores program, men i stedet har vi fået 'fortalt en løgn'. Derfor er det nødvendigt at der aldrig skrives noget kode før GPIOR2 = SERVO_ENABLED; linien.
Det samme med if(GPIOR == SERVO_ENABLED) - inde i denne if-sætning, bør der være så lidt kode som overhovedet muligt. Helst kun lige den linie, som er årsagen til at servoen starter, dvs. hvis du bruger PWM, så den enkelte kommando der skriver til I/O-registret der starter PWM'en.
Du kunne sådan set sætte if(GPIOR == SERVO_ENABLED) omkring hver eneste linie, som ændrer ved et I/O-register der har med servo'en at gøre.
Så fik jeg lavet rettelserne og der dukkede et par nye op i compileren, men de er rettet og den compiler nu uden fejl næsten. ;D
i dosing.c har jeg tilføjet linjen
volatile uint8_t gSettings;
men får fejlen
Error 1 conflicting type qualifiers for 'gSettings'
med henvisning til en anden rettelse i dosing.h hvor du skriver jeg bør sætte følgende.
extern uint8_t gSettings;
hvis jeg fjerner volatile foran uint8_t forsvinder fejlen.
Kan du i Dosing.h lave følgende...
extern volatile uint8_t gSettings;
og i Dosing.c ...
volatile uint8_t gSettings;
-Hvis den godtager dette, vil jeg anbefale det på dén måde.
endvidere får jeg også en fejl i main.c
gSettings = calculateSettingsDip(getADC1Value());
undefined reference to `calculateSettingsDip' og det samme til getADC1Value
i funktione
void startButtonPushed()
{
gSettings = calculateSettingsDip(getADC1Value()); /* update our settings */
/* ... perform other needed actions as needed ... */
}
så kommenterer den lige ud til jeg ved hvorfor.
calculateSettingsDip bliver jo defined i dosing.c og uint16_t getADC1Value(); i adc.h
Prøv at se om den i Dosing.h hedder noget andet end calculateSettingsDip.
Det kan være den dér også skal rettes.
Hvis compileren derefter brokker sig over calculateSettingsDip i Dosing.c, skal denne så også rettes, så de passer til hinanden.
Bemærk, at i headerfilen skal linien have et semikolon til slut, fx.
uint8_t calculateSettingsDip(uint16_t gDipSwitches);
Du vil sikkert gerne have tallet i grader, så vidt jeg kan forstå.
nej det behøver ikke at være i grader. tallet repræsenterer bare grader men skal vel egentligt være en definition på længden af high (0.8 - 2.2 ms) i 20 ms cyclen
Jeg er desværre ikke helt klar i hovedet i dag; jeg har gået influenza, så jeg kan ikke så nemt koncentrere mig om disse ting... :-\
Jeg er stadig igang med det tidligere nævnte projekt, men vil lige sende dig lidt...
Da jeg kiggede på Dosing.c, kom jeg til at tænke på at koden kan reduceres en smule:
void timer0Elapsed()
{
static const uint8_t repTable[] PROGMEM = { 1, 10, 4, 2 };
uint8_t repeats;
uint8_t a;
uint16_t rotation;
uint8_t unload;
unload = 0; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
repeats = pgm_read_byte(&repTable[gSettings & 0x03]);
for(a = 0; a < repeats; a++)
{
/* load: */
waitSeconds(10);
rotation = getADC2Value();
startServo(200, rotation);
/* unload: */
waitSeconds(10);
startServo(200, unload);
}
}
Som du ser, er alle 'if' sætningerne erstattet med et enkelt tabel-opslag.
Dette kan vi gøre, da vi med bit-AND filtrerer de to nederste bits ud (dvs. bit 0 og bit 1).
Det samme system kan bruges i startDosing:
void startDosing() /* this routine starts when the 'start' button is pressed */
{ /* setMinuteCountdown initiated corrosponding to dip setting*/
static const uint16_t timeTable[] PROGMEM = { 2 * 60, 24 * 60, 12 * 60, 6 * 60 };
setMinuteCountDown(pgm_read_word(&timeTable[(gSettings >> 2) & 0x03]));
timer0Elapsed();
}
Hej igen. Det er godt nok ved at blive en lang tråd det her. Ved ikke helt om det er pinligt he he
har prøvet at se på pin change interrupt, til startknappen, og her er hvad jeg kom frem til.
Har smidt det hele i main.c
int main()
{
GPIOR2 = SERVO_ENABLED; /* This must be the first code executed; no code goes before it, and GPIOR2 is not to be changed from now */
initADC(8); /* initialize ADC, we'll use the 8-bit precision for now */
initTimer0();
initDosing();
initServo();
sei(); /* globally enable interrupts */
PCMSK0 |= (1<<PCINT3); /*Set PA3 as the pin to use*/
MCUCR = (1<<ISC01); /*interrupt on INT0 pin falling edge*/
GIMSK |= (1<<INT0); /*turn on interrupts*/
while(1)
{
/* we don't really need to do anything here. Everything is handled by the interrupts */
}
return(0); /* (never reached) */
}
SIGNAL (SIG_PCINT3)
{
gSettings = calculateSettingsDip(getADC1Value()); /* update our settings */
}
Vil det virke ??
Er ikke lige sikker på det med rising, faling eller any logical change, i forhold til MCUCR
Læste et andet sted at man brugte en internal pull up og så havde knappen på fra jord til PA3 men ved ikke om attiny også kan det og om det overhovedet kan betale sig.
Hej igen. Det er godt nok ved at blive en lang tråd det her. Ved ikke helt om det er pinligt he he
Det synes jeg ikke. Tværtimod; der vil nok være mulighed for at andre kan få løst deres problemer, ved at kigge lidt i tråden. :)
Desuden er du snart kommet hele turen rundt i microcontrolleren. Der er selvfølgelig stadig nogle ting du ikke har prøvet, fx. UART/USART (serielt interface) og nogle andre småting, men det meste af hvad microcontrolleren kan, har du snuset til nu; og så har du også prøvet at bruge interrupts, hvilket er noget de fleste venter med, til der er gået lidt over et år.
-Men interrupts er gode at bruge, hvis man har muligheden.
har prøvet at se på pin change interrupt, til startknappen, og her er hvad jeg kom frem til.
Har smidt det hele i main.c
PCMSK0 |= (1<<PCINT3); /*Set PA3 as the pin to use*/
MCUCR = (1<<ISC01); /*interrupt on INT0 pin falling edge*/
GIMSK |= (1<<INT0); /*turn on interrupts*/
Åbn først dette datablad (http://www.atmel.com/dyn/resources/prod_documents/doc8183.pdf).
PCMSK0 ... ben 10 (PA3) enables, dette er som det skal være.
PCMSK0 finder du på side 52, dér står noget interessant omkring GIMSK.
GIMSK INT0 (ben 5, PB2) enables. Fint, men der er flere interrupts, som du gerne vil enable i dette I/O-register.
På side 50 finder du GIMSK, men det er først øverst på side 51, der står noget du skal bruge. :)
MCUCR ... bør ikke OR'es. Denne bør skrives på denne måde:
MCUCR = (1 << ISC01) | (0 << ISC00); /* interrupt on INT0 pin, falling edge */
-Fordi... Hvis MCUCR's laveste bit bliver sat et andet sted i koden, vil du få rising edge i stedet for falling.
(Det er ikke tilfældet i dette program, men hvis du senere plukker noget kode ud og bruger et andet sted, vil der gå ged i det).
SIGNAL (SIG_PCINT3)
{
gSettings = calculateSettingsDip(getADC1Value()); /* update our settings */
}
På side 47 ser du en oversigt over interrupt-vektorerne.
SIG_PCINT3 bliver aldrig kaldt. Se side 47 i databladet, der vil du se at der er kun PCINT0 og PCINT1 (dvs. SIG_PCINT0 og SIG_PCINT1). Hvilken én skal bruges ? :)
Derudover... Hvor er SIG_INTERRUPT0 ?
[/quote]Vil det virke ??[/quote]
Mjae, men ikke efter hensigten. ;)
Er ikke lige sikker på det med rising, faling eller any logical change, i forhold til MCUCR
Der er en ting mere omkring PINCHANGE interrupts...
Du bør sikre mod prel (glitches), for når brugeren trykker på knappen, vil du få mellem 10 og 50 interrupts!
Dette er fordi der springer gnister mellem kontakt-fladerne på knappen.
Du kan løse det på en let måde, eller en avanceret måde. Til at starte med, tror jeg det er bedst med den lette måde.
For at sikre mod prel, er det en god idé at finde ud af om knappen er trykket ned eller sluppet, derefter vente 5 ... 10 millisekunder, og så læse igen om den er trykket ned eller sluppet. Har den ændret værdi, så glem at der skete noget.
Pin-change interruptet bliver kaldt både når en knap trykkes ned, og når knappen slippes.
Derfor skal vi læse om benet er LOW, når vi kommer ind i vores pin-change interrupt, og derefter vente 5...10 millisekunder, og derefter læse benets værdi og se om det stadig er LOW...
SIGNAL (SIG_PCINT?)
{
uint8_t pinValue;
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(0 == pinValue) /* we're only interested if button is pressed */
{
_delay_ms(10); /* wait 10 ms */
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(0 == pinValue) /* only update settings when button value is stable */
{
gSettings = calculateSettingsDip(getADC1Value()); /* update our settings */
}
}
}
Læste et andet sted at man brugte en internal pull up og så havde knappen på fra jord til PA3 men ved ikke om attiny også kan det og om det overhovedet kan betale sig.
Det er faktisk dét de ar lavet til, så det kan du godt. :)
Dér skal du så sige...
DDRA &= (1 << DDRA3); /* set pin as input (you probably already set it to be input) */
PORTA |= (1 << PA3); /* enable internal pull-up resistor */
Det kan altid betale sig at spare komponenter (hvis altså kvaliteten af produktet ikke forringes) - for selv en SMD-modstand til 0.02 øre koster 1 kr. at montere for en maskine. Dertil kommer prisen på printet. Her koster hver kvadratmillimeter penge. Du finder ud af på et tidspunkt, at printplader er dyre at få lavet, og derfor vil du gerne have lavet dem så små som muligt; derved kan du få lavet en hel masse boards på samme plade (dette kaldes panelizing).
OK, det er helt fint, men hvis du har pulldown på, vil den normalt ligge LOW, hvilket vil sige at du skal kigge i dit PCINT0 om den er HIGH.
vil det sige at MCUCR skal være rising edge ?
Du kan ikke sætte MCUCR til at være rising edge på andet end INT0.
INT0 og PCINT0 er to forskellige ting.
PCINT0 og PCINT1 er af samme type. Disse to interrupts håndterer flere hver pins på samme tid.
INT0 interruptet har højere prioritet end PCINT interrupts. Du kan også styre det mere nøjagtigt, men der er kun én pin der er forbundet til dette interrupt, nemlig ben 5.
eller skal pinValue sættes til at være > 0
Jeg anbefaler at lave if-sætningen således (dvs. mit fokus er kun på den ene linie med 'if'):
if(pinValue)
{
/* pin is high, button is pressed */
}
else
{
/* pin is low, button not pressed */
}
-Undgå altså at bruge større-end, som nedenstående, når du sammenligner værdier du laver bit-operationer på...
if(pinValue > 0)
{
/* pin is high, button is pressed */
}
-Det først-nævnte er renere / bedre logik, for pinValue kan enten være nul eller ikke-nul.
Det har vi sikret os ved at lave en 'bitwise and' tidligere.
(Det er selvfølgelig ikke en katastrofe, da vi er ovre i bagatel-afdelingen, men efterhånden som man arbejder med kode, finder man mere og mere ud af, hvordan man undgår uheldige - næsten usynlige - fejl der sniger sig ind. Årsagen til min anbefaling er baseret på at man senere ændrer lidt i koden).
Hvis det er helt rigtigt, ville det være mest korrekt at sætte pinValue således:
pinValue = (PORTA >> PCINT3) & 1;
Så vil pinValue enten være 0 eller 1.
-Men den færdige binære fil vil fylde mere, end hvis du gør følgende:
pinValue = PORTA & (1 << PCINT3);
-Fordi compileren kan reducere (1 << PCINT3) til tallet 8, hvor der ellers skal indsættes ekstra maskinkode, for at få bitten ned på nederste position; her afhænger kodestørrelsen af hvor mange bitpladser bitten skal flyttes.
Jeg fik en fejl på Prel funktionen så lagde følgende linje ind i starten af main.c
#include <util/delay.h>
Det gav mig så en fejl om at F_CPU bliver redefined, da den bliver defined både i <util/delay.h> og <main.h>
så jeg lagde #include <util/delay.h> ned under #include <main.h>
Startkanp funktionen lavede jeg om til flg.
SIGNAL (SIG_PCINT0)
{
uint8_t pinValue;
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* we're only interested if button is pressed */
{
_delay_ms(10.); /* wait 10 ms */
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* only update settings when button value is stable */
{
startDosing();
}
}
}
Eneste ændring er at if sætningerne er ændret til
if(pinvalue)
istedet for
if(0==pinvalue)
samt at jeg smed et kald til startdosing() ind
Går ud fra at det løser problemet med at jeg bruger pull down og ikke pull up
Jeg går også ud fra at flg. bliver overflødigt i ADC.c
uint16_t getADC1Value()
{
return(getADCValue(1)); /* ADC value of startbutton */
}
Hvis ovenstående er korrekt så tror jeg kun at jeg mangler pot til PWM funktionen til servoen
Jeg fik en fejl på Prel funktionen så lagde følgende linje ind i starten af main.c
#include <util/delay.h>
Det gav mig så en warning (nogen gange) om at F_CPU bliver redefined, da den også bliver defined i <util/delay.h>
Ved ikke lige hvad jeg skal gøre ved den, for hvis jeg fjerner den fra main.c er der andre dele der brokker sig. Jeg kan self lave en #include <util/delay.h> i de andre .c som gør brug af F_CPU, men det virker som en lidt "beskidt" metode.
Det rigtige er at gå ind i din Makefile og definere den dér.
Godt råd:Lav en backup først!
Toppen af min Makefile ser nogenlunde sådan ud:
TARGET = LightControl-Test1
DEVICE = atmega164p
CLOCK = 8000000
Af disse linier skal du kun indsætte 'CLOCK = (frekvens)' i din Makefile.
Senere er der et sted, hvor compileren får sine parametre, linien kunne se nogenlunde således ud, men det er ikke sikkert...
COMPILE = avr-gcc -Wall -Os -mmcu=$(DEVICE)
Hvis den gør det, så indsæt '-DF_CPU=$(CLOCK)' nogenlunde således...
COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)
Startkanp funktionen lavede jeg om til flg.
{snip}
Går ud fra at det løser problemet med at jeg bruger pull down og ikke pull up
Det ser fint ud og burde løse det..
Hvis du ikke kan få defineret F_CPU i din Makefile, så kan du definere den før du inkluderer <avr/delay.h>; men det er korrekt at definere den i din Makefile, for så kan alle dele af source-koden se den.
Her er lidt advarsel om Makefile...
Der er forskel på hvad space og tab betyder, så hvis der er mellemrum, må du ikke erstatte dem med tabuleringer og er der tabuleringer må de ikke erstattes af mellemrum!
-Vældig uintuitivt, men gutterne der engang for længe siden opfandt Make mente at det var smart på det tidspunkt (hvilket det sikkert også var, så jeg vil ikke slå dem oveni hovedet).
Advarsel: Forfærdeligt langt indlæg, uha-uha-uha...
Tusind tak. her er de links jeg har kigget på
http://winavr.scienceprog.com/comment/52 (http://winavr.scienceprog.com/comment/52)
http://blog.roderickmann.org/2006/01/atmega32-pwm/ (http://blog.roderickmann.org/2006/01/atmega32-pwm/)
http://www.societyofrobots.com/member_tutorials/node/231 (http://www.societyofrobots.com/member_tutorials/node/231)
Den øverste kan jeg ikke helt greje om den er i c
De er alle 3 tutorials til en at mega men det er vel bare et spørgsmål om hvordan der addresseres
:) Sådan set er alle 3 i C, ikke assembler. =)
Jeg kan ikke selv afprøve noget kode, for jeg har ikke en servo-motor.
Men det, som er vigtigt, er at du finder ud af at regne frekvensen ud korrekt.
Derefter er det forholdsvis enkelt at sætte en timer op; så vidt jeg husker, valgte vi at have en 16-bit timer fri til PWM, hvilket vil sige at der er gode muligheder for rimelig præcise frekvenser.
Du skal altså have en rutine der regner frekvensen ud, når denne rutine får en eller anden form for input.
Du skal så også have en rutine, som starter PWM'en.
Hvis du kigger på det 3. eksempel (http://www.societyofrobots.com/member_tutorials/node/231) du sendte, vil du se den nederste kode-blok.
Han har skrevet koden til ATmega8, så hvis vi kigger i ATmega8's datablad, vil vi kunne finde ud af hvad det er han sætter op.
Det første han gør, er at slå PWM fra, mens det sættes op, det gør han her...
TCCR1A = 0;
Med andre ord: Han sætter disse bits til 0 i TCCR1A:
COM1A1, COM1A0, COM1B1, COM1B0, FOC1A, FOC1B, WGM11, WGM10
At han sætter alle COM1xx bit til 0, betyder at COMpare- (sammenlignings-) registrene slåes fra.
Jeg mener der er en bedre måde at gøre dette på, nemlig at sætte prescaleren til off (timer stopped), men det kommer lidt an på om man ønsker at timeren kører hele tiden.
Det næste han gør, er:
Dette sætter i hans tilfælde TOP-grænsen hvis man bruger Fast PWM (mode 14).
Han sætter så sin timer op til at køre Fast PWM:
TCCR1A = (1 << WGM11); /* WGM10 er her samtidig blevet sat til 0 */
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
Han sætter så samtidig CS10 til 1, og alle andre CSxx til 0, dvs...
CS12=0, CS11= 0, CS10=1: clk/1 (no prescaling)
Derefter sætter han PB1 og PB2 til at være outputs.
...Dette kan skrives lidt mere forståeligt:
DDRB |= (1 << DDB1) | (1 << DDB2);
(DDB1 betyder Data Direction for pin B1, DDB2 betyder så Data Direction for pin B2)
Efter det, enabler han COM1xx registrene:
TCCR1A |= 2 << 6;
TCCR1A |= 2 << 4;
Dette kunne skrives mere forståeligt sådan:
TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
Så indstiller han sine Output Compare Registers...
OCR1A = ICR1 * 2 / 20;
OCR1B = ICR1 * 2 / 20;
Det ene outputben (OC1A) får så resultatet af OCR1A's indstilling i forhold til ICR1
Det andet outputben (OC2A) får så resultatet af OCR2A's indstilling i forhold til ICR1
Efter det, så lader han sit program gå i loop med en
-Timeren ordner resten.
Man kan lave en slags grafisk illustration på denne måde:
+------------------------------------------------+
^ ^ ICR (top)
OCR1A (40% 'duty cycle' af ICR)
Så resultatet af ovenstående illustration kunne være...
____________________ (on)
______________________________ (off)
Se, det du så ønsker er...
1: At kunne beregne hvilken duty-cycle du vil have, altså forholdet mellem 'tændt' og 'slukket'.
Man kunne fx. sige du giver et tal i procent, så ville formlen være....
OCRxx = (TOP * procent) / 100;
Eller hvis du hellere vil give en byte...
OCRxx = (TOP * byte) / 256;
(kan skrives om til)...
OCRxx = (TOP * byte) >> 8; /* at bitskifte til venstre 8 gange er det samme som at dividere med 256, bare hurtigere og kortere */
-Find eventuelt selv på flere. :)
2: At indstille Timer1. Prøv at se på databladet, side 85, "Sektion 12. 16-bit Timer/Counter1".
-Der står en hel masse som du ikke gider læse lige nu.
Bladrer vi frem til side 106, finder vi beskrivelse af TCCR1A. Her er et lille overblik over dens bits:
COM1A1, COM1A0, COM1B1, COM1B0, -, -, WGM11, WGM10
Godt at huske til om lidt.
TCCR1B bliver beskrevet på side 108. På denne side står også lidt omkring de forskellige modes. Det er lige det vi skal bruge.
Vi vil gerne have det ligesom ham gutten i den tutorial; vi sætter selv vores TOP-værdi, og vi bestemmer selv hvor vores PWM skal tænde og hvor den skal slukke.
Mode 14 ser ud til at være fin til det brug; det virker til at det er samme mode som han brugte i sin tutorial.
Så kigger vi på hvad vi skal indstille WGM tll. Det er i nabo-kolonnen til Mode. Der står WGM1[3:0] hvilket betyder at rækkefølgen er WGM13, WGM12, WGM11, WGM10.
Dvs. for at få mode 14, skal vi sætte , WGM13=1, WGM12=1, WGM11=1 og WGM10=0.
Godt. Vi skal også indstille ICR1 til at være top-værdi.
WGM11 og WGM10 indstilles i TCCR1A, mens WGM13 og WGM12 indstilles i TCCR1B (fjollet, men det kan vi ikke lave om på).
Vi skal også finde en passende prescaler værdi. Om du vil køre 8MHz eller 1MHz, det ved jeg ikke lige nu, men vi kan prøve at starte med 1MHz.
På side 109 finder vi en oversigt over prescaler værdierne:
CS12=0, CS11=0, CS10=1 giver en prescaler på clk/1, dette giver 8MHz.
CS12=0, CS11=1, CS10=0 giver en prescaler på cllk/8, dette giver 1MHz.
OK, så godt, så langt. ;)
På side 107, ser vi en oversigt over hvad Timer1 kan gøre med OC1A og OC1B benene.
Jeg fortalte dig vidstnok noget tidligere, med at det ville være praktisk at sætte servoen på et OCxx-ben, desværre tror jeg at jeg fik overbevist dig til at bruge OC0x og ikke OC1x.
Det kan derfor være du 'bliver nødt til' at flytte servoen til fx. ben 7 som er OC1A, eller ben 8, som er OC1B.
Det kan være at servo-motoren går amok, når du så programmerer chippen, men det må den så gøre.
Altså på side 107 er der 2 tabeller. Den første er "non-PWM", den vil vi ikke kigge på.
Den anden tabel, "Table 12-3. Compare Output Mode, Fast PWM" viser noget vi kan bruge.
Vi er nok mest interesseret i at tænde OC1A først, derefter slukke den når vi rammer sammenlignings-værdien som står i OCR1A. Det ser ud til at COM1A[1:0] så skal være 11.
(Det gør ikke noget, hvis vi samtidig sætter COM1B, så kan vi flytte servo-motoren derover hvis vi har lyst).
Det bliver nogenlunde til følgende kode, som kunne ligge i PWM.c:
#define PWM_TOP_VALUE 65535 /* choose any value that suits you. */
void initPWM()
{
/* First we stop our timer: */
TCCR1B = 0; /* CS12=0, CS11=0, CS10=0: No clock source (Timer/Counter stopped). */
ICR1 = PWM_TOP_VALUE;
if(SERVO_ENABLED == GPIOR2)
{
TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1A1) | (COM1A0) | (1 << COM1B1) | (1 << COM1B0);
}
TCCR1B = 0; /* safety, to ensure PWM is not started by accident during RESET */
if(SERVO_ENABLED == GPIOR2)
{
TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10) | (1 << WGM13) | (1 << WGM12);
}
/* PWM is now running! */
DDRA |= (1 << DDA6) | (1 << DDA5); /* set both OC1A and OC1B to be output pins */
}
Et eller andet sted i programmet kan du så...
OCR1A = (PWM_TOP_VALUE * 17) / 100; /* 17% on, 83% off */
OCR1B = (PWM_TOP_VALUE * 61) / 100; /* 61% on, 39% off */
Dette kan skrives om til noget lidt mere fleksibel kode, som kunne ligge i PWM.c:
- (void)setDutyCyclePercentage(uint8_t aPercentage)
{
OCR1A = (PWM_TOP_VALUE * aPercentage) / 100;
}
-Men bemærk: Dette er sikkert slet ikke en præcis nok beregning.
Du vil sikkert gerne have nogle forud-definerede (forud-beregnede) 16-bit værdier, fx. én værdi til 'load' og én værdi til 'unload'.
Jeg ved dette indlæg ikke er udtømmende, men det skulle kunne give dig en nogenlunde pejling på hvordan PWM sættes op.
Hvis du har datablad på servoen, der fortæller dig noget om hvordan duty-cycle/frekvenserne beregnes, så kan det være til hjælp.
Det kan være, at det ikke er nok, kun at ændre OCR1A, men at du også er nødt til at kunne ændre ICR1 når du styrer servoen.
ICR1 = 19999;
Dette sætter i hans tilfælde TOP-grænsen hvis man bruger Fast PWM (mode 14).
giver det samme resultat som det vi gør når vi siger
ICR1 = servoFrequency;
hvor
servoFrequency = F_CPU / 50;
eller bør vi sige
servoFrequency = (F_CPU / 50)-1;
for at få 19999 eller har det ingen betydning?
Det er korrekt at trække 1 fra resultatet efter divisionen, men vores beregning skal være korrekt også.
Hvis vi har F_CPU / 50, og F_CPU er 8000000, så får vi et resultat der hedder 160000.
Dette tal kan ikke være i ICR1. Det kan tallet - 1 (159999( heller ikke.
Hvad er det så egentlig for en opløsning vi skal bruge ? -Dette giver du svar på her...
skal fordeles i en top på f.eks mellem 1.2-1.8 ms i duty cyclen
Da vi har 8000000 clock cycles per sekund, vil det sige at vores opløsning med en clk / 1 prescaler vil være 1 / 8000000 sekund = 0.000000125 sekunder = 125 nS (125 nanosekunder).
Vi vil maks. kunne sætte vores TOP til 65535.
65535 * 125 nS = 0.008191875 sekunder, altså 8.19 mS, hvilket er det vindue vi har til rådighed.
(minimum 125 nS, maksimum 8.19 mS - og både 1.2 mS og 1.8 mS ligger indenfor dette område)
Med andre ord:
Timeren vil køre 8000000 / 65536 = 122.07 gange i sekundet (cirka), hvilket vi kan regne efter med...
122.07 * 0.008191 sek = (cirka) 1. Vores beregning er god nok. =)
Altså, tilbage til vores indstilling af ICR1. Denne sætter vi på højest mulige værdi, for at få finest mulige opløsning, nemlig værdien 65535.
Lad os så se på hvad værdien 1.2 mS og 1.8 mS hedder, når de er skaleret op, så de passer til ovenstående...
1.2 mS = 0.0012 sekunder
1.8 mS = 0.0018 sekunder
Der går 8000000 clock-cycles på 1 sekund.
8000000 * 0.0012 = 9600
8000000 * 0.0018 = 14400
Altså bør din duty-cycle ligge mellem værdien (9600 - 1) og (14400 - 1)
OK. Nu har vi så det tal, som potmetret egentlig skal regnes om til.
Selve ADC-værdien er en værdi mellem 0 og 1023.
Vi kan så derfor gøre følgende:
uint16_t freq
...
...
freq = (uint16_t) (((14400 - 9600) * ((uint32_t) adcValue)) >> 10) + (9600 - 1);
Denne værdi burde vi så kunne lægge direkte ind i vores OCR1x register.
Bemærk: Det er i dette tilfælde vigtigt at indsætte (uint32_t) typecasting; for ellers kan vores tal blive 'klippet', da 16 bits ikke vil være nok til beregningen:
(14400 - 9600) * 1023 = 4910400 (som er højere end 65535, det maksimale der kan være i en 16-bit variabel).
Du ser også at jeg bitskifter med 10. Dette er en måde at dividere med 1024 hurtigt.
Men ovenstående har for mange konstante værdier. Dette kan vi ikke lide, for hvad hvis vi en dag vil ændre på prescaleren eller på F_CPU ?
Derfor...
#define LOWER_US (1200) /* 1.2 mS in microseconds */
#define UPPER_US (1800) /* 1.8 mS in microseconds */
#define LOWER_LIMIT (F_CPU * LOWER_US / 1000000)
#define UPPER_LIMIT (F_CPU * UPPER_US / 1000000)
uint16_t freq;
...
freq = (uint16_t) (((UPPER_LIMIT - LOWER_LIMIT) * ((uint32_t)adcValue)) >> 10) + (LOWER_LIMIT - 1);
OCR1x = freq;
Læg mærke til at vi stadig regner i heltal, men ganger vi før vi dividerer, taber vi ikke noget i disse beregninger.
hvis potten retunerer en værdi mellem 0 og 255 ville det så ikke være en ide med en tabel der bare giver OCRxx en konstant? (ligesom dipswitch)...
...men er en tabel på 256 muligheder for stor sådan rent byte mæssigt?
En tabel ville være temmelig stor til en ATtiny.
Altså ville en sådan tabel fylde 256 stk 16-bit værdier, hvilket er 512 bytes.
Beregner vi resultatet, vil det fylde meget mindre.
En fordel ved at bruge en beregnings-rutine, er at man nemt kan lave værdierne, så de tilpasses, hvis fx. F_CPU skal ændres på et tidspunkt.
vi bestemmer selv hvor vores PWM skal tænde og hvor den skal slukke.
opnår vi det ved følgende (uddrag af kode) 10 sek til at køre en dutycycle der trækker servoen ud og efterfølgende 10 sek der køre servoen modsat?
Æh, nej. Her mente jeg på frekvens-kurven.
For du sender nogle pulser mange gange i sekundet til din servo.
Hvad jeg forsøgte at sige, var noget i stil med at vi selv bestemmer om den skal starte med at sætte i/o-benet højt eller om den skal starte med at sætte i/o-benet lavt. :)
Jeg mener der er en bedre måde at gøre dette på, nemlig at sætte prescaleren til off (timer stopped), men det kommer lidt an på om man ønsker at timeren kører hele tiden.
Jeg tror det er en fordel at den kører hele tiden. servoer har det med at have en "støj" bevægelse hvis man slukker og tænder dem så det ville ikke gøre noget at servoen
hele tiden får af vide hvor den skal stå indtil næste dosering. Det belaster den ikke hvis den får samme input, med mindre den skal arbejde for at komme til den possition den får besked på.
OK, så skal den da selvfølgelig være tændt. :)
Ok så prøver jeg lige det her og hører hvad du siger
først sætter jeg PWM op i servo.c
#include <avr/io.h>
#include "servo.h"
#include "Timer1.h"
#include "ADC.h"
#include "Dosing.h"
void initServo()
{
}
void startServo(uint16_t aServoCountdown, uint16_t aRotation)
{
/*servoCountdown = aServoCountdown;*/ /* number of pulses to send to servo, before turning it off */
TCCR1A = 0; /* disable all PWM on Timer1 whilst we set it up */
TCCR1B = 0; /* (disable clock-source, so the timer is frozen) */
TIMSK1 = 0; /* disable all Timer1 interrupts */
ICR1 = servoFrequency; /* frequency is every 20ms, 50 times per second (50Hz) */
/* select fast PWM (mode 14) and no prescaler */
if(GPIOR2 == SERVO_ENABLED)
{
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); /* enable output COMpare 1 A, output COMpare 1 B, set WGM to Fast PWM */
}
if(GPIOR2 == SERVO_ENABLED)
{
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); /* set WGM to fast PWM, choose clock source CS12:CS11:CS10 = 1, clk/1 (no prescaling) */
}
OCR1A = freq; /* pulse to load or unload servo on PA7 depending on the argument aRotation */
TIMSK1 |= (1 << OCIE1B); /* enable Output Compare Interrupt 1 B */
}
OCR1A = freq; hvor freq bliver beregnet i min dosing.c som følger her
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "Timer1.h"
#include "servo.h"
#define LOWER_US (1200) /* 1.2 mS in microseconds */
#define UPPER_US (1800) /* 1.8 mS in microseconds */
#define LOWER_LIMIT (F_CPU * LOWER_US / 1000000)
#define UPPER_LIMIT (F_CPU * UPPER_US / 1000000)
/*a number that rep the pos zero when servo is unloaded*/
uint16_t freq;
volatile uint8_t gSettings;
uint8_t repeats =0;
/*ADC TABELS START*/
uint8_t calculateSettingsDip(uint16_t gDipSwitches)
{
static const uint16_t convTab[] PROGMEM = { ((1023 + 512) / 2), ((512 + 341) / 2), ((341 + 256) / 2), ((256 + 205) / 2),
((205 + 171) / 2), ((171 + 146) / 2), ((146 + 128) / 2), ((128 + 114) / 2), ((114 + 102) / 2), ((102 + 93) / 2),
((93 + 85) / 2), ((85 + 79) / 2), ((79 + 73) / 2), ((73 + 68) / 2), ((68 + 65) / 2), 0 }; /* 4K, 0% */
uint8_t i;
i = 0;
while(gDipSwitches <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
/*ADC TABELS END*/
/*FUNCTIONS START*/
void startDosing() /* this routine starts when the 'start' button is pressed */
{ /* setMinuteCountdown initiated corrosponding to dip setting*/
static const uint16_t timeTable[] PROGMEM = { 2 * 60, 24 * 60, 12 * 60, 6 * 60 };
setMinuteCountdown(pgm_read_word(&timeTable[(gSettings >> 2) & 0x03]));
timer0Elapsed();
}
void timer0Elapsed()
{
static const uint8_t repTable[] PROGMEM = { 1, 10, 4, 2 };
uint8_t repeats;
uint8_t a;
uint16_t aRotation;
uint8_t unload; /* Value 0 - 256 since the pot2 is removed and unload is defined by constant*/
unload = 0;
repeats = pgm_read_byte(&repTable[gSettings & 0x03]);
for(a = 0; a < repeats; a++)
{
/* load: */
freq = (uint16_t) (((UPPER_LIMIT - LOWER_LIMIT) * ((uint32_t)adcValue)) >> 10) + (LOWER_LIMIT - 1); /* sets how much to load, read from pot*/
waitSeconds(10);
/* unload: */
freq = unload;
waitSeconds(10);
}
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
/*FUNCTIONS END*/
Her får jeg en compiler fejl på adcvalue undeclared when first used
adcvalue bruger jeg jo i adc.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "ADC.h"
#ifndef inb
#define inb(sfr) _SFR_BYTE(sfr)
#endif
#define ADC_SIZE 5 /* number of reads to average the ADC channel (we write one, and average over last 4) */
#define ADC_STABLE (ADC_SIZE + 3)
#define ADC_FIRST 1 /* first ADC channel to read (ADC1) */
#define ADC_LAST 3 /* last ADC channel to read (ADC4)*/
static volatile uint16_t adcValue[ADC_LAST - ADC_FIRST][ADC_SIZE]; /* this buffer holds a lot of conversion results, enough for us to average the values */
static volatile uint8_t adcReadIndex = 0; /* this is the index we start reading values from (up to, but excluding writeIndex) */
static volatile uint8_t adcWriteIndex = 0; /* this is the index we write values to */
static volatile uint8_t adcCounter = 0; /* just a counter, that counts how many times we've read all the ADC channels */
static volatile uint8_t adcChannel = 0; /* current channel we're reading the ADC value from */
static uint8_t adcBits = 8;
void waitUntilADCStable()
{
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
}
uint16_t getADCValue(uint8_t aADC)
{
uint16_t result;
uint8_t i;
uint8_t idx;
idx = adcReadIndex; /* read from this index; avoiding reading at the 'write position' */
i = ADC_SIZE - 1; /* number of values to read (eg. if ADC_SIZE is 10, we only read 9 values) */
result = 0; /* zero our result */
while(i--)
{
result += adcValue[aADC - ADC_FIRST][idx]; /* add the one value we've just read from the index */
idx = idx >= (ADC_SIZE - 1) ? 0 : idx + 1; /* next index */
}
return(result / (ADC_SIZE - 1)); /* return the averaged result */
}
/* uint16_t getADC1Value()
{
return(getADCValue(1)); /* ADC value of startbutton */
/*}*/
uint16_t getADC2Value()
{
return(getADCValue(2)); /* ADC value of pot */
}
SIGNAL (SIG_ADC)
{
uint8_t adLo;
uint8_t adHi;
if(adcBits <= 8) /* using 8-bit precision */
{
if(ADCSRB & (1 << ADLAR)) /* check hardware alignment-configuration and act accordingly */
{
adLo = 0;
adHi = inb(ADCH); /* (read highbyte only) */
}
else
{
adHi = 0;
adLo = inb(ADCH); /* (read highbyte only) */
}
}
else
{
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
}
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
ADCSRA |= (1 << ADSC); /* start another conversion */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : adcWriteIndex + 1; /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : adcReadIndex + 1; /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
}
void initADC(uint8_t aBits)
{
adcCounter = 0; /* this is so we can see when the ADC conversions are stable */
adcWriteIndex = 0; /* start writing at index 0 */
adcReadIndex = adcWriteIndex + 1; /* read right after the write-index */
adcChannel = ADC_FIRST; /* initialize ADC channel number to read */
adcBits = aBits;
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* VCC used as analog reference, first channel */
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); /* free running mode, result is right aligned */
ADCSRA |= (1 << ADSC); /* start conversion */
}
da det er adc2 jeg læser fra, kan jeg så lave sætningen i dosing.c om til
req = (uint16_t) (((UPPER_LIMIT - LOWER_LIMIT) * ((uint32_t)getADC2Value())) >> 10) + (LOWER_LIMIT - 1);
desuden er jeg i tvivl om hvorvidt
waitSeconds(10);
gør at servoen har 10 sec til at bevæge sig fra freq unload til freq load, eller om jeg hellere skal have en for løkke ind
da det er adc2 jeg læser fra, kan jeg så lave sætningen i dosing.c om til
req = (uint16_t) (((UPPER_LIMIT - LOWER_LIMIT) * ((uint32_t)getADC2Value())) >> 10) + (LOWER_LIMIT - 1);
Ja, det mener jeg er korrekt. :)
desuden er jeg i tvivl om hvorvidt
waitSeconds(10);
gør at servoen har 10 sec til at bevæge sig fra freq unload til freq load, eller om jeg hellere skal have en for løkke ind
Det burde fungere fint med waitSeconds(10);.
En for-løkke ville fungere på stort set samme måde, men ulempen ved en for-løkke er at hvis du skifter CPU-frekvens, vil for-løkken ikke længere vente 10 sekunder, men mindre, hvis CPU-frekvensen er sat op, eller mere, hvis CPU-frekvensen er sat ned. Det er bedre at bruge waitSeconds i dette tilfælde.
Jeg har et forslag til lidt ændringer i Servo.c og Dosing.c...
Servo.c:
#include <avr/io.h>
#include <avr/interrupt.h>
#include "servo.h"
void initServo()
{
cli(); /* (disable all interrupts) */
TCCR1A = 0; /* disable all PWM on Timer1 */
TCCR1B = 0; /* disable clock-source, so the timer is frozen */
TIMSK1 = 0; /* disable all Timer1 interrupts */
DDRA |= (1 << DDA7); /* set OC1A as output */
ICR1 = 65534; /* we want our top to be as large as possible, for best resolution, but we'll keep it, so there's one value the OCR1x registers cannot reach. */
OCR1A = 65535; /* make sure this has a known value */
OCR1B = 65535; /* make sure this has a known value */
if(GPIOR2 == SERVO_ENABLED)
{
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); /* set WGM to fast PWM, choose clock source CS12:CS11:CS10 = 1, clk/1 (no prescaling) */
}
/* select fast PWM (mode 14) and no prescaler */
if(GPIOR2 == SERVO_ENABLED)
{
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); /* enable output COMpare 1 A, output COMpare 1 B, set WGM to Fast PWM */
}
}
void startServo(uint16_t aRotation)
{
OCR1A = aRotation; /* pulse to load or unload servo on PA7 depending on the argument aRotation */
}
Jeg har ovenover flyttet alt, der kun skal gøres én gang op i initServo().
Jeg har sat initialiseringen af output-benet ind i initServo() også.
Jeg har fjernet initialiseringen af Timer1 interruptet, da det nok ikke skal bruges.
Så har jeg også initialiseret OCR1A og OCR1B til 65535, for at undgå at der bliver bøvl med dem mens vi arbejder med koden.
Derudover har jeg sat top-grænsen for PWM til 65534 (ikke 65535), fordi hvis vi gør dette, så kan vi 'fryse PWM' ved at sætte PWM-værdien til 65535! -Da vil benet ikke skifte værdi mellem høj/lav.
Jeg har også ændret OCR1A = freq; til OCR1A = aRotation;
-Så giver du nemlig denne parameter som argument til funktionen.
(Prøv så vidt muligt at undgå globale variabler).
Du vil sikkert også bemærke at jeg har fjernet alle referencer til andre filer, dvs. header-filer som Main.h, Dosing.h, osv.
Det er fordi, hvis man tænker på koden som selvstændige enheder der kan tages ud og sættes ind, bør man ikke lave snore 'baglæns' i systemet, men kun forlæns. Man kunne sige det er som en 'uro'. hver dims der hænger i en snor, kan ikke pludselig side over den dims som den hænger ned fra. ;)
Jeg har smidt Timer1.c helt ud, da der ikke rigtigt er noget brugbart i den længere; Servo.c har sådan set overtaget dens funktion. :)
-Så Timer1.h skal ikke længere inkluderes i andre filer (som fx. Main.h).
Du er nok også nødt til at ændre prototypen for startServo i Servo.h:
void startServo(uint16_t aRotation);
Dosing.c:
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "Dosing.h"
#include "Timer0.h"
#include "ADC.h"
#include "servo.h"
#define LOWER_US (1200) /* 1.2 mS in microseconds */
#define UPPER_US (1800) /* 1.8 mS in microseconds */
#define LOWER_LIMIT (F_CPU * LOWER_US / 1000000)
#define UPPER_LIMIT (F_CPU * UPPER_US / 1000000)
#define UNLOAD 0 /*a number that rep the pos zero when servo is unloaded*/
#define LOAD(a) (uint16_t) (((UPPER_LIMIT - LOWER_LIMIT) * ((uint32_t) a)) >> 10) + (LOWER_LIMIT - 1)
volatile uint8_t gSettings;
/*ADC TABELS START*/
uint8_t calculateSettingsDip(uint16_t gDipSwitches)
{
static const uint16_t convTab[] PROGMEM = { ((1023 + 512) / 2), ((512 + 341) / 2), ((341 + 256) / 2), ((256 + 205) / 2),
((205 + 171) / 2), ((171 + 146) / 2), ((146 + 128) / 2), ((128 + 114) / 2), ((114 + 102) / 2), ((102 + 93) / 2),
((93 + 85) / 2), ((85 + 79) / 2), ((79 + 73) / 2), ((73 + 68) / 2), ((68 + 65) / 2), 0 }; /* 4K, 0% */
uint8_t i;
i = 0;
while(gDipSwitches <= pgm_read_word(convTab[i]))
{
i++;
}
return(i);
}
/*ADC TABELS END*/
/*FUNCTIONS START*/
void startDosing() /* this routine starts when the 'start' button is pressed */
{ /* setMinuteCountdown initiated corrosponding to dip setting*/
static const uint16_t timeTable[] PROGMEM = { 2 * 60, 24 * 60, 12 * 60, 6 * 60 };
setMinuteCountdown(pgm_read_word(&timeTable[(gSettings >> 2) & 0x03]));
timer0Elapsed();
}
void timer0Elapsed()
{
static const uint8_t repTable[] PROGMEM = { 1, 10, 4, 2 };
uint8_t repeats;
uint8_t a;
repeats = pgm_read_byte(&repTable[gSettings & 0x03]);
for(a = 0; a < repeats; a++)
{
/* load: */
startServo(LOAD(getADC2Value())); /* sets how much to load, read from pot */
waitSeconds(10);
/* unload: */
startServo(UNLOAD);
waitSeconds(10);
}
}
void initDosing()
{
startDosing(); /* in case of power failure, automatically restart with the current value */
}
/*FUNCTIONS END*/
I toppen af filen...
Jeg har fjernet den globale variabel 'uint16_t freq;'
Jeg har fjernet den globale variabel 'uint8_t repeats =0;'
Jeg har indsat en #define der definerer en konstant værdi for unload (den hedder UNLOAD).
Jeg har indsat en macro, dvs. en #define der beregner værdien for load (den hedder LOAD).
I timer0Elapsed()...
Jeg har fjernet uint16_t aRotation;
Jeg har fjernet uint8_t unload;
Jeg har ændret freq = unload; til at være startServo(UNLOAD);
Jeg har ændret freq = ...; til at være startServo(LOAD(getADC2Value()));
Jeg har således i beregningen erstattet adcValue med getADC2Value()
Muligvis får du nogle compile-fejl pga. ændringerne, så må du bare sige til. :D
Jeg mener selv at koden skulle være blevet lidt mere overskuelig, da den en del steder er blevet kortere; men jeg kan selvfølgelig tage fejl her. ;)
kan jeg gøre flg.
lave en led.c med flg
void setLED1(uint8_t aState)
{
PORTA = aState ? (PORTA & ~(1 << PA7)) : (PORTA | (1 << PA7));
}
void setLED2(uint8_t aState)
{
PORTA = aState ? (PORTA & ~(1 << PA6)) : (PORTA | (1 << PA6));
}
void setLED3(uint8_t aState)
{
PORTA = aState ? (PORTA & ~(1 << PA5)) : (PORTA | (1 << PA5));
}
void setLED4(uint8_t aState)
{
PORTA = aState ? (PORTA & ~(1 << PA4)) : (PORTA | (1 << PA4));
}
og en led.h med
prototypes på de 4 setLED1(uint8_t aState)
Så laver jeg en #include "led.h" i alle *.c
alt det lader jeg bare være en fast del af programmet, så det er der når og hvis der skal udvides
for at teste kan jeg så ikke bare redigerer i den *.c hvor der er noget jeg vil teste, og tilføje
uin16_t val = det_der_skal_testes();
void toggleled()
{
uint16_t val = getADC2Value();
if(val >= 1)
{
setLED1(1); /* turn LED1 on */
val = val - 1;
}
else
{
setLED1(0); /* turn LED1 off */
}
if(val >= 2)
{
setLED2(1); /* turn LED2 on */
val = val - 2;
}
else
{
setLED2(0); /* turn LED2 off */
}
if(val >= 3)
{
setLED3(1); /* turn LED3 on */
val = val - 3;
}
else
{
setLED3(0); /* turn LED3 off */
}
if(val >= 4)
{
setLED4(1); /* turn LED4 on */
val = val - 4;
}
else
{
setLED4(0); /* turn LED4 off */
}
}
jeg har svært ved at se hvordan keg kan compile og teste 1 modul ad gangen da de alle er vævet sammen og er afhængige af værdier fra andre end sig selv
Når jeg gør det på den måde, ser det ud til at selve start knap funktionen ikke virker.
jeg kan tænde og slukke led 1 på pa7 ved at skrive setLED1(1); eller setLED1(0); i starten af main.c (som det sidste i init main) men hvis jeg skriver setLED1(1); nede i ISR(PCINT0_vect) funktionen så sker der intet når jeg trykker på start knappen. er dog ikke helt sikker på at min led rutiner er globale
Min main.c ser således ud
#include <avr/io.h>
#include <avr/interrupt.h>
#include "main.h"
#include <util/delay.h>
#include "Servo.h"
#include "ADC.h"
#include "Timer0.h"
#include "Dosing.h"
#include "leds.h"
int main()
{
GPIOR2 = SERVO_ENABLED; /* This must be the first code executed; no code goes before it, and GPIOR2 is not to be changed from now */
initADC(8); /* initialize ADC, we'll use the 8-bit precision for now */
initTimer0();
initDosing();
initServo();
sei(); /* globally enable interrupts */
PCMSK0 |= (1<<PCINT3); /*Set PA3 as the pin to use*/
MCUCR = (1 << ISC01) | (0 << ISC00); /* interrupt on INT0 pin, falling edge */
GIMSK |= (1<<PCINT3);
setLED1(1);
while(1)
{
/* we don't really need to do anything here. Everything is handled by the interrupts */
}
return(0); /* (never reached) */
}
ISR(PCINT0_vect)
{
uint8_t pinValue;
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* we're only interested if button is pressed */
{
_delay_ms(10.); /* wait 10 ms */
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* only update settings when button value is stable */
{
setLED1(0); /* turn LED1 off */
startDosing();
}
}
}
kan jeg gøre flg.
lave en led.c med flg
{snip}
og en led.h med prototypes på de 4 setLED1(uint8_t aState)
Så laver jeg en #include "led.h" i alle *.c
Ser fint ud.
Det kunne være godt med...
void initLED()
{
DDRA |= (1 << DDA4) | (1 << DDA5) | (1 << DDA6) | (1 << DDA7);
}
og så kalde denne fra main(), fx. lige efter GPIOR2 = ...;
for at teste kan jeg så ikke bare redigerer i den *.c hvor der er noget jeg vil teste, og tilføje
{snip}
God idé. :)
Mit forslag til ovennævnte rutine er...
1: undgå at bruge globale variabler, men i stedet giv parametre (argumenter) til rutinen.
2: en smart lille forkortelse:
void setLEDs(uint8_t aValue)
{
PORTA = (PORTA & ~(1 << PA4) | (1 << PA5) | (1 << PA6) | (1 << PA7)) | (aValue << PA4);
}
PA4 har så værdi 1, PA5 har værdi 2, PA6 har værdi 4, PA7 har værdi 8.
Når de lyser, lægger du værdierne for dem sammen, fx. vil...
setLEDs(11);
få PA4 + PA5 + PA7 til at lyse, hvilket giver...
1 + 2 + 8 = 11...
[/quote]
jeg har svært ved at se hvordan keg kan compile og teste 1 modul ad gangen da de alle er vævet sammen og er afhængige af værdier fra andre end sig selv
[/quote]
Forståeligt nok. =)
Hvis jeg selv har rodet mig ud i store problemer, laver jeg et nyt selvstændigt program, og i dette selvstændige program laver jeg den del jeg vil teste.
Fx. til at starte med, ville jeg tage et selvstændigt modul (noget der ikke har andet vævet ind i sig), nemlig ADC'en.
ADC.h og ADC.c er selvstændigt bygget op (det er med vilje at heg har presset dig til at holde det hele modulært opbygget).
ADC.c og ADC.h kan indbygges i et andet program, og når de er testet og virker, så kan man gå videre til næste modul, fx. Timer0.c og Timer0.h.
Når jeg gør det på den måde, ser det ud til at selve start knap funktionen ikke virker.
jeg kan tænde og slukke led 1 på pa7 ved at skrive setLED1(1); eller setLED1(0); i starten af main.c (som det sidste i init main) men hvis jeg skriver setLED1(1); nede i ISR(PCINT0_vect) funktionen så sker der intet når jeg trykker på start knappen. er dog ikke helt sikker på at min led rutiner er globale
Min main.c ser således ud
/* {snip} */
int main()
{
GPIOR2 = SERVO_ENABLED; /* This must be the first code executed; no code goes before it, and GPIOR2 is not to be changed from now */
initADC(8); /* initialize ADC, we'll use the 8-bit precision for now */
initTimer0();
initDosing();
initServo();
sei(); /* globally enable interrupts */
PCMSK0 |= (1<<PCINT3); /*Set PA3 as the pin to use*/
MCUCR = (1 << ISC01) | (0 << ISC00); /* interrupt on INT0 pin, falling edge */
GIMSK |= (1<<PCINT3);
setLED1(1);
while(1)
{
/* we don't really need to do anything here. Everything is handled by the interrupts */
}
return(0); /* (never reached) */
}
ISR(PCINT0_vect)
{
uint8_t pinValue;
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* we're only interested if button is pressed */
{
_delay_ms(10.); /* wait 10 ms */
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* only update settings when button value is stable */
{
setLED1(0); /* turn LED1 off */
startDosing();
}
}
}
Ikke at det er noget der får programmet til ikke at virke, men jeg vil anbefale at rokere lidt om, plus at ændre linien MCUCR = ...:
PCMSK0 |= (1<<PCINT3); /*Set PA3 as the pin to use*/
MCUCR = MCUCR & ~((1 << ISC01) | (1 << ISC00)) | (1 << ISC01) | (0 << ISC00); /* interrupt on INT0 pin, falling edge */
GIMSK |= (1<<PCINT3);
sei(); /* globally enable interrupts */
linien som skriver i MCUCR ændrer nu ikke ved de andre bits i dette register (de var 0 i forvejen, men hvis du en dag kigger tilbage på koden, trækker du ikke en ting med over i et nyt progam, som kan give dig bøvl).
sei(); er sat sidst, fordi PCINT0 er et interrupt, og vi vil gerne 'have ro' mens vi sætter interrupts op.
Når så det er gjort, skal du vide at....
MCUCR registret har intet med Pin Change interrupt at gøre.
Den har kun med INT0, altså Pin B2 at gøre, så fjern den linie fuldstændig. :)
Derudover...
GIMSK |= (1<<PCINT3);
[code]
Se side 50 i databladet. :)
Derefter se side 52.
(Den slags har jeg selv haft meget bøvl med, [fnis])
Prøv så følgende...
I main's while(1):
[code]while(1)
{
setLED0(0);
setLED1(0);
setLED2(0);
setLED3(0);
}
...i PCINT0:
ISR(PCINT0_vect)
{
uint8_t pinValue;
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
setLED0(1);
if(pinValue) /* we're only interested if button is pressed */
{
setLED1(1);
_delay_ms(10.); /* wait 10 ms */
pinValue = PINA & (1 << PINA3); /* read value of input-pin */
if(pinValue) /* only update settings when button value is stable */
{
setLED2(1);
startDosing();
}
}
}
lige et spørgsmål mens jeg pakker og sender.
Du skulle på nuværende tidspunkt have modtaget en mindre modifikation af koden - ikke noget afsindig vigtig, men bare 'housekeeping', og så en rettelse af Makefile, som havde en fejl i 'clean' funktionen.
er der en hurtig måde man kan toggle et led så den tænder og slukker hver gang sekund skifter ? noget bit shift eller noget
AVR har sådan en smart feature (som jeg mener at PIC også har, for den sags skyld), at skriver man til INPUT porten, vil alle de bits, man skrive, blive togglet til det modsatte.
Så du kan...
PINA |= (1 << PA4); /* toggle LED on/off */
(PINA - også kaldet PORTINA - er dér, hvor du læser værdier, mens PORTA er dér, hvor du normalt skriver værdier)
Den stump kode kan du prøve at sætte ind i ISR(TIM0_OVF_vect), lige efter sSeconds++;
Man skulle næsten tro at min avr er brændt af eller noget. når jeg sætter prescaler til det som du sagde, så lyser alle leds hvad enten jeg sætter dem til off eller ej ?!?! :-[
Heh, den er ikke brændt af; der skal en del til. :)
Denne prescaler værdi burde være korrekt.
I Makefile er fuses sat til H:0xdf og L:0x62.
Dette betyder at microcontrolleren kører 1 MHz.
F_CPU skal derfor være 1000000, det er den også.
Hvis vi dividerer F_CPU med 64, får vi 15625.
Dividerer vi 15625 med 125, får vi 125. Begge disse værdier (125 og 125) passer i en byte.
Da vi har sat WGM0x til 0:0:0, er vores Timer0's MAX værdi 255.
-Så den ene trækker vi fra 256 og lægger i TCNT0 (for Timer0's MAX er 255, og der er så 256 tælle-værdier). altså 256 - ((1000000 / 64) / 125) = 131.
Derfor ser linien der sætter TCNT0's værdi sådan ud:
TCNT0 = 256 - ((uint8_t) ((F_CPU / 64) / 125));
TCNT0 starter med andre ord på 131 og tæller op til 255, hvorefter Timer0's overflow interrupt udføres så snart TCNT0 vippes om fra 255 til 0.
I overflow-interruptet gen-indstilles værdien på TCNT0, til næste gang. (Dette er det aller første der skal være i interruptet, for ellers vil værdien kunne 'glide', og derved ville tidsmåleren kunne tabe tid.
Derudover har jeg været årsag til endnu en fejl (fordi jeg ikke har været ordentlig vågen), i initTimer0 står nemlig:
Dette skulle have været...
... tilde (~) vender nemlig alle bits i en værdi til det modsatte.
Hvis der stadig ikke er nogen ændring, så prøv lige midlertidigt, at stille værdien tilbage til CS0x = 0:1:0, og se om der ændres noget.
Hvis alle LEDs derefter lyser, er det ikke pga. timeren det er galt.
Min initTimer0 ser nu således ud:
void initTimer0()
{
cli(); /* disable interrupts if not already done */
sSeconds = 0;
sMinuteCountdown = 0;
sMinuteCountdownRestart = 0;
TIMSK0 &= ~(1 << TOIE0); /* clear 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
/* set up timer to run in normal mode: (WGM02:WGM01:WGM00 = 000 */
/* make timer use PRESCALER64: (CS02:CS01:CS01 = 011): */
TCCR0A = (0 | (0 << COM0A1) | (0 << COM0A0) | (0 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (0 << WGM00));
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (1 << CS00));
/* Clear any pending interrupts, so we don't get an accidental interrupt immediately after enabling interrupts... */
TIFR0 |= (1 << TOV0); /* clear 'Timer OVerflow 0' bit in 'Timer Interrupt Flag Register 0' */
/* enable Timer OVerflow 0 interrupt: */
TIMSK0 |= (1 << TOIE0); /* set 'Timer Overflow Interrupt Enable 0' bit in 'Timer Interrupt MaSK 0' */
}
Hmm nej. jeg forstår det ikke.
Her kan jeg være helt med. ;D
...Men du skal ikke lade dig forvirre af disse ting. :)
hvis jeg har prescaler 8 i TCCR0B kan jeg godt toggle leds ved tryk på start knap, og følge det helt hen til start dosing. men den vil ikke toggle pr. sek i timeren.
OK. Vi skal prøve at lave en test. =)
Prøv at lave main's while loop om til...
while(1)
{
setLED1((SREG >> 7) & 1);
}
Dette skulle gøre at LED1 fortæller (hele tiden) om vores interrupts kører eller ej.
sætter TCCR0B prescaleren til 64 som den skal kan jeg tænde og slukke dem med en setLED(1) eller 0 i main lige efter init led, men reagerer ikke på noget med toggle eller on eller off hvis jeg smider det ind i startknap funktionen ??
HOV! Jeg kom til at klokke i det; en ting jeg næsten altid glemmer med hensyn til toggle.
Toggle-linien må ikke hedde...
men skal hedde
-Det kan godt påvirke i den retning, at det slukker alle de andre portben på PORT A, og dermed alle andre LEDs. :)
-Jeg har sendt dig en ny leds.c og leds.h. =)
Til gengæld kan jeg se at den springer direkte over i startdosing i dosing.c uden at jeg trykker på knappen for der kan jeg kode leds til at tænde eller slukke men stadig reagerer de ikke på startknappen
...Mumle, mumle... Måske indsætte toggle-LED i timer0Elapsed() ?
Efterfølgende har jeg fået 1 led til at toggle ved tryk på start, med prescaler 64, men den lyser stadig konstant hvis jeg smider toggle ind efter sSeconds++
Sikkert beslægtet med toggle-LED fejlen jeg omtalte ovenover.
tror også jeg fandt en anden fejl.
når der trykkes på start kaldes startdosing rutinen, men den har jo ingen adc1 værdier at forholde sig til så ved ikke om flg skal ind i startknap rutinen lige inden startdosing bliver kaldt
gSettings = calculateSettingsDip(getADC1Value());
Korrekt. Et bedre sted at sætte den er dog i startDosing(), fordi startDosing bliver kaldt 2 steder fra, nemlig i initDosing() og fra start-knappen.
Nu ser min startDosing således ud:
void startDosing() /* this routine starts when the 'start' button is pressed */
{ /* setMinuteCountdown initiated corrosponding to dip setting*/
static const uint16_t timeTable[] PROGMEM = { 2 * 60, 24 * 60, 12 * 60, 6 * 60 };
gSettings = calculateSettingsDip(getADC1Value()); /* read dip switches and update gSettings */
setMinuteCountdown(pgm_read_word(&timeTable[(gSettings >> 2) & 0x03]));
timer0Elapsed();
}
Så er jeg igang igen.
Har lavet alle ændringer som du skrev og brugt de nye led.c og h, og har ikke nogle toggleled() eller setLed(#) nogle andre steder i koden.
og det her er en kedelig mail der bare beskriver hvordan jeg går frem, ligeså meget for min egen skyld, så jeg prøver at være systematisk i min fejlsøgning :o
Når jeg starter systemet op lyser alle dioder pa4-7.
så prøver jeg at skrive
setLED1(0);
setLED2(0);
setLED3(0);
setLED4(0);
lige efter initled() i main så slukker alle led (det gør de ikke hvis jeg først skriver dem efter eks sei)
apropos SEI. skal den stå 2 steder. kan det ikke skabe problemer at enable interrupts 2 steder?
så men efter de alle er slukket, smider jeg setLED1((SREG >> 7) & 1); ind i main's while løkke og alt er stadig slukket så væk med den igen.
prøver at smide en toggleLED1(); ind i startknap funktionen efter prell sikring, og intet sker. stadig slukket.
prøver en setLED1(1) samme sted stadik slukket
fjerner alle setLED#(0) og smider toggle led ind i startknap funktionen igen og ser hvad der sker. Alt lyser og intet toggler. prøver også lige setLED1(0). ingen reaktion
Prøver at skrive PINA = (1 << PA4); alt lyser fortsat når der trykkes på start.
Smider alle setLED#(0) ind igen og prøver med samme toggle alle leds er nu bare slukket :-[
gør jeg noget forkert her?
Fandt ud af noget interessant. Jeg kan bruge setLED#(0) før initdosing i main.c men samme komando virker ikke efter initdosing
så det må vel betyde at den hænger i init af dosing et eller andet sted
Så søgte videre ved at følge programmet.
den går til initdosing hvorfra den går til startdosing og setLED#(0) komandoen virker indtil gSettings = calculateSettingsDip(getADC1Value()); efter denne kommando virker det ikke mere så søger videre derfra
Gennem hele uint8_t calculateSettingsDip(uint16_t gDipSwitches) virker det også men så tænker jeg
i startdosing sætter den gSettings = calculateSettingsDip(getADC1Value()); men calculateSettingsDip funktionen hedder calculateSettingsDip(uint16_t gDipSwitches) det stemmer jo ikke overens, så prøver at ændre i startdosing så det er gSettings = calculateSettingsDip(uint16_t gDipSwitches istedet for gSettings = calculateSettingsDip(getADC1Value());
og det kunne jeg ikke.
så må jeg jo kigge videre i getADC1Value() og se hvad der sker der
den slukker led gennem hele uint16_t getADCValue(uint8_t aADC) men i void waitUntilADCStable() slukker den ikke noget
i funktionen uint16_t getADC1Value() slukker den for led men ikke i de 2 efterfølgende
uint16_t getADC2Value() og uint16_t getADC3Value()
og nu vil jeg stoppe for i dag og hoppe i seng 8)
lige efter initled() i main så slukker alle led (det gør de ikke hvis jeg først skriver dem efter eks sei)
Aha. Jeg tror jeg er klar over hvad der sker nu. :)
Jeg har på fornemmelsen at programmet går ned.
-Du skal nemlig kunne tænde/slukke LEDs uanset hvor du er i koden.
Virker det ikke efter sei(), så betyder det at programmet ikke kommer videre.
Dvs. der er sikkert et eller andet interrupt, som er gået i ged. Mere om hvordan vi får det på plads senere.
apropos SEI. skal den stå 2 steder. kan det ikke skabe problemer at enable interrupts 2 steder?
cli() slår interrupts fra.
sei() slår interrupts til.
Du kan gøre dette så ofte du vil.
Mere detaljeret: sei() sætter bit 7 i SREG, mens cli() sletter bit 7 i SREG.
SREG's bit 7 hedder I, og står for "global Interrupt enable'.
så men efter de alle er slukket, smider jeg setLED1((SREG >> 7) & 1); ind i main's while løkke og alt er stadig slukket så væk med den igen.
Sikkert fordi programkørslen aldrig kommer ind i main's while-loop. ;)
Fandt ud af noget interessant. Jeg kan bruge setLED#(0) før initdosing i main.c men samme komando virker ikke efter initdosing
Aha.. Fejlen er fundet.
så det må vel betyde at den hænger i init af dosing et eller andet sted
Fuldstændig korrekt.
men så tænker jeg
i startdosing sætter den gSettings = calculateSettingsDip(getADC1Value()); men calculateSettingsDip funktionen hedder calculateSettingsDip(uint16_t gDipSwitches) det stemmer jo ikke overens, så prøver at ændre i startdosing så det er gSettings = calculateSettingsDip(uint16_t gDipSwitches istedet for gSettings = calculateSettingsDip(getADC1Value());
og det kunne jeg ikke.
Nej, for gDipSwitches er en parameter som er lokal og kun kan ses af calculateSettingsDip, og derfor bør denne parameter ikke hedde gDipSwitches, men aDipSwitches. :)
uint8_t calculateSettingsDip(uint16_t aDipSwitches)
-Så alle steder i koden, bør gDipSwitches blive erstattet med aDipSwitches.
(Dette er i Dosing.h og Dosing.c)
den slukker led gennem hele uint16_t getADCValue(uint8_t aADC) men i void waitUntilADCStable() slukker den ikke noget
Fejlen(e) er:
1: Koden venter på at ADC'en er stabil, men ADC'en er ikke sat igang endnu, så den venter forgæves og bliver ved med at vente.
2: Hvis nu vi kom forbi waitUntilADCStable(), så ville vi komme ind i timer0Elapsed, som venter 10 sekunder. Her vil den blive ved og ved at vente, fordi timer0 ikke er startet endnu.
Løsningen er ganske simpel:
Du bør ikke kalde startDosing() fra din initDosing().
Dette er fordi init____ rutinerne er ment som at skulle lave opsætning (opsætning der lige tager få clock-cycles) og ikke andet; de må ikke vente på noget.
Så prøv at flytte startDosing() fra initDosing over i Main.c, lige efter sei();
og nu vil jeg stoppe for i dag og hoppe i seng 8)
Fornuftigt. :)
Denne beskrivelse har været rigtig god. Jeg regner med at du i dag kan få lysdioderne til både at lyse og blinke, plus nogle andre småting. ;)
Du burde efter ændringen kunne få følgende i main's while-loop til at virke...
while(1)
{
toggleLED1();
waitSeconds(1);
}
Så prøv at flytte startDosing() fra initDosing over i Main.c, lige efter sei();
Det virkede desværre ikke den hænger stadig i waitUntilADCStable().
OK, men lad den blive dér, for det vil ikke komme til at fungere, hvis den sidder i initDosing. ;)
Prøv at åbne ADC.c og ændre det sidste af den til følgende:
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : (adcWriteIndex + 1); /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : (adcReadIndex + 1); /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
ADCSRA |= (1 << ADSC); /* start another conversion */
}
Jeg har lavet følgende ændringer:
- flyttet ADCSRA |= (1 << ADSC); ned i bunden.
- sat paranteser omkring adcWriteIndex + 1 og adcReadIndex + 1
ADCSRA-linien bør nok stå efter ADMUX linien, fordi ADMUX fortæller hvilken kanal vi vil læse fra næste gang, og ADCSRA-linien starter næste konvertering.
Derudover sætter jeg parantes omkring adcWriteIndex + 1 og adcReadIndex + 1, fordi 'select', dvs. udtrykket " <udtryk> ? <udtryk> : <udtryk> " har det med at være prioriteret anderledes end man forventer, så det er bedst altid at bruge paranteser, når man bruger dem.
Det burde hjælpe at få ADCSRA linien flyttet. Jeg har ingen anelse om hvorfor jeg ikke har sat den efter ADMUX linien... <glob> :)
Prøv at sætte noget lysdiode-test ind i ADC-interruptet.
den lyser hele tiden når jeg prøver det du har skrevet. har også prøvet at lave
kan ikke se forskel på om den er svag eller skarp. men
hvis jeg sætter led til at være slukket i toppen og undlader at tænde den i bunden, forbliver den slukket. hvis jeg slukker den i starten af interrupt tænder den i bunden forbilver den tændt
Dette er en rigtig god nyhed.
Altså vil det sige at interruptet kører. :)
if (adcCounter ==#)
{
setLED1(0);
}
hvor # er 1-8 og den slukker som den skal og stopper ved 9 med at slukke
Også en ganske god nyhed, for den skal jo tælle op så længe adcCounter er mindre end 8.
Og så er der fundet en fejl til... Mere herom senere. ;)
sorry. jo den bliver svag, så interrupt må køre
trykker jeg så på startknappen, slukker den helt
Fint.
Prøv at nærlæs denne kode...
if(adcCounter < ADC_STABLE)
{
adcCounter++;
}
Derefter prøv at nærlæs denne kode...
while(adcCounter < ADC_STABLE)
{
}
Ser rigtig godt ud, ikke, men der er faktisk en fejl. Lad os lige gøre det mere klart for os hvad der sker:
Så koden således ud...
if(adcCounter < 8)
{
adcCounter++;
}
Hvad ville den maksimale værdi af adcCounter så være ?
... og ...
while(adcCounter < 8)
{
}
Hvad sker her ? :)
(Hvor mange kvajekager er jeg oppe på nu?) ;D
Æh, det var en and! :o
Der er intet galt dér alligevel.
Fordi...
Hvis adcCounter er mindre end 8, bliver der lagt én til, dvs. er den 7, bliver der lagt én til, og den bliver 8.
While... Så længe værdien er under 8, bliver den i while-løkken, og låser, men kun så længe værdien er mindre end 8. Den bliver nøjagtig 8, og derfor kommer den ud af while-løkken. (I teorien ihverfald).
HAH. Det er noget helt andet, der er galt. :)
Et interrupt bliver ikke afbrudt af et andet interrupt.
Så hvis vi er inde i et interrupt, fx. start-knap interruptet, så kan dette ikke afbrydes, fordi når man kommer ind i et interrupt, bliver interrupts slået fra globalt, dvs. bit 7 i SREG bliver sat til 0.
(Dette er fordi man vil undgå at et interrupt afbryder sig selv, hvis det er for lang tid om at udføre sin kode). Når interruptet så er færdigt, bliver interrupts så slået til igen.
Her er et forslag til løsning af problemet. (Nogle programmører vil have ganske gode argumenter mod at gøre det, men.. jeg gør det alligevel).
Hvad vi gør, er at vi slår interrupts til, før vi begynder at vente.
Men lige inden dette, så gemmer vi det gamle status-register (SREG), og når vi er færdige med at vente, sætter vi SREG til den gamle værdi, hvilket vil betyde at hvis interrupts var slået fra, bliver de slået fra igen, ellers bliver de ved med at være slået til.
void waitSeconds(uint8_t aSeconds)
{
uint8_t oldSREG;
aSeconds = (aSeconds + sSeconds); /* add current second value to aSeconds */
if(aSeconds >= 60) /* we can't wait more than 59 seconds, so if the result is larger than 59... */
{
aSeconds -= 60; /* ...then subtract 60 */
}
oldSREG = SREG; /* save global interrupt flag */
sei(); /* enable interrupts */
while(aSeconds != sSeconds) /* wait until sSeconds has the same value as aSeconds */
{
}
SREG = oldSREG; /* restore global interrupt flag */
}
void waitUntilADCStable()
{
uint8_t oldSREG;
oldSREG = SREG; /* save global interrupt flag */
sei(); /* enable interrupts */
while(adcCounter < ADC_STABLE) /* keep waiting, until ADC is reliable */
{
}
SREG = oldSREG; /* restore global interrupt flag */
}
(4 nye linier i hver rutine)
når jeg køre avrdude skriver den avrdude: safemode: Fuses OK. og det er når der er compilet med den makefile hvor fuses sat til H:0xdf og L:0x62
Jeg husker ikke om den altid gør dette, eller om den kun gør det ved skrivning.
Hvis du har mulighed for at skrive følgende...
make fuse
Vil det indstille fuses som de er i Makefile. Denne kommando køres nemlig ikke normalt ved almindelig flashing.
Kan du se om lysdioden blinker (hurtigt) nu ?
Ja, når TCNT0 = 0 så blinker den hurtigt.
Har også prøvet den interne _delay_ms(10); sammen med en toggleLED
hvis TCNT0 = 255 og _delay_ms(1000) så er den ca tændt 1 sek og derfter slukket 1 sek så det er jo rigtigt nok men 1000 ms er jo 1 sek ligemeget om den køre 1 Mhz eller 8Mhz så det kan jo ikke bruges til så meget
[/quote]
Prøv at lave denne smule test-kode...
while(1)
{
_delay_ms(250);
_delay_ms(250);
toggleLED1();
}
Hvis microcontrolleren kører 1MHz, vil ovenstående LED blive tændt med 1 sekunds mellemrum, dvs. fra tænd-til-tænd vil være 1 sekund.
Derimod, hvis den blinker ca. 8 gange så hurtigt, kører microcontrolleren 8MHz.
så nu er det nok gg (er der et maks for hvor mange gange man kan skrive til en avr?)
nu prøver jeg at teste det del for del.
har fået timeren til at køre på denne måde
ISR(TIM0_OVF_vect)
{
volatile uint8_t count = 0 ;
count++;
if (count==30)
{
toggleLED1();
count=0;
TCNT0 =0;
}
}
Samtidig har jeg kommenteret initADC(8); ud i main
count er sat til 30 for (f_cpu/64=15622)/256 = 61 hvis tnct0 tæller op til 256 så må count tælle op til 61 på 1 sek, så jeg togler hvert ½ sek.
så nu vil jeg se om jeg kan finde ud af hvor i ADC det går galt for mig gg og så lige i ADC :o gg
Har lige et spørgsmål til en linje i adc
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
så vidt jeg kan læse bør adc ligge mellem 50 og 200 Khz er jeg så ikke nød til at køre med en prescaler på 8-16 altså ADPS2, ADPS1, ADPS0 = 100 eller 011 ? lige nu er den 128, altså helt nede på 7,8 khz. men jeg er absålut ikke sikker. ADC'en er somsagt den jeg har flest problemer med at tyde
et andet spørgsmål.
Hvis vi køre 8 bit opløsning på adv skal det så ikke være left aligned?
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); her er den right
og i
ISR(ADC_vect)
if(ADCSRB & (1 << ADLAR)) er den left
Jeg er på meget usikker grund for jeg prøver bare at læse alle mulige tutorials og de er både fra atmega men mest fra tiny13
Så fandt jeg en lille ting som jeg er helt sikker på --- næsten :o
i adc skal vi ikke bruge eller læse adc3value da den er sat op som ISR(PCINT0_vect) i main.c så retter #define ADC_FIRST og LAST til 1 og 2
samtidig fjerner jeg uint16_t getADC3Value() funktionen.
så nu er det nok gg (er der et maks for hvor mange gange man kan skrive til en avr?)
Ja, 10000 gange.. :)
-Så du har nok rigeligt med forsøg endnu.
Personligt har jeg ikke prøvet at skrive så mange gange at jeg har brugt en AVR op.
nu prøver jeg at teste det del for del.
god idé.
har fået timeren til at køre på denne måde
{snip}
Jeg havde egentlig planer om at sætte en AVR op selv, men har i øjeblikket vældig meget der presser fra alle sider, så jeg ved ikke om jeg kan få overskud / tid lige nu.
Samtidig har jeg kommenteret initADC(8); ud i main
count er sat til 30 for (f_cpu/64=15622)/256 = 61 hvis tnct0 tæller op til 256 så må count tælle op til 61 på 1 sek, så jeg togler hvert ½ sek.
Hmm, jeg tror jeg blev forvirret tidligere.
Prøv følgende:
ISR(TIM0_OVF_vect)
{
volatile uint8_t count = 0;
if(0 == count--)
{
TCNT0 = 256 - ((uint8_t) ((F_CPU / 64) / 125));
count = 125 - 1;
sSeconds++;
toggleLED1();
}
}
Det er fordi...
F_CPU er 1000000.
F_CPU / 64 = 15625.
15625 / 125 = 125.
TCNT0 skal sættes til 256 - 125 = 131, fordi TCNT så tæller op fra 131 til 256.
Dette skal den gøre 125 gange for at ramme 1 sekund (derfor skal count resettes når den rammer 125).
Har lige et spørgsmål til en linje i adc
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
så vidt jeg kan læse bør adc ligge mellem 50 og 200 Khz er jeg så ikke nød til at køre med en prescaler på 8-16 altså ADPS2, ADPS1, ADPS0 = 100 eller 011 ? lige nu er den 128, altså helt nede på 7,8 khz. men jeg er absålut ikke sikker. ADC'en er somsagt den jeg har flest problemer med at tyde
Ja, side 135 - det tror jeg du har ret i, ups. :)
Du kan roligt sætte den ned. ;)
et andet spørgsmål.
Hvis vi køre 8 bit opløsning på adv skal det så ikke være left aligned?
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (0 << ADLAR); her er den right
og i
ISR(ADC_vect)
if(ADCSRB & (1 << ADLAR)) er den left
IF-sætningen kigger om den er left-aligned. Hvis ikke, så går den til else, hvor den håndterer at den er right-aligned. (Det er ihvertfald idéen, om den så gør hvad den får besked på...)
Vi vil nok helst have den right-aligned; det er nemmere i vores tilfælde.
Jeg er på meget usikker grund for jeg prøver bare at læse alle mulige tutorials og de er både fra atmega men mest fra tiny13
Du er nu ved at blive vældig skrap til AVR. =)
Så fandt jeg en lille ting som jeg er helt sikker på --- næsten :o
i adc skal vi ikke bruge eller læse adc3value da den er sat op som ISR(PCINT0_vect) i main.c så retter #define ADC_FIRST og LAST til 1 og 2
samtidig fjerner jeg uint16_t getADC3Value() funktionen.
Jo, hvis det er en switch (startknap) der er koblet på PA3, så vil det være mest praktisk at læse den digitalt. :)
kan man ikke lave en løsning hvor den kun aflæser adc værdierne når der trykkes på start knappen? der er jo ingen grund til at den kigger på dem når programmet er startet op, med mindre mam selvfølgelig vil ændre doseringen, men så ma man trykke på start når det er gjort. så skal ADC interruptne jo kun køres enten ved tryk på start, eller ved timer0elapsed
Selvfølgelig. Det var egentlig forberedt på at skulle læse 2 potmetre og 2 sæt DIP-switches. ;)
...Derudover regnede jeg med at du ville køre microcontrolleren på 8MHz.
Men du kan til at starte med, prøve at kalde initADC(10) fra main().
Og så i ADC-interruptet lave følgende lille ændring, det skulle give lidt mere CPU-tid:
ISR(ADC_vect)
{
uint8_t adLo;
uint8_t adHi;
#if 0
if(adcBits <= 8) /* using 8-bit precision */
{
if(ADCSRB & (1 << ADLAR)) /* check hardware alignment-configuration and act accordingly */
{
adLo = 0;
adHi = inb(ADCH); /* (read highbyte only) */
}
else
{
adHi = 0;
adLo = inb(ADCH); /* (read highbyte only) */
}
}
else
{
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
}
#else /* always 10-bit */
adLo = inb(ADCL); /* read lowbyte before highbyte! */
adHi = inb(ADCH); /* read lowbyte before highbyte! */
#endif
adcValue[adcChannel - ADC_FIRST][adcWriteIndex] = (adHi << 8) | adLo; /* save the value we've read above */
if(adcChannel++ >= ADC_LAST) /* next channel. If channel reached last channel... */
{
adcChannel = ADC_FIRST; /* ...start over */
adcWriteIndex = adcWriteIndex >= (ADC_SIZE - 1) ? 0 : (adcWriteIndex + 1); /* increment write position and wrap if necessary */
adcReadIndex = adcReadIndex >= (ADC_SIZE - 1) ? 0 : (adcReadIndex + 1); /* increment read position and wrap if necessary */
if(adcCounter < ADC_STABLE) /* if we haven't reached the number of conversions required for the ADC to stabilize... */
{
adcCounter++; /* increment counter */
}
}
ADMUX = (0 << REFS1) | (0 << REFS0) | (adcChannel & 0x07); /* set which channel to read next time */
ADCSRA |= (1 << ADSC); /* start another conversion */
}
Her har jeg sat #if 0, #else og #endif ind. Jeg har også kopieret de 2 linier, som håndterer 10-bit konvertering.
Der spares nu omkring 10 ... 11 clock cycles i interruptet, og derfor burde det køre en smule bedre. Om det er nok, vil en prøve vise.
Jeg har en lille modifikation til Timer0.c...
I TIM0_OVF_vect:
#if (F_CPU > 2000000)
TCNT0 = 256 - ((uint8_t) ((F_CPU / 256) / 125)); /* interrupt occurs 125 times per second. */
#else
TCNT0 = 256 - ((uint8_t) ((F_CPU / 64) / 125)); /* interrupt occurs 125 times per second. */
#endif
I initTimer0:
#if (F_CPU > 2000000)
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (1 << CS02) | (0 << CS01) | (0 << CS00)); /* using PRESCALER256 */
#else
TCCR0B = (0 | (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (1 << CS00)); /* using PRESCALER64 */
#endif
-For så kan du nemlig skifte mellem 1MHz og 8MHz som du har lyst. :)
I øjeblikket kører Timer1 på clk / 1; den kan bare sættes til clk/8, så kører den samme hastighed som nu. :)
-Vi har jo gemt 16-bit timeren til det vigtige. ;)
Her er lidt forberedelse på at skifte frekvens til Servo.c, plus forhåbentlig bliver koden lidt mere læsbar...
#include "leds.h"
#define PRESCALER1 ((0 << CS12) | (0 << CS11) | (1 << CS10)) /* clk / 1 (no prescaling) */
#define PRESCALER8 ((0 << CS12) | (1 << CS11) | (0 << CS10)) /* clk / 8 */
#define PRESCALER64 ((0 << CS12) | (1 << CS11) | (1 << CS10))
#define PRESCALER256 ((1 << CS12) | (0 << CS11) | (0 << CS10))
#define PRESCALER1024 ((1 << CS12) | (0 << CS11) | (1 << CS10))
#if (F_CPU <= 1000000)
#define PRESCALER PRESCALER1
#else
#define PRESCALER PRESCALER8
#endif
void initServo()
Længere nede, nemlig i initServo():
if(GPIOR2 == SERVO_ENABLED)
{
TCCR1B = (1 << WGM13) | (1 << WGM12) | PRESCALER; /* set WGM to fast PWM, choose clock source */
}
Jeg har ændret (1 << CS10) til PRESCALER
mht til ADCSRA |= (1 << ADSC);
kan jeg så ikke lade den ligge i initADC og så en i startknappen. så i ISR(PCINT0_vect) fjerne ADCSRA |= (1 << ADSC);
lave en
if(adcChannel == ADC_LAST)
{
ADCSRA |= (0 << ADSC);
}
hvis jeg gør det på den måde så køre timeren hvertfald.
Hele denne blok...
if(adcChannel == ADC_LAST)
{
ADCSRA |= (0 << ADSC);
}
...gør ingenting - bortset fra at bruge CPU-tid. :)
Det vil være bedre at sætte microcontrolleren op på 8MHz, eller gå bort fra at bruge ADC-interrupt.
-Da skal vi til at kigge på at aflæse ADC'en manuelt; dvs. lave 4 aflæsninger manuelt i streg.
Det kan så gøres i getADCxValue().
...Men bemærk; én aflæsning vil nok være for upræcist / ustabil, for den vil nok virke nogle gange, men andre gange vil den slå fejl.
Det er derfor bedst at tage 4 aflæsninger (eller 8) og så lægge alle aflæsningerne sammen, og sidst dividere med antallet af aflæsninger.
...Jeg siger 4 eller 8, fordi man så kan bruge bitskift operationer, hvilket er langt mere CPU-venligt end almindelige divisioner. :)
-Men da skal initADC være lidt anderledes.
prøvede lige en sidste ting inden sove til
ISR(ADC_vect)
{
if(ADCL > 128)
{
setLED1(1);
setLED2(0);
}
else
{
setLED1(0);
setLED2(1);
}
ADCSRA |= (1 << ADSC);
}
void initADC(uint8_t aBits)
{
ADMUX = (0 << REFS1) | (0 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADIF) | (1 << ADIE) | (0 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
sei();
ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0) | (1 << ADLAR);
ADCSRA |= (1 << ADSC);
}
Når jeg gør sådan så skifter led kun efter jeg ændre på dips og pot (ADCL) slukker strøm og tænder igen.
den vil ikke skifte mens der er tændt og jeg skifter. alle andre init er kommenteret ud.
Hej igen.
Jeg håber du kan give mig et hurtigt hint der ikke kræver for meget tid på flg.
jeg har koden
switch (eDose1)
{
case 1: lcd_puts("Bal1 CaCl2-2H2O"); break;
case 2: lcd_puts("Bal2 NaHCO3"); break;
case 3: lcd_puts("Bal3 mineralsalt"); break;
case 4: lcd_puts("Kh buffer"); break;
case 5: lcd_puts("Ca Buffer"); break;
case 6: lcd_puts("Mg buffer"); break;
case 7: lcd_puts("Sporeelement 1"); break;
case 8: lcd_puts("Sporeelement 2"); break;
case 9: lcd_puts("Sporeelement 3"); break;
case 10: lcd_puts("Aminosyre"); break;
case 11: lcd_puts("VSV"); break;
case 12: lcd_puts("Jod"); break;
case 13: lcd_puts("Strontium"); break;
case 14: lcd_puts("Phyto"); break;
case 15: lcd_puts("Zoo"); break;
case 16: lcd_puts("Diverse1"); break;
case 17: lcd_puts("Diverse2"); break;
case 18: lcd_puts("Diverse2"); break;
}
Jeg skal bruge den 12 forskellige steder, men uafhængigt af hinnanden og kan jo bare bruge forskellige switch, men det fylder jo helt vildt hvis jeg laver 12 kodestumper.
Men jeg kan simpelthen ikke greje hvordan jeg kan lave en variabel der består af bogstaver f.eks a = "thomas";
for så kunne jeg bare definerer dem som så og så kalde den variabel jeg skal bruge.
Har også kigget på avr/pgmspace, men ligesom sidst her jeg ikke helt styr på det tabel halløj.
Håber du kan give et hint
:o
fik det løst på flg måde.
prog_char eName0[] = "";
prog_char eName1[] = "Bal1 CaCl2-2H2O";
prog_char eName2[] = "Bal2 NaHCO3";
prog_char eName3[] = "Bal3 mineralsalt";
prog_char eName4[] = "Kh buffer";
prog_char eName5[] = "Ca Buffer";
prog_char eName6[] = "Mg buffer";
prog_char eName7[] = "Sporeelement 1";
prog_char eName8[] = "Sporeelement 2";
prog_char eName9[] = "Sporeelement 3";
prog_char eName10[] = "Aminosyre";
prog_char eName11[] = "VSV";
prog_char eName12[] = "Jod";
prog_char eName13[] = "Strontium";
prog_char eName14[] = "Phyto";
prog_char eName15[] = "Zoo";
prog_char eName16[] = "Diverse1";
prog_char eName17[] = "Diverse2";
prog_char eName18[] = "Diverse3";
PROGMEM const char *eString_table[] =
{
eName0, eName1, eName2, eName3, eName4, eName5, eName6, eName7, eName8, eName9, eName10, eName11, eName12, eName13, eName14,
eName15, eName16, eName17, eName18
};
char eDose1buf[16];
char eDose2buf[16];
char eDose3buf[16];
char eDose4buf[16];
char eDose5buf[16];
char eDose6buf[16];
og nede hvor det skal bruges
strcpy_P(eDose1buf, (char*)pgm_read_word(&(eString_table[dosing1num])));
lcd_puts(eDose1buf);