Sine wave generation using look up table

Thread Starter

Vihaan@123

Joined Oct 7, 2025
223
I have been struggling and tried everything possible to understand but no use, and i really need help here, I have created a sine look up table every 5 degree (i will modify to create a 1024-point sine look up table once i am clear with this). The array is like below, i have not added all the values, there will be some negative values as well.
Code:
sinearray[]={0.087155743    0.173648178    0.258819045    0.342020143    0.422618262    0.5    0.573576436    0.64278761    0.707106781    0.766044443    0.819152044    0.866025404    0.906307787, -0.087155743, -0.173648178, -0.258819045, -0.342020143, -0.422618262, -0.5}
I want to generate a sine wave using PWM signal duty cycle variation. Some of the understandings i have are
a. Make the sinearray negative values to positive values.
b. I can generate pwm frequency of ex: 10KHz and in the update interrupt i can update the duty cycle register.
Now the task is for example i have to generate 1KHz , 2KHz sine wave, what i have to do?
c. I am running the main clock at say 4MHz (after pre scaler) then for 10KHz the ARR register is 4MHz / 10KHz = 400 count.
How do i load the sinevalues into duty cycle register?
 

crutschow

Joined Mar 14, 2008
38,363
Not that familiar with micros to answer your question, but the PWM frequency should probably be at least 10 times the highest sinewave frequency you want to generate for good sinewave fidelity and low ripple.
 

MrChips

Joined Oct 2, 2009
34,664
You don't need a complete 0 to 2π table. All you need is 0 to π/2. You can detect which of the four quadrants you are in and then compute the required value. Personally, I never use floating point. I use integer math for fastest performance.

The sine values range from 0 to 1. The table values range from 0 to 1023 for a 10-bit PWM register. Simply multiply the sine values by 1024, take the integer value and save that in the table. Check to make sure that there is no value exceeding 1023.
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
223
You don't need a complete 0 to 2π table. All you need is 0 to π/2. You can detect which of the four quadrants you are in and then compute the required value. Personally, I never use floating point. I use integer math for fastest performance.
Ok.
The sine values range from 0 to 1. The table values range from 0 to 1023 for a 10-bit PWM register. Simply multiply the sine values by 1024, take the integer value and save that in the table. Check to make sure that there is no value exceeding 1023.
Thank you for the reply, so sinetable for integer conversion can i consider for example
sin(45) = 0.707*65535 = 46430 similarly from sin(0) to sin(90) = 65535.
dutycycle = {0,1,..65535} // 16 bit pwm

so output for example for 45 deg
output = {46430*65535/8 }={46430 * 8191, ..} // i will do 32 bit multiplication.
before sending to register i will send 46430*8191/ 65535 = 5803 Compare value
Now main question i have is for example i want 50Hz which are the table points i have to consider?
output[] = {x1, x2, x3,..} for 50Hz?
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
223
Not that familiar with micros to answer your question, but the PWM frequency should probably be at least 10 times the highest sinewave frequency you want to generate for good sinewave fidelity and low ripple.
Thank you for support, main worry is i am not able to understand the algorithm.
 

MrChips

Joined Oct 2, 2009
34,664
Ok.

Thank you for the reply, so sinetable for integer conversion can i consider for example
sin(45) = 0.707*65535 = 46430 similarly from sin(0) to sin(90) = 65535.
dutycycle = {0,1,..65535} // 16 bit pwm

so output for example for 45 deg
output = {46430*65535/8 }={46430 * 8191, ..} // i will do 32 bit multiplication.
before sending to register i will send 46430*8191/ 65535 = 5803 Compare value
Now main question i have is for example i want 50Hz which are the table points i have to consider?
output[] = {x1, x2, x3,..} for 50Hz?
Why are you doing an extra division by 8?
For 16-bit DAC, output = 65536*sin(θ)
For 13-bit DAC, output = 8192*sin(θ)
For 12-bit DAC, output = 4096*sin(θ)
For 10-bit DAC, output = 1024*sin(θ)

PWM modulates the pulse-width. You keep the repetition rate constant.
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
223
Why are you doing an extra division by 8?
For 16-bit DAC, output = 65536*sin(θ)
For 13-bit DAC, output = 8192*sin(θ)
For 12-bit DAC, output = 4096*sin(θ)
For 10-bit DAC, output = 1024*sin(θ)

PWM modulates the pulse-width. You keep the repetition rate constant.
Sorry few doubts here, i understand it partially i have microchip application in one of the threads, the code available is
Code:
#pragma config FOSC = INTOSC

unsigned char gDutycount =0;

const char SINETABLE[40]=

{

50,55,60,65,70,75,80,85,90,95,

100,95,90,85,80,75,70,65,60,55,

50,45,40,35,30,25,20,15,10,5,

0,5,10,15,20,25,30,35,40,45

};

// FOSC configuration

OSCCON = 0x78; // Fosc = 16 MHz with internal oscillator

__delay_us(100);

// Timer2 configuration for PWM

PR2 = 99; // PWM period register for 40 kHz

T2CON = 0x04; // Timer2 on

// PWM 1 configuration

PWM1CON = 0xC0; // PWM1 on, PWM 1 output enable

PWM1DCH = 50; // PWM duty initialized to 50%

PWM1DCL = 0;

PIE1bits.TMR2IE =1; // Timer2 interrupt enable

INTCON =0xC0; // Global interrupt enable, peripheral interrupt enable

TRISC = 0x00; // Port C as digital output port

ANSELC = 0x00; // Port C as digital output port

void interrupt Timer2_ISR(void)

{

if (TMR2IF)

{

++gDutycount; // Increment the counter variable by 1

if(gDutycount == 39)
{
gDutycount = 0;
}

PWM1DCH = SINETABLE[gDutycount]; // Load the duty cycle register

according to the sine table

TMR2IF = 0;

}

}
I will work on understanding the look up table values, but the application note says
1776102353652.png
So, my question can i use the same look up table and generate for example 2kHz, 50Hz etc sine signal?
 

Attachments

Ian0

Joined Aug 7, 2020
13,103
Why are you doing an extra division by 8?
For 16-bit DAC, output = 65536*sin(θ)
For 13-bit DAC, output = 8192*sin(θ)
For 12-bit DAC, output = 4096*sin(θ)
For 10-bit DAC, output = 1024*sin(θ)

PWM modulates the pulse-width. You keep the repetition rate constant.
Because zero output corresponds to a mark:space of 50:50, for a 16-bit DAC it is 32768*sin(θ)+32768 etc.
 

Thread Starter

Vihaan@123

Joined Oct 7, 2025
223
My intention of doing /8 is (360 degrees = 65535 and 45 degrees will be 65535/8), i do not know if it is making sense, i need to write the complete table then probably confusion will be less for everyone.
 

Ian0

Joined Aug 7, 2020
13,103
You don't need a complete 0 to 2π table. All you need is 0 to π/2. You can detect which of the four quadrants you are in and then compute the required value. Personally, I never use floating point. I use integer math for fastest performance.

The sine values range from 0 to 1. The table values range from 0 to 1023 for a 10-bit PWM register. Simply multiply the sine values by 1024, take the integer value and save that in the table. Check to make sure that there is no value exceeding 1023.
Memory is cheap, and processors tend to have plenty of it. Swapping and inverting is a lot of trouble when you can just have one big lookup table, but I agree on the integer arithmetic. You can't output a fraction of a bit in PWM!

For PWM the resolution is the clock frequency divided by the PWM frequency. So if you choose a high PWM frequency, you have limited resolution, and if you choose a high resolution you are limited in your number of samples.
You get the best result if the number of samples per waveform and the PWM resolution are about the same.
 

Ian0

Joined Aug 7, 2020
13,103
Sorry few doubts here, i understand it partially i have microchip application in one of the threads, the code available is
Code:
#pragma config FOSC = INTOSC

unsigned char gDutycount =0;

const char SINETABLE[40]=

{

50,55,60,65,70,75,80,85,90,95,

100,95,90,85,80,75,70,65,60,55,

50,45,40,35,30,25,20,15,10,5,

0,5,10,15,20,25,30,35,40,45

};

// FOSC configuration

OSCCON = 0x78; // Fosc = 16 MHz with internal oscillator

__delay_us(100);

// Timer2 configuration for PWM

PR2 = 99; // PWM period register for 40 kHz

T2CON = 0x04; // Timer2 on

// PWM 1 configuration

PWM1CON = 0xC0; // PWM1 on, PWM 1 output enable

PWM1DCH = 50; // PWM duty initialized to 50%

PWM1DCL = 0;

PIE1bits.TMR2IE =1; // Timer2 interrupt enable

INTCON =0xC0; // Global interrupt enable, peripheral interrupt enable

TRISC = 0x00; // Port C as digital output port

ANSELC = 0x00; // Port C as digital output port

void interrupt Timer2_ISR(void)

{

if (TMR2IF)

{

++gDutycount; // Increment the counter variable by 1

if(gDutycount == 39)
{
gDutycount = 0;
}

PWM1DCH = SINETABLE[gDutycount]; // Load the duty cycle register

according to the sine table

TMR2IF = 0;

}

}
I will work on understanding the look up table values, but the application note says
View attachment 365918
So, my question can i use the same look up table and generate for example 2kHz, 50Hz etc sine signal?
I made an audio signal generator using a 16-bit audio DAC and a sinewave lookup table. I used 48ksamples per second (because it is an audio standard), and implemented it by writing a lookup table for a 1Hz sinewave (48000 samples, 96000 bytes at 16 bit resolution)

I implemented the different frequencies by stepping through the table at the frequency rate.
i.e. for a 20Hz output, take every 20th entry.
for a 1000Hz output, take every 1000th entry.
By this method you can output every frequency from 1Hz to 24kHz at 1Hz resolution.
 

panic mode

Joined Oct 10, 2011
4,894
Memory is cheap, and processors tend to have plenty of it.
yup, specially since you only need coarse values - every 5 deg means your lookup table only needs 360/5=72 values with for 16bit integers is only 144 bytes. if you needed much finer increments, then using tricks to save memory at the cost of slightly more involved access is a no brainer - and that is what MrChips mentioned
 

nsaspook

Joined Aug 27, 2009
16,261
You don't need a complete 0 to 2π table. All you need is 0 to π/2. You can detect which of the four quadrants you are in and then compute the required value. Personally, I never use floating point. I use integer math for fastest performance.

The sine values range from 0 to 1. The table values range from 0 to 1023 for a 10-bit PWM register. Simply multiply the sine values by 1024, take the integer value and save that in the table. Check to make sure that there is no value exceeding 1023.
Depends on the processor. For a uC like the PIC32MK/PIC32AKARM with hardware double precision FP/DSP, it can be faster than integer calculations.

https://forum.allaboutcircuits.com/...-bug-in-windows-and-excel.187649/post-1743403
https://namoseley.wordpress.com/2015/07/26/sincos-generation-using-table-lookup-and-iterpolation/

Use the right processor for the job.

PIC32AK https://ww1.microchip.com/downloads...K1216GC41064-Family-Data-Sheet-DS70005592.pdf

  • CPU Speed Max MHz (megahertz):200
  • The CPU can issue, and the FPU can accept, no more than one instruction per clock cycle. However,
    once issued, the CPU and FPU use independent pipelines to execute the instruction. Consequently
  • there can be multiple instructions in the process of being executed in both pipelines at any one time. The FPU pipeline will stall the CPU when it is unable to accept any more instructions. The FPU pipeline is also sensitive to speculative instruction control from the CPU (i.e., such that not all issued FPU instructions will be committed). This allows FPU instructions to be located within speculative execution slots that follow conditional branches. After successful issue of an FPU instruction, the CPU continues as if executing a single cycle FNOP instruction, and the FPU instruction execution continues within the FPU. Therefore, as some FPU instructions require several cycles to complete, subsequent CPU (and/or FPU) instructions can be fetched, issued and executed (dependencies aside) while the FPU operation progresses. Only when the CPU encounters a hazard with the FPU will it be stalled until the hazard is resolved.
 
Last edited:

MrChips

Joined Oct 2, 2009
34,664
If you want to output 50 ksps, you have 20 μs between samples which is a long time using modern MCUs. So yes, it depends on the MCU you plan on using.

You don't need floating point at run time. The lookup table is generated at or before compile time. Or it can be generated on MCU power up. If MCU resources are limited, then you have to think like an MCU processor. Do you need 16-bit resolution or will 8 bits do?

If storage is limited, you might be able to get by with 8-bit entries from 0 to π/2. If there is ample storage, you can store the full 0 to 2π with 16-bit entries.

When you start thinking the way an MCU works, why select 1 degree steps? Use powers of 2 instead.
Divide the 0 to π/2 into 256 steps. Now you have an 8-bit register to define the angle. Choose 10 bits and the upper two bits will tell you which quadrant you are in. Bingo! No testing to do.

The take away here is: When you have MCU limitations such as speed and storage capacity, start thinking at the MCU level. You will discover clever hacks to improve efficiency.

Another game changer is DMA, direct memory access. You can get a timer module to output data straight from memory to a DAC or PWM output without needing software and interrupts to do the job. Output jitter is eliminated.
 

MrChips

Joined Oct 2, 2009
34,664
Use the right controller so you won't need clever hacks and be able use clear, understandable, structured, and concise code instead.
Right. That's why we have to put up with so much hardware and code bloat. I would rather have clever hacks than insane programmers.
 

nsaspook

Joined Aug 27, 2009
16,261
Right. That's why we have to put up with so much hardware and code bloat. I would rather have clever hacks than insane programmers.
I would prefer better programmers and fewer hacks. The solution to code bloat is not hacked software, it's good structured software written my good programmers. Software hacks are the equivalent of board jumpers and trace cuts on a prototype board. Necessary to get it going due to limitations that were missed or unanticipated at the design stage but should be removed ASAP with the next board revision.
 

nsaspook

Joined Aug 27, 2009
16,261
Thank you all for the clarifications and support, the board is NUCLEO-F446RE | Product - STMicroelectronics
I have just started putting together the logic, once i am clear will start thinking about implementing in the hardware board. Thank you once again.
Great! That processor, the Cortex-M4 core features a floating point unit (FPU) single precision and DSP instructions. Using floating point for your functions shouldn't cause a speed issue and will simplify the coding of wave generation routines.
 

MrChips

Joined Oct 2, 2009
34,664
If the MCU has DAC outputs, that would be a lot easier than using PWM. Read up on DMA. Using DMA will give you amazing performance.

Here is my step by step software development.
1. Write code to send one 12-bit integer to a DAC port.
2. Toggle between two values.
3. Increment a counter and send it to the DAC.
4. Store a 12-bit ramp in SRAM and send it to the DAC.
5. Store 1024 points of one cycle sine wave in SRAM and send it to the DAC.

Once you have done this, the next set of exercises is to use a timer module to trigger the outputs.

Finally, you want to let DMA do the work.

By the way, by the end of this project you will have created your own arbitrary waveform generator.
 
Top