[AVR] Low-frequency output from a timer below what prescalers give - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 28, 2019, 11:08:49 pm
content: I am working on a project where I have a need for a variable-frequency square wave output from my AVR microcontroller. However, because I'm an idiot and didn't properly read the datasheet, I didn't realise that the particular timer that my output pin is connected to (prototype PCBs already made, bit late to change now) doesn't support a high enough prescaler/divider to allow me to output frequencies below 16 Hz. I assumed it was like the majority of AVR timers that have a maximum prescaler of 1024, but in fact it only supports up to 256.So, does anyone have any ideas for something I can do in code to get my output frequency range below 16 Hz?I was thinking perhaps I can 'manually' toggle the output pin for frequencies below, say, 100 Hz. So, if a frequency of less than 100 Hz is desired, instead configure the timer for a fixed 100 Hz, disconnect the output pin from the timer, and enable an ISR for timer overflow that implements its own counter and pin toggling. Something like the following, perhaps:Code:ISR(TIMERn_OVF_vect) {// 100 = frequency that interrupt is called// freq = output frequency desiredstatic uint8_t counter = 0;const uint8_t top = (100 / freq) - 1;const uint8_t ocr = top / 2;counter++;if(ocr == counter) {// Clear output channel pin here.}if(top == counter) {// Set output channel pin here.counter = 0;}}I think it will probably work, but wondering if there is a better way.

author: KL27x
author_rank: Super Contributor
timestamp: August 28, 2019, 11:23:04 pm
content: Use a postscaler?Instead of having the timer trigger a pin toggle, directly, can't you just make the timer increment a variable of your own choosing? Then run a check.When the variable reaches X, it gets reset. And triggers the pin toggle. And it does all these other things: (Less than X, and it only does these other things):Clears the timer interrupt flag, reset the timer counter, and continue with rest of ISR.So the timer triggers X times before it triggers your pin toggle. 2 bits of post scaling, triggering your output every 4th timer interrupt, and you have 1024 scaling. 256x2^n.You can implement as many postscalers as you want until you run out of memory.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: August 29, 2019, 12:08:08 am
content: Quote from: HwAoRrDk on August 28, 2019, 11:08:49 pmThat can work, if you can tolerate the SW jitter, which may be ok under 16Hz, You could adjust the ISR reload to manage fine-frequency and run a counter inside the int, for coarse.ormost Timers outputs toggle on match, (I think AVR does too?) so if you need to go below 16Hz, you could enable an ISR where you disable the match setting, for so many INT counts.That keeps the edges hardware based, so avoid SW jitter.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 29, 2019, 01:25:46 am
content: It's difficult pursuing some strategies like mentioned, because the type of timer I'm dealing with (one of Atmel's PSC peripherals) is fairly limited in terms of flexibility. The only interrupt I have is the overflow interrupt (so no compare match or input capture interrupt), there's no ability to force an output compare, and there isn't even a register available to read/write the counter value.I realised the idea of using 100 Hz as a base frequency is stupid, as it doesn't give me anywhere near enough resolution. After twiddling some numbers I think if I use a 1 kHz base frequency and only try to 'manually' handle outputs below 20 Hz, it should be okay. Also, the code example I posted earlier was not quite right. I think more maybe something like this:Code:ISR() {// This interrupt called at rate of 1000 Hz// These vars will be defined outside of the ISRconst uint16_t freq = 7;const uint16_t top = (1000 / freq) - 1;const uint16_t ocr = top / 2;static uint16_t counter = 0;if(counter == 0) {// Set output channel pin here.}if(counter == ocr) {// Reset output channel pin here.}if(++counter >= top) counter = 0;}

author: PCB.Wiz
author_rank: Super Contributor
timestamp: August 29, 2019, 02:04:58 am
content: Quote from: HwAoRrDk on August 29, 2019, 01:25:46 amYou could also vary that 1kHz (or less) so it is some integer multiple of the freq you want.

author: mikerj
author_rank: Super Contributor
timestamp: August 29, 2019, 08:26:37 am
content: What's the actual frequency range you require?

author: Kleinstein
author_rank: Super Contributor
timestamp: August 29, 2019, 07:01:37 pm
content: Modern AVRs allow another prescaler for the whole µC clock. So one could divide the clock up front by 2, 4, 8, 16 or even more.The postscaling with switching between just counting and the Wave form generator mode would also be an option.Another option could be a software NCO, so kind of DDS with just a single bit DAC. However this would add phase noise as the granularity would much coarser.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: August 30, 2019, 02:44:01 am
content: Quote from: mikerj on August 29, 2019, 08:26:37 am1 to a few 10's of kHz. I think perhaps 50 kHz, haven't decided yet. I don't really have much of a limitation on the upper end of the range.Quote from: Kleinstein on August 29, 2019, 07:01:37 pmAh, yes, I guess that's possible. But I would be really hesitant to reduce the clock speed of the whole chip, as it's doing a bunch of other stuff that needs to run as fast as possible.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: August 30, 2019, 03:31:07 am
content: Quote from: HwAoRrDk on August 30, 2019, 02:44:01 amWhat step size do you need ?another approach, would be to have a simple fixed optional divide in an interrupt, and when that is enabled, it divides the frequency by /16 (or /100 if you need decimal, or finer freq choices )With /16 you select 16 ~ 256 Hz, and get out 1Hz~16Hz in 1/16 Hz steps (0.0625 Hz)With /100, you select 100~1600Hz, and get out 1Hz~16Hz in .01 Hz steps.

author: mikerj
author_rank: Super Contributor
timestamp: August 30, 2019, 01:46:44 pm
content: Quote from: HwAoRrDk on August 30, 2019, 02:44:01 amI think you will have to switch the pin between OCR output for high frequencies and a GPIO serviced by an interrupt for lower frequencies. If you can tolerate a little jitter at low frequencies you could implement a DDS style counter to give a lot of resolution i.e. in your ISR you simply add a value (calculated to provide the required frequency) to an accumulator, and when the accumulator overflows you switch the state of the output pin. This requires only a single addition (probably 16 bit) in the ISR so execution time will be fast. Doing this in assembler may be best as you have easy access to the carry flag to detect overflow, and it's a pretty trivial bit of code.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 02, 2019, 12:15:52 am
content: Quote from: mikerj on August 30, 2019, 01:46:44 pmThe first stab of my implementation does just this.What I ended up doing is that when a frequency change is required, I have some code that iterates over the available prescaler/divider values the timer has (1, 4, 32, 256) in ascending order and selects the one that is the best 'fit' for the requested frequency. This is done by simply calculating a prospective compare value (ocr = F_CPU / div / freq) - 1) and sees if it is smaller than the maximum compare value the register allows (12 bit, 4095). If the loop exits without finding a prescaler 'fit', I know that the requested frequency output is too low to be operated by the timer, and so I configure things for software-driven toggling of GPIOs - 1 kHz base frequency, counter and pin toggling implemented within interrupt routine.Now I have come up against a small problem, but not regarding the software-driven output, which seems to be working just fine. I wanted to know how things look when sweeping through a frequency range, so rather than looking at the instantaneous output on the 'scope, I made longer captures using my logic analyser. However, I notice that there were small glitches in the output at certain intervals:What I figured out was that these glitches occur at the point the timer's prescaler/divider is changed over. Is there a way I can mitigate these glitches and get a clean transition?I tried using the PULOCK feature, which allows one to synchronise changes to compare/top values (i.e. register values become buffered and changes applied simultaneously upon unlocking), but this does not extend to the PCTL register where prescaler configuration is made, so doesn't help much.

author: JustMeHere
author_rank: Frequent Contributor
timestamp: September 02, 2019, 12:35:02 am
content: Can you change your main clock frequency?

author: PCB.Wiz
author_rank: Super Contributor
timestamp: September 02, 2019, 09:06:55 am
content: Quote from: HwAoRrDk on September 02, 2019, 12:15:52 amYou can expect prescaler changes to not be 'linear', as they likely are just taps on an always running counter.However, your plot seems to show multiple edges (3?) which does not make sense for counters + prescalers.Could that be some glitch-display mode in the logic analyzer ?If you toggle a pin when you change Fo, you should be able to trigger a normal scope, and see how the hand-over works.Expected would be a possibly shorter half-period, depending on the prescaler and post dividers.

author: Psi
author_rank: Super Contributor
timestamp: September 02, 2019, 09:19:44 am
content: Could it be that the prescaler is being changed but the timer counter reg is not immediately being updated to the other end of the scale (to sync it up).So you get the wrong period until this corrects on the next cycle.Some timers buffer registers until the next update.Just a guess.

author: mikerj
author_rank: Super Contributor
timestamp: September 02, 2019, 12:38:41 pm
content: If you write a value to the OCRx register which is close to but above the the current TCNT value, the output will toggle prematurely. If you write a value below the current TCNT value, you will get an extended pulse. A similar problem applies to the pre-scaler, except you have no way of reading back what the current prescaler counter value is.A possible solution would be to update the prescaler and OCRx registers immediately after the output has toggled (i.e. whilst the prescaler and TCNT values are still very small). One way to do this would be to calculate the new prescaler and OCR values, store them in variables and then enable the OCF interrupt. In the interrupt handler you would then program the timer registers with the new values you saved and disable the OCF interrupt.

author: Brutte
author_rank: Frequent Contributor
timestamp: September 02, 2019, 02:08:20 pm
content: Theavr8 timer extensionfrom years back.This problem requires resolution of events run-time.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 03, 2019, 02:17:49 am
content: After a bit of reading up on the subject, I thought I would do some experimentation with an NCO, to see how it fares.Twiddling some numbers in a spreadsheet determined that an output frequency range of 0 to 31,250 Hz should be achievable with a 16-bit accumulator and a base timer frequency of 62.5 kHz. The number say that output frequency error (of desired versus actual) should be less than 0.2% across the range, with the exception of down in the single-digit hertz, where it is a poor, but probably tolerable, 4.6%. But numbers can lie, so I wrote some code:Code:#define TIMER_FREQ_HZ 62500volatile uint16_t nco_increment = 0;void main(void) {// Set up timer 1 to trigger interrupt at TIMER_FREQ_HZ rate, etc.// nco_set_increment() called from main loop with desired output frequency according to user input.}void nco_set_increment(const uint16_t freq) {float ratio = (float)(freq * 2) / TIMER_FREQ_HZ;uint16_t incr = roundf(ratio * UINT16_MAX);ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {nco_increment = incr;}}void nco_update() {static uint16_t nco_accumulator = 0;if(nco_increment == 0) {// Turn off output pin when frequency zero.PORTB &= ~(_BV(PORTB5));} else {if(__builtin_uadd_overflow(nco_accumulator, nco_increment, &nco_accumulator)) {// Toggle output pin.PINB |= _BV(PINB5);}}}ISR(TIMER1_COMPA_vect) {nco_update();}Glad I found GCC has the __builtin_uadd_overflow(), as that's really handy to be able to do the addition and determine if it overflowed in one function call.Not sure about how efficient it is - the assembly it outputs is a couple of dozen lines.The result is that it works, but the jitter is pretty terrible at frequencies above 10 kHz.For example at 12 kHz, without any changes in user input, it's jittering by several hundred Hz.This was on a spare Arduino Uno board I had to hand, and I remembered that by default the Arduino library runs a timer and interrupts for some background stuff (e.g. millis() timing, etc.), so I thought I would see what effect disabling every interrupt except my one would have. While the jitter was ever so slightly improved, it was nothing significant.If it's pretty bad in a basic bare-bones experimental setup, I can imagine it would be even worse when there are numerous other regular and intermittent interrupts firing off, as my project's final implementation would have. So unless my experimental setup can be improved, I'm not sure about the merits of using an software-driven NCO.

author: jhpadjustable
author_rank: Frequent Contributor
timestamp: September 03, 2019, 03:30:49 am
content: In the interrupt, could you just set up the compare match action according to what will be needed at the next overflow? Jitter should approach zero and it should be easier to switch between software-assisted and direct modes on the fly.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 03, 2019, 09:26:42 am
content: Quote from: jhpadjustable on September 03, 2019, 03:30:49 amYou were probably referring to my original situation, and not my current experiment with an NCO, but this gave me an idea...I changed my NCO code so that instead of directly toggling the GPIO pin, it instead changes the timer config to alternately clear/set the output pin upon compare match. So, code is mostly as before, with the exception of the nco_update() function (as below), and also initial set-up of the timer to default to clearing the output pin upon compare match.Code:void nco_update() {static uint16_t nco_accumulator = 0;if(nco_increment == 0) {// Set timer to always clear the output pin.TCCR1A &= ~(_BV(COM1A0));} else {if(__builtin_uadd_overflow(nco_accumulator, nco_increment, &nco_accumulator)) {// Set timer configuration to clear or set (depending on previous// state) the output pin for us next time round.TCCR1A ^= _BV(COM1A0);}}}This has improved the jitter a bit, but it's still not great. The improvement is mostly at the lower frequency ranges - below 1 kHz is virtually rock-solid - but still significant jitter above that.Not sure whether to pursue this NCO avenue any further. Maybe I'm doing something wrong, or perhaps there are further improvements to be made.

author: mikerj
author_rank: Super Contributor
timestamp: September 04, 2019, 04:37:46 pm
content: Quote from: HwAoRrDk on September 03, 2019, 02:17:49 amTo be expected. The NCO approach is useful to give you good resolution at low frequencies, for high frequencies use the OCR output directly as mentioned before.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 05, 2019, 05:35:21 am
content: I implemented the NCO approach on my main project's hardware (uses an ATmega32M1 - experiments so far have been with 328P on an Arduino board), as I figured a way to get things to work with the PSC peripheral's timer. I was thinking it might not be possible because the PSC cannot be configured to explicitly set/clear the output pin upon a compare match, only toggle. But, simply switching the compare value between zero and maximum (0xFFF) results in the output pin being off/on, respectively, throughout the entire timer cycle, replicating the same behaviour. A trivial XOR-ing of the compare register with 0xFFF does the toggling.I'm still disappointed though, as the impact of the timer interrupt is far worse than I expected. As a simple test, I set up my main loop to just switch back-and-forth between two frequencies with a one second delay in-between (using_delay_ms()). The NCO interrupt was takingso muchprocessing time, the one second delay was actually being stretched out to just over 1.5 seconds!Without serious optimisation, this would be in no way workable for my project, as the firmware will need to be doing lots of other stuff besides a variable-frequency square wave output.I am contemplating rolling my sleeves up and trying to optimise the NCO interrupt, possibly by rewriting the whole thing in assembly. Looking at the current assembly code the C compiler generates shows it's quite long - about 60-something instructions - so perhaps could be shortened. I was readinga blog postthat claims an NCO with 16-bit accumulator can be implemented in 20-something instructions. Unfortunately though, I'm a noob with AVR assembler and I'm not sure where to start, or whether it's even worth spending the time to learn about AVR assembler to do it.

author: mikerj
author_rank: Super Contributor
timestamp: September 05, 2019, 10:35:43 am
content: Any pure software delay function will be affected by interrupts, that is unavoidable unless you disable interrupts during the delay. A simple NCO function should be very fast if written correctly, likely even faster than 20 cycles since you don't need to use a lookup table within the interrupt as that example shows. However the overhead of even a fast ISR can be high if the interrupt occurs very frequently.Perhaps I am missing something but I don't understand why you are trying to use the NCO to generate e.g. a 1kHz output when this is already possible using the timer hardware with zero jitter. The whole point of the NCO suggestion was to generate only the very low frequencies not achievable by the timer hardware only.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 05, 2019, 11:46:33 am
content: The reason why I was hoping an NCO would work was that it would actually allow me to have multiple channels of output at different frequencies for each, which while not strictly required at present, would be nice to have. I can't actually have that using the PSC peripheral, as even though it has three output compare channels, it has a single timer counter running them all.