-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathServoATtiny85.cpp
216 lines (189 loc) · 6.55 KB
/
ServoATtiny85.cpp
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
203
204
205
206
207
208
209
210
211
212
213
214
/*
ServoATtiny85.cpp - Interrupt driven Servo library for ATTiny85 using 8 bit timer1 - Version 1
Sawchuk.
*/
#include <Arduino.h>
#include "ServoATtiny85.h"
// This code prefers a 16MHz clock (don't use 1MHz)
#define usToTicks(_us) (( clockCyclesPerMicrosecond() * _us) / 256) // converts microseconds to tick (assumes prescale of 256)
#define ticksToUs(_ticks) (( (unsigned)_ticks * 256) / clockCyclesPerMicrosecond() ) // converts from ticks back to microseconds
#define TRIM_DURATION 2 // compensation ticks to trim adjust for digitalWrite delays
static servo_t servos[MAX_SERVOS]; // static array of servo structures
static volatile uint8_t curServo; // index to the servo being pulsed
static uint8_t ServoCount; // the total number of attached servos
static volatile uint16_t OCR1Ctotal; // cumulative HIGH pulse length in ticks
static volatile uint16_t Refresh_Delay_Counter; // interrupt count down timer
#define SERVO_MIN() (this->min) // minimum value in uS for this servo
#define SERVO_MAX() (this->max) // maximum value in uS for this servo
/************ static functions common to all instances ***********************/
static uint8_t getServo(uint8_t start) {
for (uint8_t i = start; i < MAX_SERVOS; i++) {
if (servos[i].isActive) return i;
}
return INVALID_SERVO;
}
static void servo_timer() //set timer1 interrupt
/*
* when we enter this, servo[curServo].pin has been set HIGH and we must keep it high servo[curServo].ticks
* when that's done, go to next servo and do the same
* then wait for refresh
*/
{
TCCR1 = 0;
TCNT1 = 0;
if (servos[curServo].pulse == HIGH) { // we must stay high for an interrupt
OCR1C = servos[curServo].ticks; // how long to stay high
OCR1Ctotal += OCR1C;
}
else { // pulse is LOW
// if another servo, start its pulse, else begin wait for refresh
if ((curServo = getServo(curServo+1)) != INVALID_SERVO) {
digitalWrite(servos[curServo].pin,HIGH);
servos[curServo].pulse = HIGH;
OCR1C = servos[curServo].ticks; // how long to stay high
OCR1Ctotal += OCR1C;
}
else { // must wait until refresh interval is complete
Refresh_Delay_Counter = (usToTicks(20000)-OCR1Ctotal) / 24; // this will count down, represents # interrupts with OCR1C = 24, to reach a 20ms pulse width
OCR1C = 24;
OCR1Ctotal = 0;
}
}
// interrupt COMPA
OCR1A = OCR1C;
// turn on CTC mode
TCCR1 |= (1 << CTC1);
// Set CS13 and CS10 bits for 256 prescaler
TCCR1 |= (1 << CS13)|(1 << CS10);
TIMSK |= _BV(OCIE1A); // enable the output compare interrupt
}
ISR(TIMER1_COMPA_vect)
{
if (curServo != INVALID_SERVO && servos[curServo].pulse == HIGH) { // we have been HIGH for the last 544 - 2400 microseconds
digitalWrite(servos[curServo].pin,LOW); // so, go LOW
servos[curServo].pulse = LOW;
servo_timer();
}
else { // we are waiting until 20ms has elapsed (servos use a 50 HZ (20 millisecond) waveform)
if (--Refresh_Delay_Counter <= 0) { // 20ms has elapsed when countdown expires
if ((curServo = getServo(0)) != INVALID_SERVO) { // restart servo HIGH pulse
digitalWrite(servos[curServo].pin,HIGH); // start the ON cycle again (begin a new waveform on HIGH)
servos[curServo].pulse = HIGH;
servo_timer();
}
}
}
}
static void initISR()
{
if ((curServo = getServo(0)) != INVALID_SERVO)
{
digitalWrite(servos[curServo].pin,HIGH);
servos[curServo].pulse = HIGH;
cli();
servo_timer();
sei();
}
}
static void finISR()
{
//disable use of the timer1 compare
TIMSK &= ~_BV(OCIE1A) ; // disable timer1 output compare interrupt
//timerDetach(TIMER1OUTCOMPAREA_INT);
}
static boolean isTimerActive()
{
for (int i = 0; i < MAX_SERVOS; i++) {
if (servos[i].isActive) {
return true;
}
}
return false;
}
/****************** end of static functions ******************************/
Servo::Servo()
{
if (ServoCount < MAX_SERVOS)
this->servoIndex = ServoCount++; // assign a servo index to this instance
else
this->servoIndex = INVALID_SERVO ; // too many servos
}
bool Servo::attach(int pin)
{
return this->attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
}
bool Servo::attach(int pin, int min, int max)
{
if (this->servoIndex < MAX_SERVOS) // ensure channel is valid
{
pinMode( pin, OUTPUT) ; // set servo pin to output
digitalWrite(pin,LOW);
servos[this->servoIndex].pin = pin;
if (min > 300 && min < 3000) this->min = min;
if (max > min && max < 3000) this->max = max;
servos[this->servoIndex].ticks = usToTicks(DEFAULT_PULSE_WIDTH); // store default values
servos[this->servoIndex].pulse = LOW;
servos[this->servoIndex].isActive = true;
initISR();
return true;
}
return false;
}
void Servo::detach()
{
if (this->servoIndex < MAX_SERVOS) // ensure channel is valid
{
servos[this->servoIndex].isActive = false;
if(!isTimerActive()) finISR();
}
}
void Servo::write(int value)
{
if (this->servoIndex < MAX_SERVOS) // ensure channel is valid
{
if (servos[this->servoIndex].isActive)
{
if (value < MIN_PULSE_WIDTH)
{ // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds)
if(value < 0) value = 0;
if(value > 180) value = 180;
value = map(value, 0, 180, SERVO_MIN(), SERVO_MAX());
}
this->writeMicroseconds(value);
}
}
}
void Servo::writeMicroseconds(int value)
{
if (this->servoIndex < MAX_SERVOS) // ensure channel is valid
{
if (servos[this->servoIndex].isActive)
{
// calculate and store the values for the given channel
if ( value < SERVO_MIN() ) // ensure pulse width is valid
value = SERVO_MIN();
else if ( value > SERVO_MAX() )
value = SERVO_MAX();
value = value - TRIM_DURATION;
value = usToTicks(value); // convert to ticks after compensating for interrupt overhead
servos[this->servoIndex].ticks = value;
}
}
}
int Servo::read() // return the value as degrees
{
return map( this->readMicroseconds()+1, SERVO_MIN(), SERVO_MAX(), 0, 180);
}
int Servo::readMicroseconds()
{
unsigned int pulsewidth;
if( this->servoIndex != INVALID_SERVO )
pulsewidth = ticksToUs(servos[this->servoIndex].ticks) + TRIM_DURATION ;
else
pulsewidth = 0;
return pulsewidth;
}
bool Servo::attached()
{
return servos[this->servoIndex].isActive;
}