-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.c
201 lines (141 loc) · 7.47 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
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
/*
* main.c
*
* Created: 28.12.2018 22:30:22
* Author : Andreas Ennemoser
* Description: ATTINY85 servo control with 8-bit resolution
* The servo can be moved to 256 positions within its 1ms-2ms duty range
* The angle normally is -45° / +45° --> 90° / 256 steps = 0.35° resolution
*
* Credits: JimK (AVR-Freaks forum) "https://www.avrfreaks.net/comment/810846#comment-810846"
*/
// the clock speed on ATTINY85 is approx. 8MHz
// if fuse CKDIV8 is set (factory default), a prescaler of 8 is used which results in a 1MHz clock
// for this code CKDIV8 needs to be unset as the code relies on 8MHz CPU speed
// the actual frequency can be measured at PB4 if the CKOUT fuse is set
// ATTINY85 PIN Configuration (for more details see datasheet page 2)
// -------
// RESET / PB5 ---| o |--- VCC
// ADC3 / PB3 ---| |--- PB2 / SCK
// PB4 ---| |--- PB1 / MISO
// GND ---| |--- PB0 / MOSI
// -------
// includes
#include <avr/io.h>
#include <avr/interrupt.h>
// pin configuration (PB5 is RESET and would require special treatment if used)
// take care that each port is assigned only once
#define SERVO_PORT PB0 // define pin where servo is attached (one of PB0, PB1, PB2, PB3, PB4)
#define LED_PORT PB4 // define pin where LED is attached (one of PB0, PB1, PB2, PB3, PB4)
#define POTENTIOMETER_PORT PB3 // define pin where potentiometer is attached (one of PB2, PB3, PB4)
#define SERVO_PWM_ON PORTB |= (1 << SERVO_PORT)
#define SERVO_PWM_OFF PORTB &= ~(1 << SERVO_PORT)
#define LED_ON PORTB |= (1 << LED_PORT)
#define LED_OFF PORTB &= ~(1 << LED_PORT)
// variables
float servo_pwm_duty; // ADC reading (10-bit) mapped to servo duty cycle (8-bit )
volatile uint8_t servo_pwm_duty_int; //
volatile uint16_t adc_value; // result from 10-bit ADC reading (0-1023)
volatile uint8_t overflows = 0; // number of timer1 overflows (0-255)
volatile uint8_t flag = 0; // flag that indicates that initial 1ms of servo pulse is over
void Init_ADC(void){
// this function initializes the ADC
// do NOT left shift result (ADLAR) when reading ADC (see ADC_Read)!!!
if (POTENTIOMETER_PORT == PB2){
ADMUX |= (1 << MUX0); // use ADC2 at PB2 for ADC input (datasheet page 135)
}
else if (POTENTIOMETER_PORT == PB3){
ADMUX |= (1 << MUX1) | (1 << MUX0); // use ADC3 at PB3 for ADC input (datasheet page 135)
}
else if (POTENTIOMETER_PORT == PB4){
ADMUX |= (1 << MUX1); // use ADC4 at PB4 for ADC input (datasheet page 135)
}
// input clock frequency should be between 50kHz and 200kHz to get maximum resolution (datasheet page 125)
// 8MHz CPU with ADC prescaler of 64: 8000000 / 64 = 125000 --> 125 kHz
ADCSRA |= (1 << ADEN); // Enable ADC (datasheet page 136)
ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // set ADC prescaler to 64 (datasheet page 136)
}
uint16_t Read_ADC(void){
// this function reads analog values through ADC
uint16_t adc_result;
ADCSRA |= (1 << ADSC); // ADC Start Conversion (datasheet page 136)
while (ADCSRA & (1 << ADSC) ); // wait till conversion complete (ADSC will read as one as long as a conversion is in progress. When the conversion is complete, it returns to zero. page 136)
adc_result = ADC; // if not reading ADCL and ADCH do NOT left shift result in ADC_Init (i.e. keep ADLAR bit unset) !!!!! (the 16bit ADC is correctly populated by the AVRgcc toolchain)
return adc_result;
}
float Map_ADC(float adc_value, float adc_min, float adc_max, float map_min, float map_max) {
// function that maps the ADC reading to a target range
// ADC reading with 10-bit resolution results in values from 0 to 1023
// the mapping of these values results in values from map_min to map_max
float result;
result = map_min + adc_value * (map_max - map_min) / (adc_max - adc_min);
return result;
}
void Init_PORT(void) {
// this function initializes the pins which are used as output
DDRB |= (1 << LED_PORT); // set led port as output
DDRB |= (1 << SERVO_PORT); // set servo port as output
}
void Init_TIMER(){
// this function initializes the timer
// the timer is set up in order to last approx. 1ms per overflow
// the CPU runs at 8MHz (requires that CKDIV8 fuse is NOT set)
// timer 1 is used as it allows for more prescalers; here 32 is used to come from 8MHz to 250kHz
TCCR1 |= (1 << CS12) | (1 << CS11); // set prescaler of timer1 to 32
}
void Init_INTERRUPTS(){
// this function initializes the interrupts
TIMSK |= (1 << TOIE1); // enable overflow interrupt of timer1 (page 92)
// did not work with OCIE1A???
TIMSK |= (1 << OCIE1B); // enable compare match B interrupt of timer1 (page 92)
sei(); // enable interrupts (MANDATORY)
}
void LED_Control(){
// this function switches the LED on if servo center position is reached
uint8_t shift = 8; // fine tune center position
if ( (servo_pwm_duty_int > 125 + shift) && (servo_pwm_duty_int < 127 + shift) ) {
LED_ON;
}
else {
LED_OFF;
}
}
int main(void)
{
Init_PORT(); // initialize PORTB (output pins only)
Init_ADC(); // initialize ADC converter
Init_TIMER(); // initialize timer
Init_INTERRUPTS(); // initialize interrupts
while(1)
{
adc_value = Read_ADC(); // get 10-bit ADC result (0-1023)
servo_pwm_duty = Map_ADC(adc_value, 0, 1023, 1, 254); // 1 and 254 to stay away from adjacent overflows
servo_pwm_duty_int = (int)servo_pwm_duty;
LED_Control();
}
}
ISR(TIMER1_OVF_vect){
// interrupt service routine for timer1 overflow
// 1 interrupt is approx. 1ms
overflows++;
if (overflows == 20){ // corresponds to 20ms servo frequency (50Hz)
SERVO_PWM_ON;
overflows = 0;
}
if (overflows == 1){ // corresponds to 1ms servo pulse minimum
flag = 1;
// adds in the compare B match interrupt ISR the remaining part of servo pulse on top of this minimum 1ms pulse
// can be anything between 0ms and 1ms (or 0-255 steps according to timer1 setup)
OCR1B = servo_pwm_duty_int; // set Timer/Counter1 Output Compare RegisterB (datasheet page 91)
TCNT1 = 0; // reset counter for the upcoming match with OCR1B
}
}
ISR(TIMER1_COMPB_vect){
// interrupt service routine for timer1 compare match based on OCR1B value
// this match happens anywhere between 1ms and 2ms after SERVO_PWM_ON
// the match is varying depending on the ADC value read from the potentiometer which is mapped to servo_pwm_duty_int (256 steps)
if (flag){
SERVO_PWM_OFF;
flag = 0;
}
}