-
Notifications
You must be signed in to change notification settings - Fork 11
/
pwmVariableDDS.c
202 lines (167 loc) · 7.49 KB
/
pwmVariableDDS.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
* pwmVariableDDS.c
*
* Created: 2016-11-15
* Author : Craig Hollinger
*
* Source code for producing a DDS (Direct Digital Synthesis) generated sine
* wave signal. We'll use the Fast PWM Mode of Timer/Counter 0 and use an
* update frequency of 50,000Hz generated by Timer/Counter 2.
*
* DDS works by sampling only some of the table of voltage level values
* (SINE_TABLE) instead of all entries. By changing how many entries are
* skipped, the output frequency can be changed. The more that are skipped, the
* higher the frequency generated. The limit of minimum number of samples use
* (for the highest frequency) is two per cycle of SIN_TABLE as long as they are
* spread out evenly through the table. The way DDS works ensures this.
*
* In this version of the DDS, the frequency can be varied by the user typing
* the desired frequency on the PC keyboard. The microcontroller UART is
* utilized to do this.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either the GNU General Public License version 3
* or the GNU Lesser General Public License version 3, both as
* published by the Free Software Foundation.
*/
#include <avr/io.h>
#include <stdlib.h>/* for atoi() */
#include <avr/interrupt.h>
#include "uart/uart.h"
/* function prototypes */
void serialFrequency(void);
/* ASCII codes for carriage and line feed */
#define CR 0x0d
#define LF 0x0a
/* Initial DDS output frequency in Hz. */
#define DDS_INIT_FREQ 1000
/* DDS update frequency in Hz (NOT the PWM frequency). */
#define DDS_UPDATE_FREQ 50000
/* Maximum DDS output frequency that can be manually entered. */
#define MAX_DDS_FREQ 15000
/* Sine table, 256 entries. Each value represents a voltage level on a
sinusoidal waveform for one complete cycle.
This uses up a lot of RAM (256 bytes) because this is where it is stored.
Could be moved to FLASH, but would require more overhead to get the data out.
*/
uint8_t SINE_TABLE[]=
{
128, 131, 134, 137, 140, 143, 146, 149, 152, 156, 159, 162, 165, 168, 171, 174,
176, 179, 182, 185, 188, 191, 193, 196, 199, 201, 204, 206, 209, 211, 213, 216,
218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 237, 239, 240, 242, 243, 245,
246, 247, 248, 249, 250, 251, 252, 252, 253, 254, 254, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 254, 254, 253, 252, 252, 251, 250, 249, 248, 247,
246, 245, 243, 242, 240, 239, 237, 236, 234, 232, 230, 228, 226, 224, 222, 220,
218, 216, 213, 211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179,
176, 174, 171, 168, 165, 162, 159, 156, 152, 149, 146, 143, 140, 137, 134, 131,
127, 124, 121, 118, 115, 112, 109, 106, 103, 99, 96, 93, 90, 87, 84, 81,
79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39,
37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10,
9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8,
9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35,
37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76,
79, 81, 84, 87, 90, 93, 96, 99, 103, 106, 109, 112, 115, 118, 121, 124
};
/* These are the counters used to generate the index into SINE_TABLE. They are
16-bits long here, but could be just 8. Eight-bit registers will work but
more bits give better frequency resolution. */
uint16_t phaseReg, phaseInc;
/* This is the Timer 2 Compare A interrupt service routine. Runs every time
the counter (TCNT0) matches the output compare register A (OCR0A). Here a
new value is read from SINE_TABLE and written the output compare register. */
ISR(TIMER2_COMPA_vect)
{
uint8_t i;
phaseReg += phaseInc;
i = (uint8_t)(phaseReg >> 8); /* use only the upper 8-bits */
/* because PORTC is only 6-bits wide, use only the upper 6-bits for OCR0A
so that both outputs will match */
OCR0A = SINE_TABLE[i] & 0b11111100; /* update PWM register */
PORTC = SINE_TABLE[i] >> 2;/* update R2R ladder */
}/* end ISR(TIMER2_COMPA_vect) */
/* This is where it all happens. */
int main(void)
{
/* Set up the IO port registers for the IO pin connected to the PWM output
pin OC0A (PD6), Arduino pin 6. and PORTC for the R2R ladder. */
DDRD |= (_BV(PORTD6)) | (_BV(PORTD5));
DDRC = 0b00111111;
/* Set up Timer 0 to generate a Fast PWM signal:
- clocked by F_CPU (fastest PWM frequency)
- non-inverted output */
TCCR0A = (_BV(WGM00) | _BV(WGM01) | _BV(COM0A1)); /* TC0 Mode 3, Fast PWM */
TCCR0B = _BV(CS00); /* TC0 clocked by F_CPU, no prescale */
OCR0A = 0; /* start with duty cycle = 0% */
/* Set up Timer 2 to interrupt at 50,000Hz:
- clocked by F_CPU / 8
- generate interrupt when OCR2A matches TCNT2
- load OCR2A with the count to get 50,000Hz
*/
TCCR2A = _BV(WGM21); /* TC2 mode 2, CTC - clear timer on match A */
TCCR2B = _BV(CS21); /* clock by F_CPU / 8 */
OCR2A = (F_CPU / 8 / DDS_UPDATE_FREQ - 1); /* = 39 */
TIMSK2 = _BV(OCIE2A); /* enable OCIE2A, match A interrupt */
phaseReg = 0;
phaseInc = DDS_INIT_FREQ * 65536 / DDS_UPDATE_FREQ;
uart_init(9600, USART_CHAR_SZ_EIGHT, USART_PARITY_NONE, USART_STOP_BIT_ONE);
/* enable the interrupt system */
sei();
/* run around this loop for ever waiting for an interrupt */
while (1)
{
serialFrequency();/* process characters received from the serial port */
}/* end while(1) */
}/* end main() */
/* serialFrequency()
Test if a character has been received from the serial port. Return if none,
otherwise process the character.
By calling this repeatedly, incoming serial characters can be processed to
set a new frequency. Only numerical digits will be received, anything else
will reset the process. Up to five digits can be received. If less than
five are needed to set the frequency, the RETURN key must be pressed. The
maximum frequency entered is limited to 15000Hz.
*/
void serialFrequency(void)
{
uint16_t phaseIncTemp;
char c;/* the received character */
static char buf[6];/* a string to store the 5 received characters, one extra
space to store the null terminator */
static uint8_t i = 0;
if(uart_available() == UART_AVAILABLE)/* test if a character is waiting */
{
c = uart_getchar();/* save the character */
uart_putchar(c);/* echo it back to the terminal */
if(((c >= '0') && (c <= '9')) || (c == CR))/* is it a valid character? */
{
if(c != CR)/* put it in the buffer if it is not a RETURN */
{
buf[i++] = c;
}
if((i == 5) || (c == CR))/* process if five characters have been received
or carriage return received */
{
if(i > 0)/* do conversion only if at least one character entered */
{
buf[i] = 0;/* null terminate to make it a string */
phaseIncTemp = atoi(buf);/* convert string to an integer */
if(phaseIncTemp > MAX_DDS_FREQ)
{
phaseIncTemp = MAX_DDS_FREQ;/* limit to the maximum value */
}
phaseInc = phaseIncTemp * 65536 / DDS_UPDATE_FREQ;/* update DDS phaseInc */
buf[0] = 0;/* reset everything */
i = 0;
uart_putchar(CR);/* move cursor onto beginning of next line */
uart_putchar(LF);
}/* end if(i > 0) */
}/* end if((i == 5) || (c == CR)) */
}
else/* not an acceptable character, reset the process */
{
buf[0] = 0;
i = 0;
}/* end if(((c >= '0') && (c <= '9')) || (c == 0x0d)) */
}/* end if(uart_available() == UART_AVAILABLE) */
}/* end serialFrequency() */