Drive counter with external clock source

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Drive counter with external clock source

Post by blmorris » Fri Oct 17, 2014 1:14 pm

Does anyone know if it is possible to use the pyboard timer peripherals to count pulses (and thus measure the frequency) from an external oscillator?
It would be nice to be able to use the hardware timer peripherals to do this directly rather than relying on callbacks; I have already tried the callback approach, and even with external logic implementing a clock divider (a very old 74'163 binary counter chip) to get the frequency down to 50 kHz, I am finding that processing 50,000 callbacks per second is still too much for the pyboard.
My goal is to synchronize an OCVCXO (Oven-compensated, voltage controlled crystal oscillator) with the 1 Hz pulses from a GPS receiver. The OCVCXO is a high-precision oscillator that can be tuned with an external voltage source, and PPS output from the GPS comes very close to atomic clock precision, so it should be possible to tune the oscillator to generate exactly 12,800,000 oscillations per GPS pulse, at least as a long-term average. The only trick is to accurately count pulses and generate the analog compensation voltage.
If it is already possible to do this using the timer class, that would be great, but I haven't figured out how just yet. I would also be open to suggestions of a custom written C module or using inline assembler. Or, if anyone has reason to believe that the interrupt system on the STM32F405 has too much latency to do what I am attempting, then I would appreciate hearing that as well! ;)
-Bryan

User avatar
dhylands
Posts: 3351
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Drive counter with external clock source

Post by dhylands » Fri Oct 17, 2014 4:09 pm

It looks like you can use the ETR signal of a timer to be a clock source.

Page 521-523 of RM0090 http://www.st.com/web/en/resource/techn ... 031020.pdf gives an overview of the 2 external triggering modes.
Page 552-554 describes the TIMx_SMCR register which appears to have all of the stuff for setting up ETR.

At the HAL level, the function HAL_TIM_ConfigClockSource https://github.com/micropython/micropyt ... im.c#L3816 seems to be the function which does the low-level work. HAL_TIM_ConfigClockSource takes one of these structures: https://github.com/micropython/micropyt ... #L213-L223 and the clock sources are described here: https://github.com/micropython/micropyt ... #L213-L223

On the pyboard, it looks like 2 of the pins can be configured as ETR.

X1 can be configured as ETR for TIM2 or TIM8
X6 can be configured as ETR for TIM2

Timer 5/11 have a mode where you can connect it up to one of the internal clocks (see page 156-158). TIM5_CH1 is avallable on X1. TIM11_CH1 is available on Y4

There isn't currently anything in python to setup a timer for an eternal trigger.

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Drive counter with external clock source

Post by blmorris » Fri Oct 17, 2014 4:39 pm

dhylands wrote:There isn't currently anything in python to setup a timer for an eternal trigger.
Thanks, and since I know you did a lot of the work to get the Timer class up to its current functionality I will take that as authoritative.
For the moment I am going to add another 4-bit divider stage to bring my input down to 3125 Hz, I should be able to handle callbacks at that rate for a simple test, and delve into the HAL functions after that.
As far as I can tell, with my current code and hardware setup generating 50,000 callbacks per second, I was consistently dropping 3230 of them, occasionally 3231, while otherwise idling. Any other activity and I would drop many more counts. My guess is that I missed counts whenever I got a second callback while one was pending to process a higher priority event.

-Bryan

User avatar
dhylands
Posts: 3351
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Drive counter with external clock source

Post by dhylands » Fri Oct 17, 2014 5:15 pm

There is a systick interrupt that fires once per millisecond.

And USB would have some interrupts (the rate depends on the host).

I guess at some point, I should profile the interrupt handler and figure out what kinds of rates we should expect.

I know I can do 100,000 interrupts per second on a 16-MHz AVR, in C (for 1 megabit uart), so I was hoping we'd be able to similar kinds of rates on the pyboard.

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Drive counter with external clock source

Post by blmorris » Fri Oct 17, 2014 9:10 pm

I added another 4-bit counter stage (total of three) to divide the oscillator frequency down from 12.8 MHz to 3125 Hz, or a factor of 4096. 3125 is 5^5, so to divide any lower and maintain nominal integer frequencies I will need to buy some 4-bit decade counters to give me my factors of 5.
At this rate of 3125 callbacks per second the pyboard and REPL remain responsive, and I don't lose any ticks unless I read or write from USB mass storage. I did get an error at one point: "Memory allocation failed, heap is locked" (thanks to your emergency exception buffer hack!) but the board didn't freeze up and I was able to restart my monitoring routine.

The more interesting part for me is that I am able to use the DAC output to tune my oscillator to be less than 20 parts per billion faster than the GPS PPS ticks, or roughly one microsecond drift per minute. (I use the pyb.micros() function to measure the time difference between the oscillator counter rolling over and the GPS tick.) Using the GPS as my reference, the measured frequency of the oscillator is 12,800,000.23 Hz, less than 1/4 cycle off per second. Surely when I close the feedback loop I'll get basically a perfect lock.

That is my success for the day :D Time to start digging in to the HAL functions so I can do all of this without so many resource-intensive callbacks and all the external discrete logic.
dhylands wrote:I know I can do 100,000 interrupts per second on a 16-MHz AVR, in C (for 1 megabit uart), so I was hoping we'd be able to similar kinds of rates on the pyboard.
My original plan for this project (last year, before I put it aside) was to use discrete logic and a PIC or AVR. Its not surprising that you can handle a higher rate of interrupts when running on bare metal without a complex layer of Python in between - but a lot more effort to do just about everything else…

-Bryan

User avatar
dhylands
Posts: 3351
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Drive counter with external clock source

Post by dhylands » Fri Oct 17, 2014 9:32 pm

Your memory error could be occuring if an int overflows from a small int. The + 1 will then try to allocate a multi-precision int.

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Drive counter with external clock source

Post by blmorris » Mon Oct 20, 2014 7:23 pm

Short update and thanks again to @dhylands for setting me on the right track :)

It is true that there is nothing within the current pyb.Timer class to set up a timer for an external trigger. However, we do have full access to all of the special function registers (SFR's) by way of the 'stm' and 'uctypes' modules to control pretty much every possible parameter of the processor operation. Probably not the first choice for a seasoned C programmer when the HAL is available - but I am a hardware engineer, not a C developer, and I find reading data sheets to be much easier than sifting through deeply nested directories of C library files. Just a personal preference ;)

As Dave said, TIM2 and TIM8 are the only timers that can accept external triggers on the pyboard, and of these I chose to use TIM2 because it is 32 bits. I was able to use the SFR's to set TIM2 to accept a trigger signal on pin X1. I am now able to drive a counter directly with the 12.8 MHz signal from my precision oscillator, trigger a callback from the GPS PPS signal to print the counter, and fine-tune the precision oscillator's frequency with a DAC output. With fine-tuning, usually the count changes by less than one cycle (out of 12.8 million) per GPS reference second.

Still to do is to directly set the DAC based on the difference between the GPS reference and the precision oscillator count, and maybe another register hack to allow for 12-bit precision DAC writes to reduce control hysteresis. Also, it would be good to monitor the data output of the GPS unit to only calibrate when the receiver has a solid fix, and provide some LED indication of status.

At this point my code is pretty hacky, but I will clean it up since I plan to use it in a larger project. I'm also starting to think about putting together a repository of SFR hacks, which could be a reference for users who need to enable functions that aren't, and might never be, supported within the official pyboard API.
-Bryan

User avatar
pythoncoder
Posts: 4151
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Drive counter with external clock source

Post by pythoncoder » Wed Oct 22, 2014 7:57 am

I'm also starting to think about putting together a repository of SFR hacks
That would be very useful and educational.

Regards, Pete
Peter Hinch

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Drive counter with external clock source

Post by blmorris » Wed Oct 22, 2014 3:35 pm

pythoncoder wrote:
I'm also starting to think about putting together a repository of SFR hacks
That would be very useful and educational.

Regards, Pete
For me as well - I figured out what I needed to use the 'uctypes' module from the examples out there, but I'm pretty sure there are some subtleties that I'm missing. My grasp of Python conventions and techniques is still pretty primitive - most of what I know about Python is from participating in this project. Looks like I have a chance to put up some examples that can help others and get informed feedback on my own coding at the same time, really the perfect situation :)
I still have some functions that I need to work out for my project, and I'll try to clean it up and post in a day or so.
-Bryan

Post Reply