According to Pete: Extreme Underclocking with the ProMini

How slowly can you clock an ATmega328 and still have any functionality?

Favorited Favorite 1

Welcome to the latest ATP! Today, we look at something of particularly questionable value: underclocking an ATmega328 to the point of just-what-are-you-trying-to-accomplish-here-anyway? What happens at extremely low clock frequencies? We pull off the resonator, splice in an external clock and watch the shenanigans!

May time be distorted for you in the best possible way this new year! Happy 2018.

Comments 20 comments

  • Member #27199 / about 7 years ago / 4

    It seems that the watchdog time may be on and causing a timeout.

    • If that were the case, I'd expect it to timeout before the end of every loop, not after many. Said another way, I'd expect the watchdog to get reset every loop. But I think more importantly, the watchdog runs on the system clock, not independently. So it shouldn't clock any faster than that.

      • GregFR / about 7 years ago / 3

        According to the datasheet, the watchdog timer does operate on its own 128khz oscillator, not the system clock, so if it was tripping up the system it would be after the same amount of time each time, regardless of the system clock. So if the watchdog counter isn't being reset, it could be tripping up the system.

        • Ooooh... good catch, my bad. But even so, I'd still expect it to get reset after every loop if it was active at all.

          • ...also per the datasheet, when the WDT times out, it either causes a system reset or initiates an interrupt. If it's a system reset, the code will restart, which it doesn't. If it's an interrupt, there has to be an ISR in play to do something about it. If there's one under the Arduino hood (as it were), it should still be doing something. Stop running code?? That seems an undesirable outcome, but maybe it's non-functional at this frequency. But I still don't think the WDT is active. Arduino is largely for people without tons of code expertise, and I would put use of the WDT as a somewhat advanced feature.

            • LightManCA / about 7 years ago / 1

              So this watchdog timer idea is interesting... Would it take soo long to reboot that you would think the processor was dead? Or maybe it's actually not possible for the boot loader to run at that frequency, since it's going to check for input on the serial line?

              I know there's a pin that can cause a reboot. Is there a pin you can watch to see if the processor reboots? Or just turn off the wdt, and try again.

              I'm curious as to why you decided to do your experiments with a pro mini. Seems like it would have been much easier to grab a redboard kit, or even just put the atmega chip on a breadboard, especially if it was already programmed and didn't need the programming circuit.

            • GregFR / about 7 years ago / 1

              Also from the datasheet: Note: If the Watchdog is accidentally enabled, for example by a runaway pointer or brown-out condition, the device will be reset and the Watchdog Timer will stay enabled. If the code is not set up to handle the Watchdog, this might lead to an eternal loop of time-out resets. To avoid this situation, the application software should always clear the Watchdog System Reset Flag (WDRF) and the WDE control bit in the initialisation routine, even if the Watchdog is not in use

              Do you know what the status is of the WDRF and WDE by default in arduino?

              • An eternal loop of time-out resets? Pete Dokter, this is your life!

                Seriously though, I don't know, that's a good question and I'm going to track it down (unless somebody beats me to it). But...

                1) I'd be seriously disappointed in Arduino for not dealing with that from the outset for noobs. If you're running blink, the last thing you should have to worry about is the WDT.

                2) You can accidentally enable the WDT?? DO I NEED ANY MORE REASONS TO LOSE FAITH IN HUMANITY??

                • wpmcnamara / about 7 years ago * / 3

                  The AVR 328P is rated to run down to 0Hz -- it is a static core. The same 128kHz oscillator that drives the WDT can be used as the system clock, as can an external 32.768 kHz watch crystal, so you have not done anything from a slow clock perspective that isn't fundamentally possible.

                  I had the same though about the WDT, but it's max timeout is 8 seconds, not minutes, and certainly not 10's of minutes. Interesting puzzle you've got there...

                • wpmcnamara / about 7 years ago / 1

                  It would be interesting to program the Clock Select to use the 128kHz internal oscillator and see if the problem remains. The one problem being that you have to suffer through the Arduino bootloader and initialization at 128kHz. Alternatively, you could switch the Clock Select to "external clock" and drive XTAL1 with a full swing square wave. You do run into that "don't vary the clock by more than 2% per cycle" note that you reference in the video, but it would seem that it shouldn't cause problems since, as you also not, we aren't worried about any of the peripherals in this little experiment.

  • Did Bob ever get back to you on the low Freq issues?

    • That's a negative on Bob. Xmas and all, so not surprised. I'll mention to him that the video posted, and that might bring him around.

  • Wilko / about 6 years ago * / 1

    I couldn't help myself, I just had to try it. My ATMEGA328p works like a charm on 0.2 Hz. That's as low as my equipment will allow me, one clock pulse every five seconds. No jitter, no hanging, just flawless operation. Current consumption at 3.3V fluctuates between 80uA and 170uA. This is how I programmed it.

    #define F_CPU 8000000UL
    #include <avr/io.h>
    int main(void)
        DIDR0  |= (1 << ADC5D) | (1 << ADC4D) | (1 << ADC3D) | (1 << ADC2D) | (1 << ADC1D) |(1 << ADC0D);
        ACSR |= (1 << ACD);
        ADCSRA &= ~(1 << ADEN);
        PRR |= (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM1) | (1 << PRTIM0) | (1 << PRSPI) | (1 <<  PRUSART0) | (1 << PRADC);
        DDRB |= (1 << PB5);
        while (1)
            PORTB |= (1 << PB5);
            PORTB &= ~(1 << PB5);

  • Wilko / about 6 years ago / 1

    Simple question, did you disable interrupts?

  • noworries / about 7 years ago / 1


    It is possible that you are reading the current state of the output pin, inverting that state, then outputting that state to the pin. This can and does work at high clock frequencies, but can, and will fail under lower frequencies in some circumstances. I have not looked at the code library to see if they shadow the value that is last written to the port in a register, so this may not be what you are running into. If they don't shadow the port. this may be what you are experiencing:

    The output node has an impedance and some amount of capacitance. When the pin is being driven as an output, this capacitance is charged to the voltage the pin is driven to. If the pin is then switched to be an input, (to read its current state) the output register is no longer driving the pin. The voltage is sampled that is currently on the pin which depends on the amount of capacitance in the circuit connected to the pin, the resistance of the load connected to the pin, and any stray leakage paths on the PCB. If, due to the slow clock speed, you give the leakage paths enough time to change the charge on the capacitance at the pin to a different logic level, the value read will be different than that written. It is most likely to be of a nature that tends to either drift to one of the power rails. Lets say it wants to drift to a high for argument sake. If the port wrote a low to the pin, but the pin drifted to a high before it was read due to the slow clock, the code would then invert that high state to a low and write that low state to the pin. However, that was the same state that was previously written to the pin, so the pin is no longer toggling, even though it is still running. The slower the clock is ran, the more sensitive to this problem the system will be in this circumstance. (You are giving the leakage paths present at the pin more time to modify the charge on the capacitance at the pin).

    This can be avoided by toggling a register in the processor and outputting the current state of the register to the pin. The pin then always stays in output mode, but just follows the state of the register which is toggled in code. The ATMEGA 328P is specified to be entirely static in nature, so you should be able to clock it at any slow clock speed you desire while maintaining the clock stability spec.

    One practical reason to do this could be to minimize power consumption for a micro controller while allowing it to wake up on external events where it would briefly switch to high speed for moments when an important event occurs. You are correct that such a design needs to be fully analyzed to minimize other losses so that such techniques actually achieve the desired results. Many processors for this use case exist with the ability to turn off unused peripherals to further reduce the quiescent power required.

    It would be interesting to see if this is what is causing your system to stop toggling the LED.

  • Member #444741 / about 7 years ago * / 1

    It occurs to me that if there were any floating pins going into, say, interrupts, that 1msec is a plausible timeconstant for charging of a pin's capacitance by PCB leakage current: 10pf / 10nA = 1msec/v. Maybe it would be informative to define all unused pins as outputs, in the setup section of the program, and run it again...

  • When you got down to 3 and 4 clocks per loop, were you using "|=" or "&=" operators?

    I would assume raw registers (ports) are declared as volatile for the compiler. The volatile keyword would force a read-modify-write for "|=" and "&=" operations. A read-modify-write would take 3 clock cycles each plus 1 for a jump which is consistent with your observation. Looking at the disassembly would also confirm if a read-modify-write is occuring.

    Changing to a direct assignment "=" could eliminate the read and modify leaving only a "write-write-jump" loop, 3 clocks. If so, that would be as good as assembly could get while still living at the high level C :). Super blinky.

  • ShapeShifter / about 7 years ago / 1

    I haven't researched this (I haven't read the datasheet recently) but my gut feeling says that I would start reasearching it by looking at the clock generator section. You haven't changed the crystal/resonator oscillator settings, so it still thinks that it's driving a crystal/resonator. In my experience, these crystal oscillator circuits often have a phase locked loop (PLL) to track the frequency. These take some large number of clock cycles to lock onto the frequency. Since you're running it so far out of spec, it's virtually assured that it won't lock. After a while (2^14 clocks seems reasonable) it may give up and reset. That reset will take a certain number of clocks, during which time your LED is not changing state.

    At the very low speeds, you say it just stops. Could it be that the number of clock cycles it takes to re-initilize is longer than you are waiting to see if it restarts?

    I think it would be interesting to retry this with the clock settings updated for an external clock, making sure that any PLL is disabled.

  • Madbodger / about 7 years ago / 1

    There are a few things (like the serial port) that are handled when the code exits loop(), so if you never do (your internal forever loop), those things won't get taken care of. I suspect that what you and wpmcnamara alluded to, setting the clock bits to "external" might improve things. Now I'm tempted to try this myself. I don't have that fancy signal generator, but I have a tube of 555s (and a "three fives" kit). I remember clocking an old KA-50 at about that speed, and trying to do so with a 6800, but it has more complex clock requirements.

    • Madbodger / about 7 years ago / 1

      I just looked at the source code (yay open source). Yup, it handles the serial port. Here's the guts of it:

      for (;;) {
          if (serialEventRun) serialEventRun();

Related Posts

Recent Posts


All Tags