-
Hi everybody,
I'm trying to port this arduino DDS signal generator to the Blue Pill:
https://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/
I configured TIM1 to genrate a PWM carrier frequency at 281KHz and fires an interrupt each overflow. At this freq the duty resolution is 8bits so a 256 bytes sine LUT is provided.
In the TIM1 interrupt handler an index in the LUT is incremented and the corresponding value is put in the CCR to adjust the duty cycle.
The LPF is 1k + 0.1u.
The result is the attached screen shot. a beautiful 1KHz sine wave.
volatile uint8_t i;
void TIM1_UPhandler(void){
TIM1->CCR[0] = SINE_LUT[i++];
TIM1->SR = 0; // clear interrupt flag bits
}
The questions, please:
1- How to calculate the best values for the LPF elements?
I've read some where that the value of R depends on the GPIO pin output driving capability. How?
2- What's the MAXimum sine frequency I can get from this configuration? How to compute it?
3- How to generate all the range from 0 to MAX Hz?
I've tried the algorithm given in the above link but with no success:
unsigned int freq = 1000; // starting freq = 1000 Hz
unsigned int refclk = 281000;
volatile unsigned int phaccu; // phase accumulator
volatile unsigned char phase;
volatile unsigned int tword; // tunning word (phase increment)
tword = (1<<24)*freq/refclk;
void TIM1_UPhandler(void){
phaccu += tword; // increment the phase accu
phase = phaccu >> 24; // use upper 8 bits as index
TIM1->CCR[0] = SINE_LUT[phase];
TIM1->SR = 0; // clear interrupt flag bits
}
What I'm missing?
4- Can we use only 1/4 of the LUT to generate the sine wave? How?
Any ideas, suggestions, algorithms .... are very appreciated.
Thanks
-
Some python code to generate quarter of sine lookup table with arbitrary number of bits precision: https://github.com/gproskurin/learning/blob/master/embedded/stm32_midiplayer/lookup_gen.py
Corresponding C++ code to look up those tables: https://github.com/gproskurin/learning/blob/2fa53b832005f817e942e5689e60af3b52bd38db/embedded/stm32_midiplayer/src/player.cc#L73-L122
May be not ready to use in your project as-is, but you can easily tweak it for your needs
As a bonus, has generation of music notes frequencies if you need it :)
-
Thanks gpr for the links.
Perhabs we can save the 1/4 length LUT in the flash then with some formula we generate a full LUT in the RAM.
-
You can also use DMA to make it work independent of the processor.
Take a peak at my project here: https://github.com/pecostm32/STM32F303_Sine_Square_Generator/tree/main (https://github.com/pecostm32/STM32F303_Sine_Square_Generator/tree/main)
It uses the integrated DAC of the F303, but DMA can also be used to generate PWM output. Have the target address be the compare register of a timer and read the samples from FLASH. Pulse the DMA based on another timer to set the output frequency.
The output frequency is determined by the number of samples in the full sine table and how fast you output them to the PWM timer.
The maximum frequency is basically half the carrier frequency, but it won't be a nice sine anymore unless the low pass filter has a much lower cutoff point. The amplitude will be very low then.
When using DMA the quarter LUT is not very handy, but when doing it in software there is no need to unpack it to a full one. Just use the given phase to determine the quadrant and adjust the values based on it. See attached files for how this can be done.
The low pass filter calculations can be simple as long as a first order is good enough for you. Just take a resistor of say 15K and calculate the capacitor to go with it to set the filter to half or a third of the carrier frequency.
-
The output frequency is determined by the number of samples in the full sine table and how fast you output them to the PWM timer.
Here the speed of samples is constant and equals the frequency of the PWM carrier. How can I change the Nr of samples for each frequency?
You can also use DMA to make it work independent of the processor.
Yes I'm planing to use it later. In this pahe I'm just trying to grasp the principles.
Is there any idea how to compute R of the LPF in function of the GPIO pin fan out?
Why the code taken from the arduino don't work?
-
Not sure how it could potentially work: TIM1->CCR[0] = SINE_LUT[i++];
Access to CCR is like that: TIM2->CCR4 = reg_cc;
Q: 1- How to calculate the best values for the LPF elements?
I've read some where that the value of R depends on the GPIO pin output driving capability. How?
A: RC filter in simplest form: F = 1/ 2*pi()*R*C
GPIO may affect total R in formula only if external R is low, anyway there are max current limits for GPIO that prohibits of using low values of R,
Q: 2- What's the MAXimum sine frequency I can get from this configuration? How to compute it?
A: Nyquist says F/2, so higher carrier freq. drives sine up. In theory. Practically, upper sine freq. limited by LPF performance, to get sharp cut-off at F/2 order of the LPF filter has to be enormously high. So cost wise F/10, or even F/100 for first order RC filter.
-
In what I suggested with the DMA and two timers the outputting of the samples is not in sync with the period of the carrier. This way a single sample can be outputted multiple times or completely skipped because the compare timer is updated more then once during the carrier period. This is similar to using a phase accumulator setup.
The using of a phase accumulator should work. Here is a project I use a similar setup in. It is not finished and more of a play thing. The sine based drum sounds do work reasonably well in comparison to the original analog drum machine it is based on. The noise part is the problem though, but not relevant for your project.
There are a lot of comments in the source, but part of it is in Dutch, so might loose meaning in translation.
Don't get hung up on the GPIO fan out. For a simple RC based LPF the output impedance won't play a big role as long as the resistor is not to small and the capacitor not to big. For my project I use a 3K9 resistor with a 4n7 capacitor. So around 8680Hz low pass filter. With a carrier way up in the 70kHz range it works very well for the low frequencies in drum sounds. Not so good for the snares and cymbals though.
The one thing to point out, but it might be elsewhere in your code, the phase accumulator is not initialized to zero. Should only have impact on the initial phase though
The calculation of tword is not correct because of the low headroom in the variable. You are multiplying a 1000 with 16777216 and this is larger than 32 bits. Instead of using 32 bit integers you need to cast the calculation to 64 bits. (long long)
-
Not sure how it could potentially work: TIM1->CCR[0] = SINE_LUT[i++];
In my "hal.h" the struct timer is like this:
typedef struct {
................................
volatile uint32_t RCR; // 0x30 repetition counter register
volatile uint32_t CCR[4]; // 0x34 capture/compare registers 1 TO 4
..................................
} TIMER;
-
Instead of using 32 bit integers you need to cast the calculation to 64 bits. (long long)
You mean that I've to declare these vars as long long?
-
Instead of using 32 bit integers you need to cast the calculation to 64 bits. (long long)
You mean that I've to declare these vars as long long?
Either that or type cast in the calculation.
tword = (1<<24)*(long long)freq)/refclk;
//or simplified
tword = (long long)(freq << 24) / refclk;
I might be wrong but I think that casting just the freq part is enough. The divide will be based on 64 bits / 32 bits, which is ok and the result is automatically casted to 32 bits due to tword being 32 bits.
If you look back at the Arduino code, you would see that dfreq and refclk are declared as double. This takes care of the range there, but the calculation will be a bit slow. Using 64 bits integer on the F103 is faster with a bit of rounding error. I also noticed that they multiply the double with 4294967296 instead of 16777216. So check if your calculation provides correct values.
-
tword = (1<<24)*(long long)freq)/refclk;
//or simplified
tword = (long long)(freq << 24) / refclk;
Ok, thanks I'll try this ASAP.
Here is a project I use a similar setup in. It is not finished and more of a play thing.
WOW!! Very interresting project. I love drum sounds.
Can you share the circuit please?
Once upon a time I tried to build a sound effects machine using an ATMEGA328.
https://www.eevblog.com/forum/microcontrollers/avr-pcm-player-class-d-amp/ (https://www.eevblog.com/forum/microcontrollers/avr-pcm-player-class-d-amp/)
When using a simple audio amp (LM386) it works but when plugged in the aux input of a car radio I've got a lot of noise and the volume was very low!
-
When using:
tword = (1<<24)*(long long)freq)/refclk;
Her's the compilation errors:
error: expected ';' before ')' token
error: expected statement before ')' token
error: expected expression before '/' token
When using:
tword = (long long)(freq << 24) / refclk;
Her's the compilation errors:
undefined reference to `__exidx_start'
undefined reference to `__exidx_end'
undefined reference to `abort'
Note: I'm using the old bare metal toolchain YAGARTO.
-
Isn't obvious? You're missing a bracket at the very beginning, freq has a closing bracket, but isn't matching any opening: tword = (1<<24)*(long long)freq)/refclk;
See the difference? tword = ((1<<24)*(long long)freq)/refclk;
Just use the simplified form: tword = (long long)freq<<24 / refclk;, it's exactly the same thing.
-
You're missing a bracket at the very beginning.
Ok, now the same compilation errors:
undefined reference to `__exidx_start'
undefined reference to `__exidx_end'
undefined reference to `abort'
-
What same compilation errors? Wasn't your code working before?
Delete everything off your code (Make a backup of course), leave only an empty main(){ ; }
If that comopiles, start adding our code back. Maybe you messed up, lacking some key or semicolon.
Sometimes a wrong symbol can really mess compilation in a such way that it won't simply tell you wrote something wrong at line 656:33, but throw all kind of strange errors.
At this point, better you post the entire code in https://pastecode.io/
-
What same compilation errors? Wasn't your code working before?
Compi errors when I add this line:
tword = ((1<<24)*(long long)freq)/refclk;
When it was :
tword = (1<<24)*freq/refclk;
No errors.
Perhaps it's due to some math library missing.
-
We must cast all the result:
tword = (long long)((1<<24)*freq/refclk);
Now it compiles. But always no sine wave.
-
I also noticed that they multiply the double with 4294967296 instead of 16777216.
using:
double TUN_K = (4294967296.0/refclk); // tuning coefficient
and:
tword = TUN_K*freq;
Works without casting.
-
The original design was published in a magazine called ELO. I don't have the full articles, but do have a bunch of pages with the old schematics and PCB designs. I redrew the schematics in EasyEDA to preserve them, but not the PCB's.
I have modified and build the kit as part of a school project long ago and still have it, and it still works.
Have to make two posts due to the large size of the scans.
-
More pages of the original article.
-
And here are some images of the machine I made.
-
You're missing a bracket at the very beginning.
Ok, now the same compilation errors:
undefined reference to `__exidx_start'
undefined reference to `__exidx_end'
undefined reference to `abort'
This has to do with exception handling and it looks like that when using 64 bit variables the compiler references these "objects". How to resolve this I don't know, but maybe when you search on the net a solution can be found.
I have used 64 bit calculations before, but it might not have been on Cortex-M based devices. Can't find the project right now. Maybe it is due to the toolchain you are using. I'm on Linux and use the GNU compilers (arm-none-eabi), but not on the machine I'm typing this on. 8) This is my leisure, watch TV and browse the web machine.
-
Casting is fine. I'd simply #include <stdint.h> and use (uint64_t)(...) instead.
-
@pcprogrammer
I need the circuit of the STM32 based drum please.
Other questins:
- Is there an advantage using 2 timers: 1 for the PWM carrier and the other for the modulation?
- Have you any idea how to use the uC as a class D amplifier given that the PWM carrier is already generated?
-
I need the circuit of the STM32 based drum please.
Attached below are the schematic and the EasyEDA project. Also pictures of the setup I made. Nothing fancy, just a bluepill, a display and some simple modules. The potentiometers are connected between gnd and 3.3V and have a small capacitor on the wiper.
- Is there an advantage using 2 timers: 1 for the PWM carrier and the other for the modulation?
Only when using DMA.
- Have you any idea how to use the uC as a class D amplifier given that the PWM carrier is already generated?
I'm not up to speed with class D amplifiers, but if it is about having two signals to driving the output transistors timer 1 has provisions for things like dead time and outputting inverted signals.
-
Attached below are the schematic and the EasyEDA project.
Thanks.
How to solve the problem of noise and low volume when I connect the output of the PWM DAC to a power amplifier?
-
What's the effective frequency range you're seeking?
You can get a cheap PCM5102A I2S DAC (Up to 32-bit, 384KHz), then feed it with DMA.
Another think is, it's a bad idea to compute the value in the ISR before feeding the value to the register, might cause buffer underrun specially at a such high ISR frequency.
Do otherwise, have a precomputed value, load it instantly, then you have plenty of time to compute and store the next value.
void TIM1_UPhandler(void){
static uint16_t next_pwm_value;
TIM1->CCR[0] = next_pwm_value;
TIM1->SR = 0; // clear interrupt flag bits
phaccu += tword; // increment the phase accu
phase = phaccu >> 24; // use upper 8 bits as index
next_pwm_value = SINE_LUT[phase];
}
-
tword = (1<<24)*(long long)freq)/refclk;
//or simplified
tword = (long long)(freq << 24) / refclk;
I might be wrong but I think that casting just the freq part is enough. The divide will be based on 64 bits / 32 bits, which is ok and the result is automatically casted to 32 bits due to tword being 32 bits.
"Just casting the freq part" is, in fact, enough, but the simplified code is not OK, as it's not doing that:
freq is casted to 64 bits only after being shifted left 24 positions, this means that all bits 0-23 will be 0, as will all bits 32-63.
That is to say, the cast has accomplished nothing (a good compiler will simply ignore it (https://godbolt.org/z/E5qcEseGn)), and if freq is less than (1u << 24) = 16777216, the result will be, of course, 0.
The non-simplified code, as we know, is short of a bracket - or, alternatively has an extra bracket after freq - but correct as principle.
Just use the simplified form: tword = (long long)freq<<24 / refclk;, it's exactly the same thing.
No it's not.
Here we have a different problem:
The left shift operator has a lower priority than the division one, so freq will be shifted left by the result of of 24/refclock (https://godbolt.org/z/1nPjorfqE).
For any reasonable values of refclock (i.e. geater than 24[ tword will be set to value of freq.
The cast is once again immaterial.
You need the brackets. The following code will cast freq to 64 bits, without losing bits, then left shift it 24 positions, which is guaranteed not to overflow, then divide it by refclk.
The result will then be cast back to 32 bits.
tword = ((long long)freq<<24) / refclk;
I usually refer to the standard, but understanding operator priorities from it is cumbersome, as they are embedded in the way the grammar is defined.
This table (https://en.cppreference.com/w/c/language/operator_precedence) at cppreference.com is what I use - and yes, I admit I sometimes need it.
-
What's the effective frequency range you're seeking?
You can get a cheap PCM5102A I2S DAC (Up to 32-bit, 384KHz), then feed it with DMA.
The STM32F103 is not the best fit for I2S. It won't do 384kSa/s and there is no separate pll to get a proper sampling rate like 44.1kSa/s. You would need an external clock synthesizer to do this right.
I have a proto board with the bluepill, a PCM1808 module and a PCM5102A module on it and yes it works, but it is better to use a MCU with proper I2S implementation if you want some decent sample rate.
-
Just use the simplified form: tword = (long long)freq<<24 / refclk;, it's exactly the same thing.
No it's not.
Here we have a different problem:
The left shift operator has a lower priority than the division one, so freq will be shifted left by the result of of 24/refclock (https://godbolt.org/z/1nPjorfqE).
Right! :)
-
Now I'm using TIM1 to generate the PWM carrier @ 70KHz (for 10bit resolution) and TIM2 to trigger a DMA transfer from the LUT[] to TIM1->CCR1.
Many questions:
1- to synthetize frequencies from 0 to 35KHz, what's the best frequency for TIM2 (sampling freq)? can I use a single value for the full range or it's better to devide the range to multi parts and adjust the TIM2 frequency? How to calculate the TIM2 freq in both cases?
2- How to generate the LUT for 1Hz resolution? what's the best compromise between LUT size and frequency range?
3- How to adjust the amplitude of the generated signal?
I'm a total newebie in this domaine please forgive my ignorance.
-
Now I'm using TIM1 to generate the PWM carrier @ 70KHz (for 10bit resolution) and TIM2 to trigger a DMA transfer from the LUT[] to TIM1->CCR1.
Ok with this working you can set the output frequency by changing the rate of TIM2.
1- to synthetize frequencies from 0 to 35KHz, what's the best frequency for TIM2 (sampling freq)? can I use a single value for the full range or it's better to devide the range to multi parts and adjust the TIM2 frequency? How to calculate the TIM2 freq in both cases?
The way this works is that every time TIM2 counts out the DMA will transfer another sample to the "DAC". The length of the lookup table determines what the frequency will then be. Divide TIM2 rate by the number of samples and you have your output frequency. The PWM generator TIM1 will take care of either up or down sampling so to speak.
2- How to generate the LUT for 1Hz resolution? what's the best compromise between LUT size and frequency range?
This depends on your timer resolution. Just think about it in relation to the answer to question 1.
3- How to adjust the amplitude of the generated signal?
This requires recalculating the LUT values. The DMA can't do it for you, so if you want to set the amplitude you just lower or raise the value of all the samples. An external voltage or digitally controlled amplifier will give better results though. Search for digitally controlled potentiometer.
-
Ok with this working you can set the output frequency by changing the rate of TIM2.
What's the formula?
if you want to set the amplitude you just lower or raise the value of all the samples.
This initial LUT values are for MAX amplitude?
-
The documentation about the timers answers your first question. You have to consider the setting of the system clock, timer prescaler and the timer counter to get the required timer rate. Look at my projects on how the timers are set for different frequencies.
You will have to write code to do the calculations for you to set the timer registers dynamically, based on some input.
Creating an initial look up table with maximum output values is the easiest way to do it. Keep in mind that when lowering the amplitude, by lowering the values in the LUT, the distortion will go up.
There is also a pitfall with the asynchronous setup of the two timers, that can give problems at certain output frequencies. This has to do with when the CCR register is updated by the DMA in relation to where the PWM timer is in it's cycle.
Play with the concept to find these problems.