Arduino, strippen van decimale waarde

Ik heb het nog niet allemaal gelezen hoor, maar gaat de truc met delen en vermenigvuldigen wel altijd goed? Niet alle getallen zijn namelijk in een float te stoppen.

Bijvoorbeeld:
0.3 + 0.6 => 0.89999999999999991

PE2BAS

float resultaat = 0.0;
int counterwaarde = 2345;
1 : resultaat = counterwaarde / 10; >> geeft interger
2 : resultaat = counterwaarde / float(10); geeft integer
3 : resultaat = float(counterwaarde / 10); geeft integer
4 : resultaat = counterwaarde / 10.0; geeft een float

Klopt niet.

In alle bovenstaande gevallen gevallen krijg je een float want resultaat is als float gedeclareerd.

counterwaarde en 10 zijn allebei integers, dus dan wordt er een integer deling gedaan met als resultaat 234 en die wordt dan omgerekend naar float.
-- Geval 1 en 3. --> 234.00

float(10) en 10.0 zijn allebei floats. Dus dan wordt er een float deling uitgevoerd met als resultaat 234.5.
-- Geval 2 en 4. --> 234.50

Een berekening is altijd in floats(doubles) als minstens 1 van de operands een float is.

Nog een ander trucje:

c code:


float test = 1863.16843;
int A = test;			//Alles voor de comma
int B = (test*100f)%100;	//2 cijfers achter de comma
int C = (test*1000f)%1000;	//3 cijfers achter de comma

Probeer het zelf:
http://cpp.sh/3b4am

PE2BAS
benleentje

Golden Member

Op 1 april 2022 16:42:04 schreef deKees:
[...]

Klopt niet.

Daar denkt mijn LCD scherm toch heel anders over alleen met de laatste krijg ik ook een float op mijn display, ben er al met al een lange tijd mee bezig geweest om een float op het lcd te krijgen.

Maar nu ik wat testjes doe met de seriële monitor krijg ik wel altijd een float te zien. En blijkbaar werkt snprinf niet op arduino uno maar op de due werkt het wel daar heb ik het ook eerder voor gebruikt.

Maar waarom het op het LCD scherm niet lukte is me toch een raadsel

[Bericht gewijzigd door benleentje op zaterdag 2 april 2022 00:13:51 (31%)

Mensen zijn soms net als een gelijkrichter, ze willen graag hun gelijk hebben.
blackdog

Golden Member

Hi,

Hieronder de code zover ik vandaag gekomen ben, het wordt al steeds meer zoals ik het hebben wil.
De tekst staat nu goed op het display en de meeste functies werken nu goed.
Ik heb nog gezocht naar dit teken: Ω maar die kan ik niet vinden in de gebruikte font, jammer maar ik ga daar niet moeilijk voor doen.

Ik doe de ADC functie nu ongeveer 1x per seconde, dan wordt misschien nog wat langzamer, zo maak ik een beetje hysteresys voor de foutfunctie als een onderdeel te heet wordt.

De temperatuur voor de foutmeldingen staat nu op 70C, maar dat moet ik nog meten als de versterker afgebouwd is, wat het uiteindelijk moet worden.
De code moet ik nog schrijven als een van de te hoge temperaturen bereikt wordt.

Dat zal dan nog wat schuiven worden in de code, want ik wil de ruimte van de bovenste twee regels gebruiken om deze fout duidelijk aan te geven.
Het is allemaal wel luxe in deze Breedband versterker, maar ik leer er weer een stukje coderen bij, en dat is ook wat waard.
En ik heb er nu wat tijd voor om het een en ander uit te zoeken.

c code:


#include <JC_Button.h>                                                          // https://github.com/JChristensen/JC_Button
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4

#include <ADC.h>
#include <ADC_util.h>
ADC* adc = new ADC();

//Gebruikt referentie spanning voor de ADC
float Ref = 3.288;

// Kalibratie waarden ter kalibrtie TMP37 sensoren 
float T_Amp_Cor = 0;
float T_PSU_Cor = 0;
float T_Trafo_Cor = 0;

// Error temperatuur per sensor, geeft de maximale temperatuur aan die bereikt mag worden per meetpunt in graden Celcius
int T_Amp_Err = 70;
int T_PSU_Err = 70;
int T_Trafo_Err = 70;


// Variabelen nodig voor de temperatuur omzetting
float T_Amp = 0,  T_PSU = 0,  T_Trafo = 0;
int T_Amp_D = 0, T_PSU_D = 0, T_Trafo_D = 0;

// Variabelen voor timing weergave temperatuur, ongeveer 1 x per seconde
const unsigned long E_I_Ta = 1000;
unsigned long P_T_Ta = 0;

const unsigned long E_I_Tp = 1020;
unsigned long P_T_Tp = 0;

const unsigned long E_I_Tt = 1040;
unsigned long P_T_Tt = 0;
// Variabelen voor timing weergave temperatuur, ongeveer 1 x per seconde

//Tijdelijk voor test
float jaap = 0;

// Setup Display
Adafruit_SSD1306 display(OLED_RESET);

// pin assignments
const byte
    Output_1(4),                                                      // Input relais, switch 50 Ohm or 1K Impedance
    Output_2(5),                                                      // Output Enable Relais
    Output_3(6),                                                      // LT1210 Enable, used for "High Temp" condition
    BUTTON1_PIN(2),                                                   // connect a button switch from this pin to ground, internel pull-up is used
    BUTTON2_PIN(3);                                                   // connect a button switch from this pin to ground, internal pull-up is used


ToggleButton                                                          // define the buttons fuction
    btn1(BUTTON1_PIN, false, 5),                                      // this button's initial state is off, used for = Input Impedance
    btn2(BUTTON2_PIN, false, 5);                                      // this button's initial state is off, used for = Output Enable

// variabele voor knop status
byte state_btn1;
byte state_btn2;





void setup()   {                
Serial.begin(9600);

// ADC0 Settings Teensy-4, Teessy-LC
    adc->adc0->setAveraging(64);                                            // set number of averages
    adc->adc0->setResolution(16);                                           // set bits of resolution
    adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);    // change the conversion speed
    adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED);             // change the sampling speed


// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);                              // initialize with the I2C addr 0x3C (for the 128x32)

// Clear Display buffer.
    display.clearDisplay(); 

// initialize the button objects
    btn1.begin();
    btn2.begin();

// set the vollowing pins as outputs
    pinMode(Output_1, OUTPUT);
    pinMode(Output_2, OUTPUT);
    pinMode(Output_3, OUTPUT);
    
// show the initial states
    digitalWrite(Output_1, btn1.toggleState());
    digitalWrite(Output_2, btn2.toggleState());
}





void loop() {

// read the buttons
    btn1.read();
    btn2.read();

//  Bepaal de status van de knop uitgangen
    state_btn1 = digitalRead(4);
    state_btn2 = digitalRead(5);


// LT1210 temperatuur berekening 
    unsigned long C_T_Ta = millis();
    if ( C_T_Ta - P_T_Ta >= E_I_Ta ) {
    T_Amp = adc->adc0->analogRead(14);
    T_Amp_D = (T_Amp*Ref/adc->adc0->getMaxValue()/0.02) +(T_Amp_Cor);
    P_T_Ta = C_T_Ta;
    }
    
// Voedings temperatuur berekening
    unsigned long C_T_Tp = millis();
    if ( C_T_Tp - P_T_Tp >= E_I_Tp ) {
    T_PSU = adc->adc0->analogRead(15);
    T_PSU_D = (T_PSU*Ref/adc->adc0->getMaxValue()/0.02) +(T_PSU_Cor);
    P_T_Tp = C_T_Tp;
    }

// Trafo temperatuur berekening
   unsigned long C_T_Tt = millis();
    if ( C_T_Tt - P_T_Tt >= E_I_Tt ) {
    T_Trafo = adc->adc0->analogRead(16);
    T_Trafo_D = (T_Trafo*Ref/adc->adc0->getMaxValue()/0.01) +(T_PSU_Cor);  
    P_T_Tt = C_T_Tt;
    }

// Plaats variabelen en vaste tekst voor de temperatuur metingen op het display en maakt eerst het display leeg
  display.clearDisplay();
  
  String T_Amp_Display =  String(T_Amp_D);                 
  robojaxText(T_Amp_Display, 17, 25, 1, false);                         // Plaatst de temperatuur variabel van de LT1210 op het display
  robojaxText("Ta", 1, 25, 1, false);
  robojaxText("C", 30, 25, 1, false);

  String T_PSU_Display =  String(T_PSU_D);                 
  robojaxText(T_PSU_Display, 62, 24, 1, false);;                        // Plaatst de temperatuur variabel van de voeding op het display
  robojaxText("Tp", 47, 24, 1, false);
  robojaxText("C", 76, 24, 1, false);

  String T_Trafo_Display =  String(T_Trafo_D);                 
  robojaxText(T_Trafo_Display, 108, 25, 1, false);;                     // Plaatst de temperatuur variabel van de trafo op het display
  robojaxText("Tt", 92, 25, 1, false);
  robojaxText("C", 122, 25, 1, false);

  

// Plaatsen van in en uitgang relais status op het schrm
  if (state_btn1  == 1 ) {
  robojaxText("OUTPUT           ON", 0, 1, 0, false);  
  } else {
  robojaxText("OUTPUT           OFF", 0, 1, 0, false);
  }

  if (state_btn2  == 1 ) {
  robojaxText("INPUT Impedance  1K", 0, 12, 1, false);  
  } else {
  robojaxText("INPUT Impedance  50", 0, 12, 1, false);
  }
 

  display.display();

//////////////////////////////////////////////////////////////////////////////////
//  testcode  
    
    Serial.println("LT1210");

    jaap = (T_Amp*3.288/adc->adc0->getMaxValue() /0.02) +(T_Amp_Cor);
    
    Serial.println(jaap);
    
    Serial.println("");


    
  Serial.println(state_btn1);
  Serial.println(state_btn2);

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//Temperatuur error meldingen






//////////////////////////////////////////////////////////////////////////////////  
}
  
void robojaxText(String text, int x, int y,int size, boolean d) 
    {
    display.setTextSize(size);
    display.setTextColor(WHITE);
    display.setCursor(x,y);
    display.println(text);
  if(d){
        display.display();
       }
  
 // if button state changed, update the LEDs
    if (btn1.changed()) digitalWrite(Output_1, btn1.toggleState());
    if (btn2.changed()) digitalWrite(Output_2, btn2.toggleState());   
    
    
    }

Natuurlijk is de bovenstaande code nog niet netjes gemaakt en ook de omschrijving is nog niet zoals ik het wil, dus hou daar rekening mee i.v.m. commentaar. :+

Wat betreft de omzetting van wel of geen decialen in het display, ik gebruik nu float naar een int om de decimalen kwijt te raken voor het display.
De kalibratie en de temperatuur fout gaat wel via twee decimalen worden uitgevoerd.

Shoot,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Op 1 april 2022 23:48:31 schreef benleentje:

Maar waarom het op het LCD scherm niet lukte is me toch een raadsel

hangt enorm af van de libraries van de LCD's.
sommige willen char[]'s hebben, andere enkel strings. de ene aanvaard floats, de andere doet er een int() mee.
in 1 geval kreeg ik zelf gewoon een naam mee van een andere variabele bij het doorsturen van een float naar een print. kreeg je bij het sturen van 'temp' op het display 'batteryVoltage' als tekst te zien. moet ergens een pointer zijn geweest maar zelf nooit de fout gevonden.

daarom probeer ik altijd in mijn hele programma integers te gebruiken (x10 of x100 afhankelijk hoeveel decimalen ik wil) en op het einde deel ik daar door 10 of 100, cast het naar string en dat naar het display. als string weet ik tenminste zeker wat er op het schermpje komt, en verdere berekeningen doe ik wel met de initiele waarde

[Bericht gewijzigd door fcapri op zaterdag 2 april 2022 05:57:55 (17%)

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

Golden Member

Hi,

De laatste twee dagen wat video's bestudeerd die mijn Arduino progrmmeer kunsten helpen te verbeteren, tenminste, dat hoop ik. ;)

Ik heb nog een vraag, en die plaats ik ook in dit topic omdat het uiteindelijk om het zelfde script gaat.

Ik ben de temperatuur meetingen op een bepaalde tijd aan het doen via de Millis functie.
De bedoeling is de TMP37 sensoren iedere 0,5 tot 2 seconde uit te gaan lezen.
Dit heb ik na het het bestuderen van de stof hierover nu onder de knie denk ik.

De vraag!
Ik stel nu de status van de Millis Counter bij iedere TMP37 sensor vast, dit is misschien wat veel van het goede/onnodig?

Ik vraag mij ht volgende af, wanner houden jullie rekening met de tij dat de loop loopt,
Als je zeg precies na 1-Seconde iets uitwilt voeren na de laatste keer dat je dit deed, dan ben je volgens mij toch afhankelijk
van hoelang de loop loopt met de variabelen van de tijd die optreed van meerdere funties binnen de loop.

Mijn metingen van rond de 1-Seconde is natuurlijk totaal niet van belang, ik ga echt niet met een klokje kijken of de trafo temperatuur wel precies na 1-seconde wordt ge-updated. :)

Code zoals de TMP sensoren nu getimed worden.
De timing is per sensor iets anders als experiment die ik gisteren gedaan heb.

c code:



// Variabelen voor timing weergave temperatuur, ongeveer 1 x per seconde
const unsigned long E_I_Ta = 1000;
unsigned long P_T_Ta = 0;

const unsigned long E_I_Tp = 1020;
unsigned long P_T_Tp = 0;

const unsigned long E_I_Tt = 1040;
unsigned long P_T_Tt = 0;
// Variabelen voor timing weergave temperatuur, ongeveer 1 x per seconde


// LT1210 temperatuur berekening 
    unsigned long C_T_Ta = millis();
    if ( C_T_Ta - P_T_Ta >= E_I_Ta ) {
    T_Amp = adc->adc0->analogRead(14);
    T_Amp_D = (T_Amp*Ref/adc->adc0->getMaxValue()/0.02) +(T_Amp_Cor);
    P_T_Ta = C_T_Ta;
    }
    
// Voedings temperatuur berekening
    unsigned long C_T_Tp = millis();
    if ( C_T_Tp - P_T_Tp >= E_I_Tp ) {
    T_PSU = adc->adc0->analogRead(15);
    T_PSU_D = (T_PSU*Ref/adc->adc0->getMaxValue()/0.02) +(T_PSU_Cor);
    P_T_Tp = C_T_Tp;
    }

// Trafo temperatuur berekening
   unsigned long C_T_Tt = millis();
    if ( C_T_Tt - P_T_Tt >= E_I_Tt ) {
    T_Trafo = adc->adc0->analogRead(16);
    T_Trafo_D = (T_Trafo*Ref/adc->adc0->getMaxValue()/0.01) +(T_PSU_Cor);  
    P_T_Tt = C_T_Tt;
    }

Dus hoe doen jullie het als het je precies wilt hebbenwat timing betreft, gaat dit an via interupts?

Groeten en vast bedankt.
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Ik doe dat meestal zo:

code:


 unsigned long C_T_Tt = millis();
    if ( C_T_Tt - P_T_Tt >= E_I_Tt ) 
    {  ...  
       P_T_Tt += E_I_Tt;
    }

Door P_T_Tt telkens te verhogen met E_I_Tt krijg je strakke intervals. Dan kan het wel eens gebeuren dat je te laat bent, maar dan wordt de volgende interval vanzelf korter.

code:


unsigned long OldTime = 0;
const int Interval = 100;
unsigned long currentMillis;

void loop(){
  currentMillis = millis();
  if(currentmillies - OldTime >= Interval) {
    OldTime = currentmillies;   
    ...
    do something
    ...
  }

quasi hetzelfde als dekees, alleen iets leesbaardere variabelen.

eigenlijk is dit stuk code een deel om met threads te werken.

code:


unsigned long OldTime1, Oldtime2, Oldtime3, Oldtime4 = 0;
const int Interval1 = 100;
const int Interval2 = 200;
const int Interval3 = 300;
const int Interval4 = 400;

void loop(){
  unsigned long currentMillis = millis();
  func1(currentMillis);
  func2(currentMillis);
  func3(currentMillis);
  func4(currentMillis);  
}

void func1(unsigned long millies){
  if(millies - OldTime1 >= Interval1) {
    OldTime1 = millies;   
    ...
    do something
    ...
}

void func2(unsigned long millies){
  if(millies - OldTime2 >= Interval2) {
    OldTime2 = millies;   
    ...
    do something
    ...
}

void func3(unsigned long millies){
  if(millies - OldTime3 >= Interval3) {
    OldTime3 = millies;   
    ...
    do something
    ...
}

void func4(unsigned long millies){
  if(millies - OldTime4 >= Interval4) {
    OldTime4 = millies;   
    ...
    do something
    ...
}

met die interval waardes bepaal je dan hoeveel cpu tijd elke functie krijgt

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

Op 3 april 2022 16:23:00 schreef blackdog:
Dus hoe doen jullie het als het je precies wilt hebbenwat timing betreft, gaat dit an via interupts?

Wat bedoel je met precies?
- Dat alle 3 sensoren op vrijwel hetzelfde moment worden gelezen
- Dat het aantal uitlezingen per uur precies 1Hz is?
- Dat de variatie in de periodetijd zo laag mogelijk is?

millis() wordt door de Arduino-librarys met een interrupt bijgehouden. Een timer van de onderliggende hardware wordt ingesteld op 1kHz, en iedere keer als die afgaat wordt in een interrupt een globale teller opgehoogd. Die teller lees je uit met millis.

Afhankelijk van de chip waarmee je arduino gemaakt is kan die globale teller ook een hardware teller zijn, en dan is er dus geen interrupt nodig.

Meestal is jouw methode (in een loop wachten tot de millis() teller voldoende opgehoogd is) ruimschoots voldoende nauwkeurig.

- Als je drie metingen precieser tegelijk wil hebben:
Je loopt tegen de HW-beperking aan dat microcontrollers meestal maar 1 ADC hbben die via een multiplexer gedeeld is. Enige wat je kunt doen is alle meteingen direct achter elkaar.

- Als je de meetfrequentie precies op 1Hz wil hebben:
Controleer je klokbron. Meestal is het een kristal, dus zit je vrij nauwkeurig. Dat sluit niet uit dat je (om dat het makkelijk is) toch op 1.024Hz zit, of de reciproke (1/1.024) daarvan.
Als je niet in de daadwerkelijke implementatie van je Arduino, of in de details van de microncontroller-atchitectuur wil duiken kun je alleen maar meten. Gebruik de "blink without delay" sketch.

- Als je variatie in periode-tijd zo laag mogelijk wil houden
Doe de meting in een interupt, en zorg (indien van toepassing) dat je interrupt de hoogse prioriteit heeft.
Als je niet zelft interrupt routines wil maken (en gewoon in de standaard Arduino-tuin wilt blijven):
- Doe je periode-bepaling als eerste in loop. Kijk eerst of het tijd is voor je tijd-kritische dingen. Doe je niet tijd-kritische dingen (aanpassen LCD bijvoorbeeld) ALLEEN gelijk na de tijdkritische, wanneer je nog de meeste tijd over hebt totdat er iets anders moet gebeuren.

@fcapri

Dat is niet helemaal hetzelfde.
Als je een keer te laat bent tgv andere funkties in de loop dan komt het nooit meer goed in jouw stukje code.

Maar door de interval op te tellen wordt de te late aktie weer ingehaald.

Methode 1

code:


  uint32_t Now = millis();
  if((Now - T) > Interval)
  {  T += Interval;
  }   

Methode 2

code:


  uint32_t Now = millis();
  if((Now - T) > Interval)
  {  T = Now;
  }   

Stel T = 1000
Interval = 1000

Als je nu checkt wanneer millis() = 2020 (dus 20 millies te laat) :
T = Now (= 2020) --> Volgende timeout is pas op 3020
Maar
T += Interval (= 2000) --> Volgende timeout vanaf 3000

Dat hoeft niet altijd een probleem te zijn. Dat hangt er maar vanaf hoeveel processing je in de loop hebt en hoe belangrijk het is dat alle intervallen gemiddeld even lang zijn.

Bij methode 1 wordt een te late interval weer ingelopen doordat de volgende interval korter wordt. Die krijgt in bovenstaande voorbeeld nog maar 980 millies en komt dus wel op het juiste moment.

Ik haat het herhalen van "standaard" stukjes code.

D'r zullen wel een paar syntax fouten in zitten, maar dit is het idee....

code:


struct timedfunc {
  int interval; 
  func_t func_to_call;
  int next;
};

struct timedfunc funcs[]={
  {100, func1, },   
  {200, func2, },   
  {300, func3, },   
  {400, func4, }};

void loop ()
{
  int m;
  m = millis ();
  for (i=0;i< 4;i++) 
    if (funcs[i]. next < m) {
       func[i].func_to_call ();
       func[i].next += func[i].interval;
    }
}

}

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

Golden Member

Hi blurp,

Ik gaf al aan dat de timing van het uitlezen van de temperatuur sensoren hier helemaal niet van belang is.
Maar ik leer graag bij, dus toen ik de "millis" functie in deze code ging toepassen dacht ik direct, hoe nauwkeurig is dit eigenlijk?
Allerlij andere zaken in de loop verbuiken namlijk ook tijd.

Als je b.v. millis iedere 5 mSec wilt gebruiken, dan wordt meestal je totale looptijd belangrijk.
En natuuijk weet ik dat dit een microcontroler is en niet een i7 12700K zoals in mijn werkstation.

Ik wordt dagelijk geconfronteerd met te krappe microcontrolers in mijn meetapparatuur, die ondanks interrups gebruik niet confortable te bedienen zijn.
Mijn Hameg/R&S scoop is daar een goede uitzondering op.

Maar goed, de vraag die ik stel is vooral om kennis op te doen zodat ik voor zo min mogelijk verrassingen kom te staan. ;)

De anderen beantwoordt ik later even, het is hier nogal druk in de omgeving hier, en daardoor kan ik mij niet goed concentreren op jullie oplossingen. (Men denkt hier muziek te kunnen maken door de genuttigde drank, :-) )

Groet,
BRam

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Op 3 april 2022 20:00:38 schreef deKees:
@fcapri

Dat is niet helemaal hetzelfde.
Als je een keer te laat bent tgv andere funkties in de loop dan komt het nooit meer goed in jouw stukje code.

het is maar hoe kritisch het is. dit wordt dus gebruikt in thread waarbij je verschillende processen een deel van de cpu tijd geeft.
als een process dan wat uitloopt omdat het aan het verwerken is, is niet echt een probleem.

uw methode is idd precizer, maar wat als de data nooit verwerkt kan worden? als de ene 110ms nodig heeft en er maar 100 krijgt.
2de process heeft 150ms nodig, en krijgt er maar 120
... dan haalt het ook nooit meer in omdat je meer en meer achterloopt.

100% perfecte oplossing is er niet echt.
ik gebruik bovenstaande voor bv sensoren uit te lezen die niet van belang zijn.
bv in mijn auto ooit zoiets gemaakt.
de accu spanning is realtime, de parkeersensoren zijn realtime (boeit me weinig als ik 5sec geleden op 1cm van het object reed).
die meten dus volcontinue en geven de data op display. display schrijven staat in de loop.

het meten van de temperatuur van de motor, het brandstofniveau en het klokje zijn dingen die trager mogen.
k geloof dat de brandstof en temperatuur elke seconde worden gemeten, met een gemiddelde over 10 metingen (dus slechts een display update elke 10sec.
het klokje heb ik nog trager gemaakt, ik lees namelijk een RTC uit en de volgende keer dat ik die uitlees, laat ik hem exact 60sec wachten (seconden zitten er niet in).
het is nutteloos om in mijn loop 10keer per seconde de tijd uit te lezen en weer te geven en daar werkt bovenstaande perfect op.
en als de brandstofmeter uitgelezen wordt na 1050ms ipv 1000ms, ligt niemand wakker van. zal zoveel niet schelen in zijn berekening :-)

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

@fcapri
Dit wordt ook gebruikt in de single-thread loop() van een arduino.

Als de loop soms ook andere dingen moet doen dan kan de timer wel eens te laat komen. Voor het updaten van de brandstof meter niet zo spannend inderdaad, maar voor het updaten van een software klok weer wel.

@blackdog
millis() is in principe even nauwkeurig als het xtal. millis() loopt soms op een hardware timer en soms op een timer interrupt. Die interrupt kan wel eens een paar micro's te laat komen. Maar toch.

delay_ms() is doorgaans een tel lus. Die geeft in principe een vaste vertraging, maar die wordt langer naarmate er meer interrupts binnenkomen. Want die worden niet meegeteld.

benleentje

Golden Member

Als je b.v. millis iedere 5 mSec wilt gebruiken, dan wordt meestal je totale looptijd belangrijk.
En natuuijk weet ik dat dit een microcontroler is en niet een i7 12700K zoals in mijn werkstation.

Nog niet met milisecondens. In 1 milliseconde kan een 20Mhz controller al 20.000 opdrachten uitvoeren dat is voor microcontroller begrippen zeg een week. Zolas deKees zegt is millis redelijk nauwkeurig zal hooguit een paar klokpulsen achter lopen.

Als er op de micro seconde nauwkeurig iets moet gebeuren ja dan speelt de looptijd mee, en je zal dat zeker moeten incalculeren.
JE kan dan niet meer met millis of micro's werken maar moet dan echt een eigen interrupt gaan gebruiken. En met interrupt komt daar dan ook weer een aantal klokpulsen bij die je moet incalculeren.

Als het heel erg tijd kritisch gaat worden dan kan er een moment komen dat je dat stukje in assembly moet gaan schrijven omdat je van assembly exact weet hoe lang elke instructie duurt, van een stukje C++ code weet je dat eigenlijk noot, maar dat zou ook wel weer te timen zijn. Je zal dan in ieder geval ook tijdelijk interrupts uit moeten zetten.

Maar dan ook weer 1 microseconden is voor een 20Mhz controller maar 20 instrcuties en een 400Mhz controller heb je weer genoeg tijd.

Je kan ook het bereiken van een exacte timing vergelijken met jouw proces van elektronica ontwikkelen, je ontwerpt iets bouwt iets, meet aan iets, en na de meting pas je het ontwerp van iets weer aan enz.
Zo kan je dat ook met de software doen, totdat de timing goed genoeg is

Mensen zijn soms net als een gelijkrichter, ze willen graag hun gelijk hebben.
blackdog

Golden Member

Hi,

Wat ik bedoel is dit, en ik denk dat deKees het al een beetje aangeeft.
Dat de millis en micros direct gerelateerd zijn aan de Clock source van de microcontroler is mijn natuurlijk duidelijk.

Als ik nu b.v. iedere 2 millis een puls van 100uSec wil hebben op een uitgang en de rest van de code in de loop varieerd tussen zeg 0,3 en neem eens aan 1,4mSec, dan blijft voor zover mijn kennis rijkt, weinig over van de iedere twee mllis dat ik een puls op een bepaalde uitgang wil hebben.

Dus vergeet mijn timing van de temperatuur meting die ik gebruik het gaat mij om het beeld wat ik hierboven schets met de korte tijden voor de puls en de in verhouding lange tijd die de rest van de code nodig heeft in de loop.

Dus daarom vroeg ik in het beging, als je de korte millis tijd die ik voorstelde in een Interrupt laat lopen blijft de 100uSec puls dan wel mooi regelmatig lopen?

Het is natuurlijk heel goed mogelijk dat ik het verkeerd zie, wat er allemaal op de achtergrond gebeurd.
Ik begrijp ook wel dan de millis counter een achtergrond proces is die zich niets aantrek van de code die de processor gaat runnen, maar je kan met de korte tijden volgens mij niet voorbij gaan aan de tijd die de code in de loop moet lopen als je pulsen wilt genereren die in de buurt komen van je looptijd.

Ik probeer de grensen duidelijk te krijgen in mijn hoofd, ik heb er al een beetje naar gezocht maar daar weinig over gevonden, vandaar mijn vraag hier. :)

Dank en groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Op 4 april 2022 02:56:42 schreef blackdog:
...maar je kan met de korte tijden volgens mij niet voorbij gaan aan de tijd die de code in de loop moet lopen als je pulsen wilt genereren die in de buurt komen van je looptijd.

Dat klopt als een bus. Als je pulsen van 0.1ms wil maken, en je hebt activiteiten in loop() die 1ms duren, dan kun je die pulsen niet vanuit loop() maken.

In het algemeen doe je in loop() dingen die of niet tijdkritisch zijn, of langer duren dan alle activiteiten in loop().

Bijvoorbeeld een Li-acculader uitzetten is tijd-critisch op minuten schaal. Dat mag best een iteratie van loop (als die 2ms duurt) later gebeuren. Af en toe een display update missen (als je die normaal elke 20ms doet) hoeft ook niet erg te zijn.

Als je een puls van 100us wil, terwijl je in loop 2ms bezig bent dan zijn er twee goede en een matige oplossing:

- Gebruik een timer om de puls te maken: Hangt een beetje van de mogeljkheden van je controller af, maar meestal kunnen ze hardwarematig pulsen met instelbare frequentie en lengte maken. Ook single-shot. Dit is het meest nauwkeurig, want er komt behalve het instellen geen software aan te pas. Helaas zul je dan zelf in de details van je processor moeten duiken, want er is bij mijn weten geen Arduino ondersteuning voor (of het moet via een PWM module kunnen)

- Gebruik een timer om een interrupt te maken. En toggle de juiste pin vanuit de interrupt. Bijna even nauwkeurig als de eerste methode, en meer flexibiliteit in pin en patroonkeuze (als je elke 100us een interrupt hebt, kun je prima een puls van 100us en een van 300us maken. Kwestie van aantal interrupts tellen.

- Tos slot, als je de 100us puls minder vaak nodig hebt dan dat je loop() itereert, dan kun je gewoon 100us wachten in loop. Lelijk, maar effectief.

Als de timing kritisch is gebruik ik de peripherals of de interrupts, afhankelijk wat de processor kan.

- Peripherals,
Sommige controllers hebben een peripheral die je zo kan instellen dat het precies doet wat je wilt. In dat geval is de CPU verder niet belast met het genereren van het signaal.

- Interrupts,
Een interrupt gebruikt onderwater nog steeds een peripheral. In dit geval is dat een timer. Die stel je in zodat hij na een bepaalde periode een interrupt genereerd. Wanneer deze interupt plaatsvind, zal de processor ineens naar een ander stuk code springen. Daarna gaat hij weer terug waar hij gebleven was. (Dit 'springen' komt wel met wat complicaties.)
NOTE: Interupts hebben een prioritijd, Dat wil zeggen, het kan dus zijn dat een andere interupt voorrang krijgt op de interrupt van de timer.

Minder kritische timing:
- Fixed Priority Scheduling (Voorbeeld van fcapri en rew)
- Preemtive Priority Scheduling (Bijvoorbeeld FreeRTOS)

Hier heb ik ook nog wel een pdfje over liggen mocht je het interessant vinden.
Hmm, dit verhaal is net iets kort, en er staat wat door elkaar. Als je het interessant vind kan ik vanavond wel eens kijken of ik het wat beter neer kan zetten.

PE2BAS
benleentje

Golden Member

Als ik nu b.v. iedere 2 millis een puls van 100uSec wil hebben op een uitgang en de rest van de code in de loop varieerd tussen zeg 0,3 en neem eens aan 1,4mSec, dan blijft voor zover mijn kennis rijkt, weinig over van de iedere twee mllis dat ik een puls op een bepaalde uitgang wil hebben.

Dat zie je toch verkeert er blijft minimaal zelfs 0,6mS over. In dit geval kan je dus zelfs met een blokerende delay van 100µS werken en hebt daarna nog 1,9mS over. Als je langste routine dan 1,4mS dan heb je nog steeds 0,5mS over.

Zelfs 100µS is voor een arduino uno op 20Mhz nog 2000 klokpulzen, als de cpu per klokpuls 1 instuctie kan uitvoeren loopt het dan door 2000 x 16 bit = 4kByte aan software.

De Uno heeft 32kB aan flashgeheugen en theoretisch loopt het daar in 16000 instructies/ klokpulsen doorheen of in 0,8mSec. Dit is dan zonder jump instructies, interrupts en uitgebreide instructies die meer dan 1 klokpuls duren. Dit om even te relativeren naar de tijdschaal waarin een cpu / microcontroller werkt.

Dat er in de software een hoop loops zitten die lang duren heeft voornamelijk te maken als er bv naar een relatief trage I2C display geschreven moet worden, dan gaat de tijdbasis van 20Mhz ineens terug naar 100kHz (standaard voor Arduino) of dat je in C++ nu eenmaal met weinig regels ingewikkelde berekeningen kan maken. Ook een sprong naar een subroutine en terug vergt veel meer tijd dan je zou verwachten, maar dat is voor nu even te ingewikkeld om uit te leggen.

Mensen zijn soms net als een gelijkrichter, ze willen graag hun gelijk hebben.

Mocht je het interessant vinden, FreeRTOS heeft goeie uitleg over scheduling.

https://www.freertos.org/implementation/a00004.html

PE2BAS
blackdog

Golden Member

Ha hardbass,

Ik heb wat gelezen over freetos maar dat gaat te ver voor wat ik nu aan het doen ben.

Wat ik nu heb reageert goed en hoef ook de wat moeilijker dingen voor mij nog niet toe te passen omdat de gebruikte microcontroler lekker snel is.
Verder heb ik ook video's bekeken over interrupts en stukjes er over gelezen.

Maar de meeste tijd is gaan zitten in het netjes formatteren van de tekst op het display, zowel het logo en wat info bij het opstarten als de standaard tekst.
Het gebruikte display is maar klein met zijn 128x32 pixels mar het is gelukt het netjes te krijgen, later wat plaatjes van wat ik gefrutselt heb.

Maar eerst weer een vraag...
Ik heb boven in de code drie variabel staan die van drie punten de maximale temperatuur aangeven die een onderdeel mag bereiken, dat ziet er zo uit.

c code:


// Error temperatuur per sensor, geeft de maximale temperatuur aan die bereikt mag worden per meetpunt in graden Celcius voor de foutmelding
int T_Amp_Err = 85;
int T_PSU_Err = 65;
int T_Trafo_Err = 70;

Nu kan ik drie maal een IF statement onder elkaar zetten, maar volgens mij moet je ook het kunnen regelen op een regel.
Als een van de drie, twee stuks of alle drie waar zijn, moet ik naar een ander stuk code springen en de bovenste twee regels van het display
gaat dan de temperatuurfout aangeven en een anatal zaken wordt tijdens de fout uitgeschakeld.

Ik heb er naar gezocht, maar begrijp het nog niet goed genoeg, ik kwam stukjes code tegen met "&& xxx && xxx" maar is het simpel mogelijk
in elkaar te zetten dat als minimaal een van de variabel waar is, de mijn error code gaat werken?

Of moet ik drie maal een "IF" statement onder elkaar zetten?

Ik wou het nog wat uitgebreider laten zien wat ik gefrut had, maar moet nu Burito's maken voor de familie! :+

Dank en groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Hallo,

Dat freertos is inderdaad overkill voor jouw toepassing. Was meer om eens in te lezen. Ik vind de theorie erachter wel erg interessant. Deze vorm van scheduling komt ook met een aantal nadelen / complicaties.

Wat betreft je vraag:
De && betekend een logische AND. Dus allebei moet waar zijn. In jou geval moet er 1 of meer waar zijn dus wil je een logische OR.

c code:


if( (tAmp > T_Amp_Err) || (tPsu > T_AmT_PSU_Err p_Err) || (tTrafo> T_Trafo_Err ))
{
   //Handle error stuff
}

In C heb je de volgende smaakjes:
&& = Logsiche AND
& = Bitwise AND
|| = Logische OR
| = Bitwise OR

PE2BAS
blackdog

Golden Member

Hi hardbass, :-)

Dank je, ik moet je bekennen dat in niet naar de OR functies heb gezocht, ik heb dat nog nooit nodig gehad maar had dit natuurlijk wel kunen verwachten.
Ik ga het morgen even testen en sla je lijstje hier even op.

Ik laat het snel weten of het gelukt is, dan kan ik de software voor dit meetinstrumentje afsluiten en jullie wat plaatjes van het display laten zien.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.
blackdog

Golden Member

Hi,

Ik ben al een tijdje aan het werken met de logische OR die hardbass voorstelde.
Dit is verder niet moeilijk te begrijpen, ik loop echter op iets anders vast.

Boven in de loop lees ik de ADC waarden uit en doe wat berekeningen, ook lees ik de knopjes uit maar dat is voor nu even niet van belang.
De volgende stap is het berekenen van drie variabelen die ik ga gebruiken voor de OR vergelijking van hardbass.

Eigenlijk is mijn bedoeling de loop hier op te splitsen dus een
vork met twee tanden die naar beneden wijst.
Als de OR vergelijking waar is, wat te hoge temperaturen betreft, dan wil ik niet meer in de hoofdloop terecht willen komen.
Het aansturen van het display en de uiteindelijke "display.display();" wil ik in het error deel afhandelen.

Natuurlijk weet ik uit mijn basic en batch tijd dat ik dit deed met een goto, maar zover ik weet is dit een doodzonde in C. :+
Kan iemand mij uitleg geven hoe je dit netjes kan aanpakken in de Arduino code?

Onder in de loop staat een stukje code van robojax die ik dan ook gecopieerd heb naar de error code voor de fout toestand.
Maar kan je dus b.v. als je klaar bent met de afwerking van de foutcode, naar het begin van je void loop springen, of doe je dit anders.

c code:


void robojaxText(String text, int x, int y,int size, boolean d) 
    {
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(x,y);
    display.println(text);
  if(d){
        display.display();
       }
    
    }

Alvast mijn dank!
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.