Arduino tuned clock management

Issue #16 resolved
Former user created an issue

May I suggest a little upgrade on NewPing library ? I have successfully updated it on my side to handle clock changes at run time for Atmega328p by setting up new millis(), micros(), delay(), delayMicroseconds() functions. My code is not very clean - it's only appropriate for my usage - but it's a first step.

Comments (9)

  1. Tim Eckel repo owner

    Huh? I don't follow the reason for this. I've built many circuits running at lower speeds and I never needed to do anything like this. Maybe you're just changing the speed of the microcontroller with a crystal and not setting the CPU speed? If you set the CPU speed correctly in software, the timers all still work correctly.

  2. Qqlapraline

    I may have been unclear. Let me elaborate a little. First of all, I'm using an Arduino platform. And the microcontroler is an Atmega328p. I use a standard library (avr/power.h) to adapt at runtime the clock of the microcontoler (in order to have better power consumption). The function that allows that is clock_prescale_set. Once this is done, time base is false (it is very well documented). From this point, every call to delay(), millis(), micros(), ... are wrong. This is the reason why I'm proposing this evolution.

    Regards,

    Qq.

  3. Tim Eckel repo owner

    I build lower cpu speed circuits all the time and there's no issue with the timers being incorrect. I've run 1mhz even with no timing issues. You must be doing something wrong as I have no issue doing this. Just be sure clock_prescale_set matches the clock speed.

  4. Qqlapraline

    OK. My code does not run at the same clock during all the time. Just to be sure there is no misunderstanding, here is a piece of my code:

    void loop() {
    
      measureCount ++;
      sendBattery ++;
      bool forceTransmit = false;
      transmission_occured = false;
    #ifndef MY_OTA_FIRMWARE_FEATURE
      Serial.print(F("Measure count:"));Serial.println(measureCount);
    
      if ((measureCount == 3) && highfreq) 
      {
        Serial.println(F("Switching clock"));
        clock_prescale_set(clock_div_8); // Switch to 1Mhz for the reminder of the sketch, save power.
        highfreq = false;
      } 
    #endif
    
      if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
        forceTransmit = true; 
        measureCount = 0;
      }
    
      sendTempHumidityDistMeasurements(forceTransmit);
    /*  if (sendBattery > 60) 
      {
         sendBattLevel(forceTransmit); // Not needed to send battery info that often
         sendBattery = 0;
      }*/
    #ifdef MY_OTA_FIRMWARE_FEATURE
      if (transmission_occured) {
          gw.wait(OTA_WAIT_PERIOD);
      }
    #endif
    
      gw.sleep(MEASURE_INTERVAL);  
    }
    

    You can see that I am changing the clock speed from the standard clock of the Atmega328p (8 Mhz) to a lower time (1 Mhz) after 3 loops. This is done to allow the setup to run at a lower battery level.

    Again, this is very well documented that millis(), micros(), delay(), .. does not behave appropriately doing this. And again, I have checked the code...(mine and NewPing one)...a lot.

    I was using the standard library and every ping() was failing after clock changes. Once Ive updated the code to handle different times/clock, it works like a charm with the same accuracy.

    Regards,

    QQ.

  5. Tim Eckel repo owner

    But that's not how you do it. You do it via compile time options in the Arduino IDE. Use the Arduino Board Manager to download ATmega328 bare compile options. Then, when you compile you set the frequency (it sets the fuses on the microcontroller). All timers will work correctly.

    Also, to really build low-powered circuits, you should be using sleep, not the frequency. Typically, a circuit uses far less power by running at a faster speed when awake, then putting it into a low-powered sleep mode till it wakes again. Also, you can use interrupt triggers to wake from sleep state. Here's some thing to do searches on to get you started:

    sleep.h power.h sleep_enable() sleep_cpu() sleep_bod_disable()

    Using the Arduino's sleep state uses virtually no power. I've developed circuits that have run for 4 years on a set of AA batteries and another that's run for over a year on a coin cell. Both of these circuits run at 8Mhz (when awake). If you think about it, if the CPU is running at a faster rate, it spends less time awake. Sleep mode can be tweaked via careful programming to use a few μA while sleeping.

    Anyway, you're not setting the CPU speed correctly which is why you're having a problem with the timer. Also, it appears you're doing low-powered circuits the wrong way as well. Check out the bare ATmega328 in Board Manager and look up the above sleep mode commands and libraries for proper ultra low powered circuits.

    In any case, there's zero reason to add any type of alternative timing code in NewPing as long as you're using the fuses to set your CPU's speed. Also, the proper way of building a low-powered circuit is to use the built-in sleep and interrupt wake-up. Using that technique is FAR lower power than what you're trying to do with the above code.

  6. Qqlapraline

    That is what I'm doing. But, please, don't consider I'm doing things badly. There are reasons why I'm adjusting clock at run time. Furthermore, of course, I also do use sleep() primitives to lower power consumption (this is inside the gw.sleep() that you can see). Be aware that I, as well, have many setups that lasts for more than 4 years on a set of 2 AA batteries. I am aware of all the power options you are referring to but I'm free to choose the way I handle it. Just have a quick review of the Atmel documentation, you will see that the Vcc requirements at 1 Mhz are much lower than at 8 Mhz. It means, that even though it does not save any µA, it mathematically increases battery life.

    But I do understand that you don't want to update your code because you consider that I'm doing bad things. Fair enough, my code is working well. That will be probably a good thing to create a more open-minded ping library.

    Oh, remember to tell the guys from Atmel to remove the clock_prescale_set() function as you consider this is a very bad thing to do. I don't thank you for the patronizing advice.

    QQ.

  7. Tim Eckel repo owner

    If I want very low voltage, I run at 4Mhz which runs code quicker for the very short periods it's awake and then goes into long sleep mode till an interrupt wakes it up. I've tested the different in power draw and it uses less power when using a higher frequency as it's awake for a shorter period of time. Also, everything still works this way (time is correct, you can communicate via serial, etc.). Setting clock_prescale_set() is a hack at best. Just set it to the actual CPU speed before calling NewPing and everything will work correctly. But, I'd highly consider doing everything with fuses instead (the correct [better] way).

    I don't want to include code which would make compiled code larger and slower. Speed and reduced code size are two of my highest priorities. I also don't find the additions useful for anyone by yourself, so you should probably just do it custom anyway.

  8. Log in to comment