-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.c
102 lines (87 loc) · 3.2 KB
/
main.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
// Copyright (c) 2013 Shaun Crampton.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <avr/io.h>
#include <util/delay.h>
#define STRINGIFY(s) XSTRINGIFY(s)
#define XSTRINGIFY(s) #s
#define PWM_FREQ 62500
#define PWM_RESOLUTION (F_CPU / PWM_FREQ)
#define MIN_DUTY_CYCLE 0.40
#define MAX_DUTY_CYCLE 0.80
const uint8_t MIN_PWM_LEVEL = PWM_RESOLUTION * MIN_DUTY_CYCLE;
const uint8_t MAX_PWM_LEVEL = PWM_RESOLUTION * MAX_DUTY_CYCLE;
#define VREF 1.1
#define DESIRED_VOUT 20.0
#define DIVIDER_RATIO 30.0
#define DESIRED_ADC_IN_V (DESIRED_VOUT / DIVIDER_RATIO)
const uint8_t DESIRED_ADC_RESULT = 255 * DESIRED_ADC_IN_V / VREF;
#define DUTY_CYCLE_REG OCR0B
#define ADC_ENABLE() (ADCSRA |= _BV(ADEN))
#define ADC_START_CONVERSION() (ADCSRA |= _BV(ADSC))
int main(void) {
/* Set A7 as an output. (Needed for PWM.) */
DDRA |= _BV(DD7);
PORTA = 0;
/* Let input power stabilize... */
_delay_ms(500);
/*
* Configure Timer0 as a fast PWM. It will
* - turn on the output pin at the start of each cycle
* - turn it off when the value hits DUTY_CYCLE_REG
* - wrap to 0 when it hits OCR0A
*/
TCCR0A = _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
OCR0A = PWM_RESOLUTION;
/* Start with 40% duty cycle and ramp up to avoid inrush. */
DUTY_CYCLE_REG = (uint8_t)(PWM_RESOLUTION * 0.40);
/* Set Timer0 clock source to be main oscillator. This enables the timer. */
TCCR0B = _BV(CS00) | _BV(WGM02);
/*
* Turn on the ADC,
* - use internal voltage ref.
* - configure ADC0 as our source
* - left-adjust the result, 8-bits is enough for us
* - disable digital input buffer on pin
* - enable the ADC.
*/
ADMUX = /* REF = */ _BV(REFS1) | /* INPUT = */ 0;
ADCSRA |= /* PRESCALER = 16 = 2^ */ 4;
ADCSRB |= /* LEFT-ADJUST */ _BV(ADLAR);
DDRA &= ~_BV(DD0);
DIDR0 |= _BV(ADC0D);
ADC_ENABLE();
_delay_ms(1);
/*
* Now enter our main loop. Monitor the output voltage and manipulate
* the duty cycle to control it.
*/
while (1) {
/* Wait for the Timer0 to overflow... */
loop_until_bit_is_set(TIFR0, TOV0);
/* End of our OFF period, should be peak voltage... */
TIFR0 |= _BV(TOV0); /* Clear the flag. */
/* Check the output voltage. */
ADC_START_CONVERSION();
loop_until_bit_is_clear(ADCSRA, ADSC);
uint8_t adc_result = ADCH;
if (adc_result < DESIRED_ADC_RESULT &&
DUTY_CYCLE_REG < MAX_PWM_LEVEL) {
DUTY_CYCLE_REG++;
}
else if (adc_result > DESIRED_ADC_RESULT &&
DUTY_CYCLE_REG > MIN_PWM_LEVEL) {
DUTY_CYCLE_REG--;
}
}
}