Nauwkeurigheid DELAY_ms

Hoi, ben ik weer met mijn CooCox :D
Voor een project waarbij ik een analoog signaal wil samplen door middel van de ADC (12 bits, 0 - 4096 waarbij 4096 = 3.3V) op mijn microcontroller loop ik tegen het volgende probleem aan.

Ik zal proberen het probleem te omschrijven.
Ik gebruik momenteel een sinus van 5 Hertz, met een offset zodat het DC wordt in plaats van AC.

Nu neem ik gedurende 1 seconde 25 samples door middel van het onderstaande stuk code. De DELAY_ms(40) zijn functie is dat hij om de 40 ms een sample neemt. De variabele sampleFreq is 25. Dus de eerste for loop doorloopt hij 25 keer. 25 x 40 ms = 1 seconde.

Wanneer op de terminal de gesampelde waardes uitlees krijg ik dit.

Nu wil ik deze samples weer omzetten naar de oorspronkelijke sinus. Dit doe ik even via een excel bestand.

Ik krijg nu niet de 5 Hertz die er in ging te zien. Sterker nog, ik kom rond de anderhalve Hertz te kort. Ik heb hogere samplefrequenties geprobeerd, en verschillende input frequenties, maar ik kom in het excel bestand nooit uit op de oorspronkelijke sinus, maar altijd een aantal Hertz onder de oorspronkelijke sinus. Ik ben ondertussen het spoor bijster en vermoed dat het iets met de timing van de DELAY_ms te maken heeft. Hoe nauwkeurig is zon DELAY nou precies?

Arco

Special Member

Delay functies bij microcontrollers zijn meestal softwarematig, en kunnen dus behoorlijk afwijken.
Alle bijkomende zaken zoals interrupts moet je erbij optellen,met heel veel interrupts kan een delay zelfs wel tweemaal zo lang worden...
(bij interrupts wordt de delayloop tijdelijk 'stil gezet', dus de totale tijd langer)

Voor nauwkeurige delays altijd een timer gebruiken...

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Op 22 januari 2020 11:38:38 schreef Arco:
Delay functies bij microcontrollers zijn meestal softwarematig, en kunnen dus behoorlijk afwijken.
Alle bijkomende zaken zoals interrupts moet je erbij optellen,met heel veel interrupts kan een delay zelfs wel tweemaal zo lang worden...
(bij interrupts wordt de delayloop tijdelijk 'stil gezet', dus de totale tijd langer)

Voor nauwkeurige delays altijd een timer gebruiken...

Klopt, maar dat is dus het vreemde, het lijkt alsof de timer juist te snel gaat. Als het probleem zou zijn zoals jij het beschrijft zou de afstand tussen alle samples dus groter worden.
Maar het tegenovegestelde lijkt werkelijkheid. Het lijkt wel alsof de afstand tussen de samples kleiner wordt. dus in plaats van dat die for loop 1 seconde moet duren, duurt hij maar +- 0.8 seconde.

buckfast_beekeeper

Golden Member

Op 22 januari 2020 11:30:39 schreef Christiaan371:
EDIT: oeps, gereageerd ipv aangepast. Waarom kunnen we geen posts verwijderen :(:)

Omdat je anders het forum kan verminken. Begint iemand al zijn post te wissen ontstaat er een kaas met heel grote gaten. Neem bijvoorbeeld Arco, bijna 36 000 posts. Uiteraard kan hij nog gaan wijzigen. Hopelijk hebben de mods het dan snel in de gaten. Op vele fora kan je maar maximaal 15 minuten een post wijzigen. Om dezelfde reden.

Van Lambiek wordt goede geuze gemaakt.
Arco

Special Member

Bij het opnemen van de sinus zal de tijd langer duren als verwacht. Maar bij weer proberen de sinus te reconstrueren zal dit exact andersom zijn.
(er zijn immers 'stukjes' met onbekende lengte tussenuit geknipt...)

Als bij opnemen een 40mS delay 42mS duurde, zal er bij reconstructie 2mS 'zoek' zijn... (en lijkt dus maar 38mS te zijn)
Ofwel: door interrups krijgt een Delay (en dus sampletijd) een variabele en onbekende lengte. Hoe wou je daar het originele signaal weer mee maken?

[Bericht gewijzigd door Arco op woensdag 22 januari 2020 12:54:52 (18%)

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Misschien overbodig, maar staat de klokfrequentie wel goed ingesteld?

De Delay_MS die ik ken van ATmega's is inderdaad gewoon volledig software gebaseerd. Ergens moet je de klokfrequentie definiëren waar hij mee moet rekenen. Als de definitie trager is dan de praktijk loopt de boel te rap.

Regelmatig ingetrapt met 20MHz en 16MHz kristallen.

elmowww

Golden Member

Probeer hiervoor de systick functie te gebruiken: Dat zorgt er ook voor dat interrupts je delay niet teveel verminken.

PA0EJE - www.eje-electronics.nl - e.jongerius[aapje]eje-electronics.nl - EJE Electronics - Elektronica/firmware ontwikkeling
Arco

Special Member

Gebruik een timer interrupt voor sampling: dan is er helemaal geen afwijking.

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Je "sample_loop" wordt doorlopen in de tijd die "delay_ms (40)" kost PLUS de tijd die "get_ADC_channel ()" kost. Als je een ADC waarde wil opnemen, dan kan dat best redelijk snel, maar als je een "handige" functie wilt maken die gewoon altijd doet wat ie moet doen, dan kan het zijn dat je op eea moet wachten. Dus "get_ADC_channel ()" zal iets doen als: selecteer het juiste kanaal, set de referentiebron goed, wacht, neem een sample, gooi dat weg, neem nog een sample en geef die terug aan de aanroepende functie". Al met al genoeg om kennelijk een aantal ms te kosten.

probeer eens iets als:

code:


nextsample = millis () + 10;
for (i=0;i<25;i++) {
  while (millis() < nextsample)
     ;
  samples[i] = adc_get_channel (chan); 
  nextsample += 40;
}
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Arco

Special Member

Je haalt in de timerinterrupt het A/D conversie data op en start daarna een nieuwe conversie. Die data staat dan bij de volgende interrupt klaar...

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Zover ik me kan herinneringen werkt de Delay_ms in Coocox met de systick en zou dus eigenlijk geen problemen mogen geven.
Dit doordat de systick interrupt een hogere prioriteit heeft dan hw en timer interrupts.
Probeer in een nieuw programma eens een pin te toggelen in combinatie met een Delay_ms en kijk dan op de scoop of dit klopt, dan weet je zeker dat je alle clocken goed hebt ingesteld.

Ps Coocox is dood en alle library's etc zijn ook al een hele tijd niet meer geupdate, misschien zit er in 1 van deze libs nog een buggie die jou programma beinvloed.

Nu twee reacties onder de mijne die kennelijk mijn reactie niet gelezen hebben. De loop kost de delay plus de ADC tijd. De ADC kan je efficient maken. Iedere systick een ADC doen en met GET_ADC de laatste waarde teruggeven o.i.d. Maar dat zal gewoon niet zo zijn.

Mijn codevoorbeeld gaat uit van een paar arduino functies. Als je iets anders programmeert kan het zijn dat je naar alternatieven moet zoeken, maar die zijn er vast.

De "systick", 12 bit ADC en 3.3V doen vermoeden dat het om een ARM processortje gaat. Mijn ARMpjes hebben een interne klok die +/- 1% is (itt de 10% van de interne oscillator van de AVR) Tijd opvragen zal toch wel geimplementeerd zijn....

[Bericht gewijzigd door rew op woensdag 22 januari 2020 16:38:28 (22%)

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 22 januari 2020 12:34:54 schreef Arco:
Bij het opnemen van de sinus zal de tijd langer duren als verwacht. Maar bij weer proberen de sinus te reconstrueren zal dit exact andersom zijn.
(er zijn immers 'stukjes' met onbekende lengte tussenuit geknipt...)

Als bij opnemen een 40mS delay 42mS duurde, zal er bij reconstructie 2mS 'zoek' zijn... (en lijkt dus maar 38mS te zijn)
Ofwel: door interrups krijgt een Delay (en dus sampletijd) een variabele en onbekende lengte. Hoe wou je daar het originele signaal weer mee maken?

Duidelijk verhaal. Bedankt.

Op 22 januari 2020 15:05:23 schreef rew:
Je "sample_loop" wordt doorlopen in de tijd die "delay_ms (40)" kost PLUS de tijd die "get_ADC_channel ()" kost...

probeer eens iets als:

code:


nextsample = millis () + 10;
for (i=0;i<25;i++) {
  while (millis() < nextsample)
     ;
  samples[i] = adc_get_channel (chan); 
  nextsample += 40;
}

helaas is millis zoals je in je laatste bericht aangeeft niet opgenomen in het CoIDE. Een andere manier van tijd opvragen heb ik nog niet kunnen vinden. SysTick blijkbaar, maar daarvan weet ik nog niet hoe ik deze kan gebruiken

Op 22 januari 2020 14:08:04 schreef Arco:
Gebruik een timer interrupt voor sampling: dan is er helemaal geen afwijking.

Ik denk dat ik mijzelf nog eens moet verdiepen in interrupts. Ik denk dat ik dat toch nog niet helemaal begrijp.

Ik ben ooit begonnen aan een universele functie die het aantal ms retourneert sinds de vorige aanroep. Hierin staat ook de code om dat voor een stm32 te doen
Als iemand uitbreidingen/verbeteringen heeft dan hoor ik dat graag

code:


#include <libmaple/systick.h> // STM32
// #include <sys\timeb.h>     // WINDOWS
// #include <time.h>          // LINUX
//######################################################################################################
uint32_t elapsed_ms( void ) {
    static unsigned long previous;
    #if defined(_WIN32) || defined(_WIN64)
        struct timeb now; // #include <sys\timeb.h>
        ftime(&now);
        uint32_t ms = (unsigned long) 1000 * now.time + now.millitm;
    #endif
    #if defined(__APPLE__) || defined(__MACH__)
        // Apple -> #include <...>
    #endif
    #if defined(__FreeBSD__)
        // FreeBSD -> #include <...>
    #endif
    #if defined(__unix) || defined(__unix__)
        // Unix -> #include <...>
    #endif
    #if defined(__linux) || defined(__linux__) || defined(__gnu_linux)
        struct timespec now; // #include <time.h>
        clock_gettime( CLOCK_MONOTONIC, &now );
        uint32_t ms = (unsigned long) 1000 * now.tv_sec + now.tv_nsec / 1000000.0;
    #endif
    #if defined(__AVR__) // no aditional includes needed
        uint32_t ms = millis();
    #endif
    #if defined(_LIBMAPLE_SYSTICK_H_) // STM32
        //  #include <libmaple/systick.h>
        uint32_t ms = systick_uptime();
    #endif
    uint32_t diff = ms - previous;  
    previous = ms;
    return diff;
}

Ik gebruik als het even kan geen delay functies omdat de controller dan een tijd lang helemaal niets doet. Nu laat ik de controller in een loop zinnige dingen doen en als de tijd verstreken is (gemeten met elapsed_ms) dan voer ik de volgende stap uit.

reading can seriously damage your ignorance

Op 22 januari 2020 22:31:05 schreef Christiaan371:

helaas is millis zoals je in je laatste bericht aangeeft niet opgenomen in het CoIDE. Een andere manier van tijd opvragen heb ik nog niet kunnen vinden. SysTick blijkbaar, maar daarvan weet ik nog niet hoe ik deze kan gebruiken

dan zal je moeten gaan tweaken en uitzoeken hoeveel tijd je 'verliest' bij de handelingen van de code.
en de rest van die tijd opvullen met de delay.

net zoals je vroeger klokpulsen kon tellen voor elke instructie dat de cpu moest doen

ik hou van werken ..., ik kan er uren naar kijken
Arco

Special Member

Da's een gecompliceerde manier om te doen wat een interrupt op simpele wijze doet... ;)
Om sampling met vaste sampletijd te krijgen kom je eigenlijk niet om een timerinterrupt heen...

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Op 23 januari 2020 14:01:38 schreef Arco:
Om sampling met vaste sampletijd te krijgen kom je eigenlijk niet om een timerinterrupt heen...

Ben ik met je eens, maar als de tijd tussen samples 40ms is en de afwijking 2ms dan is de vraag eerst hoe het komt dat die verstorende interrupts 2ms duren. Voor een microcontroller is 2ms erg lang. Zeker voor een STM32 op een clock >= 72MHz

Misschien wordt de resolutie beter met een hogere sample frequentie en het gebruik van interrupts maar ik denk dat het probleem in deze situatie door wat anders wordt veroorzaakt

Da's een gecompliceerde manier

Valt mee, als je alle defines voor andere omgevingen verwijdert dan blijven er maar een paar regels code over.
Het lijkt erger dan het is.

reading can seriously damage your ignorance

Op 22 januari 2020 22:31:05 schreef Christiaan371:
[ Een andere manier van tijd opvragen heb ik nog niet kunnen vinden.

IN de handleiding op p22 staat dat er CoGetOSTime() is.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 23 januari 2020 16:10:30 schreef rew:
[...]IN de handleiding op p22 staat dat er CoGetOSTime() is.

Deze vind CooCox nie leuk bij mij :(

CogetOSTime is onderdeel van het Coocox RTOS. Een poging van Coocox om zelf een rtos systeem te maken. Dit rtos moet je dan wel eerst aan je code toevoegen.
Volgens mij gaat die handleiding in zijn geheel over het Coocox rtos.
Lekker niet gebruiken, als je een rtos wilt neem dan iets wat wordt ondersteund zoals Freertos.