feature: add PWM pin transmitter to the Morse code example#11
feature: add PWM pin transmitter to the Morse code example#11deadprogram wants to merge 1 commit intomainfrom
Conversation
d8f49ab to
dedf310
Compare
examples/morse/pwm.go
Outdated
| func generateSineWave(samples []float32, freq uint64, sampleRate float64) { | ||
| for i := range samples { | ||
| v := float32(math.Sin(2.0 * math.Pi * float64(i) * float64(freq) / sampleRate)) | ||
| samples[i] = (v + 1.0) / 2.0 // shift to [0, 1] | ||
| } | ||
| } |
There was a problem hiding this comment.
Depending on the frequency, number of samples and sampleRate this function would generate different number of sine wave periods, and the last sample would not always be at the end of the wave period so when sending samples continuously this will potentially create big phase shifts. Should we instead generate samples for single (or multiple) sine wave period and store them in samples. That way, instead of defining sample rate, we would define number of samples per sine wave period and make samples array of that size (or multiple of that size) and store it in PinRadio. Also, instead of storing values from 0 to 1 as samples, we could directly calculate and store duty values for the PWM (of type uint32) so they don't need to be calculated each time.
| func generateSineWave(samples []float32, freq uint64, sampleRate float64) { | |
| for i := range samples { | |
| v := float32(math.Sin(2.0 * math.Pi * float64(i) * float64(freq) / sampleRate)) | |
| samples[i] = (v + 1.0) / 2.0 // shift to [0, 1] | |
| } | |
| } | |
| func generateSineWave(samples []uint32) { | |
| top := pwm.Top() | |
| min := top / 4 | |
| max := top | |
| for i := range samples { | |
| v := float32(math.Sin(2.0 * math.Pi * float64(i) / float64(len(samples)))) | |
| v = (v + 1.0) / 2.0 // shift to [0, 1] | |
| samples[i] = uint32(float32(min) + v*float32(max-min)) | |
| } | |
| } |
There was a problem hiding this comment.
This is not what I want, however. The purpose is to generate an audible sine wave at a specific frequency (440Hz in this case), and then to transmit it over a different transmit frequency (540Khz) using amplitude modulation so it can be heard using an standard AM radio, or even a crystal radio.
There was a problem hiding this comment.
@deadprogram so If understand it correctly, you want 540Khz carrier frequency to be modulated with 440Hz sine wave signal that is modulated as morse code? If this is the case, I still don't see how is pwm generating 540Khz carrier frequency?
There was a problem hiding this comment.
Ah, that is thanks to this:
https://github.com/tinygo-org/wireless/blob/morse-pinradio/examples/morse/pwm.go#L21
There was a problem hiding this comment.
Ah, that is thanks to this: https://github.com/tinygo-org/wireless/blob/morse-pinradio/examples/morse/pwm.go#L21
So you are using PWM switching interval as a carrier frequency. I will have to get a refresher on my telecommunications theory, but it looks to me that you will need some specific band filter for it to work (or live with a lot of harmonics :) ).
This PWM period is defined independently of morse.NewMorse frequency parameter, and also samples are generated independent of Transmit frequency parameter so they are still ignored (which could be fine for the example purposes)
Regarding phase shift issue, I ploted the generated waveform from the example to clarify my point

as you can see, the phase at the beginning of the generated waveform is different than the one at the end, and since this waveform is played in the loop, there will be phase shift in each cycle:

That is why I suggested that generate function generates one (or multiple) complete sine waves.
There was a problem hiding this comment.
looks to me that you will need some specific band filter for it to work (or live with a lot of harmonics
That is true, hence the comment here: https://github.com/tinygo-org/wireless/pull/11/changes#diff-e9349dfe6339ee0a21f0420ebf324ce17c305b5ebb4888cd4e0233cd57bf7890R44
Regarding the phase, I think if I change the size of samples to some semi-clean division of sampleRate such as 344 the phase should mostly sync up.
| stopChan chan struct{} | ||
| } | ||
|
|
||
| func (r *PinRadio) Transmit(freq uint64) error { |
There was a problem hiding this comment.
frequency in morse constructor (540_000) is ignored, and also the frequency argument to Transmit function. If we change generateSineWave like I suggested in other comment, we could calculate sample duration on each Transmit call (or only if the frequency changes) and use that duration for sleep between samples.
r.sampleDuration = time.Duration(float64(time.Second) / float64(freq) / float64(len(r.samples)))
| for _, v := range r.samples { | ||
| top := pwm.Top() | ||
| min := top / 4 | ||
| max := top | ||
| duty := uint32(float32(min) + v*float32(max-min)) | ||
| pwm.Set(r.transmitChannel, duty) | ||
| time.Sleep(time.Duration(1e9/sampleRate) * time.Nanosecond) | ||
| } |
There was a problem hiding this comment.
The loop would look like this:
| for _, v := range r.samples { | |
| top := pwm.Top() | |
| min := top / 4 | |
| max := top | |
| duty := uint32(float32(min) + v*float32(max-min)) | |
| pwm.Set(r.transmitChannel, duty) | |
| time.Sleep(time.Duration(1e9/sampleRate) * time.Nanosecond) | |
| } | |
| for _, v := range r.samples { | |
| r.pwm.Set(r.transmitChannel, v) | |
| time.Sleep(r.sampleDuration) | |
| } |
Signed-off-by: deadprogram <ron@hybridgroup.com>
dedf310 to
622926e
Compare
|
@HattoriHanzo031 I did like some of your ideas on how to refactor the code, even if I still need to generate an audio tone, I think it is better now. Please take a look. |
| r.stop() | ||
| r.stopChan = make(chan struct{}) | ||
|
|
||
| go func(stop <-chan struct{}) { |
There was a problem hiding this comment.
Maybe instead spinning a goroutine on each Transmit call, just spin one goroutine on PinRadio init (or on first Transmit call) and then just send it start and stop signals over the channel
| samples := make([]uint32, 512) | ||
| generateSineWave(samples, 440, sampleRate) | ||
|
|
||
| m := morse.NewMorse(&PinRadio{transmitChannel: transmitChannel, samples: samples}, 540_000, 5) |
There was a problem hiding this comment.
I think morse protocol should not be configured with carrier frequency (540kHz) but instead with frequency that is modulated by digital signal (440Hz in this case) like it is the case with other radio types.
This PR adds a PWM pin transmitter to the Morse code example.
It uses amplitude modulation to transmit an audible 1200Hz tone on the broadcast frequency of 540KHz.