Low frequency Signal generator for CTCSS

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
User avatar
Pip
Posts: 11
Joined: Mon Jul 12, 2021 6:37 pm
Contact:

Low frequency Signal generator for CTCSS

Post by Pip » Sun Aug 01, 2021 10:48 pm

Hi I am wanting to generate a set of frequencies such as 67.0Hz, 79.7Hz up to 192.8Hz on my Pi Pico for CTCSS (Continuous Tone Controlled Squelch System)

I thought that this would be easy using PWM, however I have found that the resolution of the PWM function in micropython is in 1Hz steps as the function takes an integer for the frequency value the 1 Hz steps are not precise enough for my application as I need have fractions of Hz resolution.

I have tried using a timer with a call back set to 2 X the frequency and toggling a bit to produce the output, but alas the timers seem to derived from interrupts within the Pico and as such I get a very glitchy output.

Anyone got any ideas of the best way to generate the precision clean low frequency tones on the pico,

Cheers

davef
Posts: 811
Joined: Thu Apr 30, 2020 1:03 am
Location: Christchurch, NZ

Re: Low frequency Signal generator for CTCSS

Post by davef » Sun Aug 01, 2021 11:04 pm

CTCSS ... that is a blast from the past, like 40 years ago in PMR development. I checked to see if the Pi Pico had a DAC. What about an external DAC?

davef
Posts: 811
Joined: Thu Apr 30, 2020 1:03 am
Location: Christchurch, NZ

Re: Low frequency Signal generator for CTCSS

Post by davef » Sun Aug 01, 2021 11:06 pm


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

Re: Low frequency Signal generator for CTCSS

Post by pythoncoder » Mon Aug 02, 2021 6:55 am

Two comments. Firstly, if using a hardware timer you are getting glitchy output, you may have found a bug in the firmware. If you have a scope I would suggest taking a measurement and reporting the issue.

As for a solution, I would use the PIO to implement a divide by N, or a precision PWM. You can run the state machine at a lower frequency (I have used 1MHz) and the SM could count down from a supplied 32 bit value. Getting your head round the PIO takes a bit of work but it's worth it. In addition to the MicroPython docs I would look at the excellent Pi docs.
Peter Hinch
Index to my micropython libraries.

fdufnews
Posts: 76
Joined: Mon Jul 25, 2016 11:31 am

Re: Low frequency Signal generator for CTCSS

Post by fdufnews » Mon Aug 02, 2021 7:21 am

The frequency of the PWM has nothing to do with the one of the signal you want to synthesize. Apart than the frequency of the PWN shall be way higher than the one you synthesize.

You should use DDS to create your signal. DDS stands for Direct Digital Synthesis.
Say you have a large accumulator you call at known interval. Each time you call the accumulator you add an increment to it.
Say the output of the accumulator addresses an array containing the waveform you want to synthesize.
The output of the array is used either as the input of a DAC or to a PWM function.
Varying the frequency of the call and/or the increment you can synthesize any frequency.
The constraints are :
  • the call to the accumulator function shall be executed at a frequency much higher than the frequency*resolution of the signal you want to synthesize
  • the size of the accumulator and the frequency of the call give the resolution of the signal.
  • The higher the resolution is, the lower the distortion will be
Here you'll find a more detailed explanation on DDS's principle https://www.analog.com/en/analog-dialog ... hesis.html
I am not sure Micropython is the best solution to the problem as the accumulator function needs to be called at high frequency, although the frequencies you want to synthesize are quiet low.
DDS is used to generate signals in electronic music with low end microcontrollers. But they are programmed in C.

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

Re: Low frequency Signal generator for CTCSS

Post by pythoncoder » Mon Aug 02, 2021 7:55 am

fdufnews wrote:
Mon Aug 02, 2021 7:21 am
The frequency of the PWM has nothing to do with the one of the signal you want to synthesize. Apart than the frequency of the PWM shall be way higher than the one you synthesize...
It's common to use PWM to generate a fixed frequency square wave. The solution you propose would involve changing the duty ratio at a rate of up to 192Hz. This implies using a timer interrupt, and if timers are problematic on the Pico then the problem would still exist.

DDS might work if the Pico actually had a DAC. It doesn't - but as you say it could drive the PWM duty ratio. But the killer is that it implies a timing signal of precise regularity which @Pip says we lack.

I still think the PIO is the best solution because its timing is precise and entirely independent of the CPU.
Peter Hinch
Index to my micropython libraries.

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

Re: Low frequency Signal generator for CTCSS

Post by pythoncoder » Mon Aug 02, 2021 8:39 am

On reflection this may be a ready made solution. It uses the PIO to output arbitrary pulse trains. Each pulse width is specified by a number of μs, that number can be 16 or 32 bits. Pulse widths are stored in an array of any length and can be output continuously. So you could output a fixed, precise, low frequency or a higher frequency PWM whose duty changed so that its mean value was a sinusoid.

The original aim was for pulses in the hundreds of μs but it should work at very low frequencies. Ignore the carrier option.
Peter Hinch
Index to my micropython libraries.

User avatar
Pip
Posts: 11
Joined: Mon Jul 12, 2021 6:37 pm
Contact:

Re: Low frequency Signal generator for CTCSS

Post by Pip » Mon Aug 02, 2021 3:25 pm

Thank you everyone for you input. :)

Just to add a bit more clarity. I am happy to use a square wave output by simply bit banging an IO pin. A simple RC filter will round off square and give a suitable tone to inject into the radio transmitter. No need to use and DAC to synthase a frequency

I have tested the PWM method with the duty cycle set to 50:50 and all was well. I get a nice stable bit stream at the designated frequency. As I mentioned in micropython on the Pico we can't do fractions of Hz. Unlike other micro-controllers, I think I saw some code somewhere in python or it may have been C were the PWM frequency was controllable by timing values rather than Hz and getting higher resolution frequencies. So on the Pico with micropython we seem to be at a dead end with PWM :x

Using the timers with callbacks in my case is even more problematic as I have a couple of other timers configured to read switches and control a display. So it looks like the jitter I am seeing is down to scheduling of the callbacks and the functions being run from these. Furthermore I am driving a sh1106 OLED display using I2C as well as monitoring Vsys voltage using an ADC(29) so call backs don't seem to be a reliable where it comes to timings.

On my test jig, in the absence of an oscilloscope, I hung an LED off the output pin where I was sending the tone out, and one can actually see the jittering, and indeed the jitter is linked to the other timers as it correlates with switch movements.

So unless we can find a way of getting the PWM to do frequency other than increments of 1 Hz I am leaning towards Peters idea of the PIO , as these will happily run without interruption from the various timers I have declared.

More pondering to be done :geek:
Last edited by Pip on Mon Aug 02, 2021 9:13 pm, edited 1 time in total.

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

Re: Low frequency Signal generator for CTCSS

Post by pythoncoder » Mon Aug 02, 2021 4:26 pm

If you have multiple timers running then I think jitter is inevitable. The poor old CPU can only do one thing at once ;) I never use timers to read switches or to control displays: uasyncio is your friend here. See the official docs and my tutorial - this repo has code for debouncing switches and pushbuttons. I think there should be no jitter if only one timer is running, otherwise we have a bug.

My PIO code should be easy if you just want a fixed frequency squarewave. This conversation got me thinking about using it to synthesise arbitrary (slow) waves by pre-computing the on and off times to perform PWM.

Re squelch <nostalgia mode ON> I developed a solution for a hi-fi FM receiver which was published in Wireless World. Back in the days when switching an analog signal with a JFET (with ultra low distortion) was fairly cutting-edge. In 1973...
Peter Hinch
Index to my micropython libraries.

User avatar
Pip
Posts: 11
Joined: Mon Jul 12, 2021 6:37 pm
Contact:

Re: Low frequency Signal generator for CTCSS

Post by Pip » Mon Aug 02, 2021 11:55 pm

Right I think I may have it. I have had a play with the PIO and came up with this:

Code: Select all

# PIO timing tests
from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
from time import sleep

#outputs a signal through a single GPIO pin
@asm_pio(set_init=PIO.OUT_LOW)

def CTCSS_out():     # we are setting this so that we do this in 100 clock cycles to make the maths easy
    set(pins,1) [24]     # pin high
    nop() [24]
    set(pins,0) [24]     # pin low
    nop() [24]
    
TestTone = 71.9  # one of the standard CTCSS tones

CTCSS_freq = int(TestTone*100)   # note minimum value for CTCSS_freq is 1908
CTCSS_gen =  StateMachine(0,CTCSS_out, freq=CTCSS_freq, set_base=Pin(9))   # In my tests I am outputting to GPIO pin 9

print (CTCSS_freq)

CTCSS_gen.active(1) # turn it on 
sleep(30)           # wait 30 seconds
CTCSS_gen.active(0) # turn it off
CTCSS_gen.exec("set(pins,0)") # set GPIO pin low just to be sure

So it looks like I have increased the resolution by 100. All I need to do is multiply the tone frequency I want by 100 and the PIO will do its thing. By having 100 clock cycles in the PIO code we are doing a divide by 100 of the frequency. Or at least that is what I am thinking :idea:

Changing the duty cycle can be done by modifying the wait-state clock cycles, those being the numbers the square brackets. But I am only interested in 50:50

Looks to be running a treat, nice and stable. I need to do more tests and check the frequencies for accuracy and jitter. I have yet to integrate this into the rest of my code with all the timers and stuff, so still not proven at this stage. Fingers crossed though.

Can someone sanity check this for me.

Cheers
Last edited by Pip on Wed Aug 04, 2021 3:49 pm, edited 2 times in total.

Post Reply