-
Notifications
You must be signed in to change notification settings - Fork 0
/
AAT-Smart-LED-demo-board.ino
194 lines (179 loc) · 8.04 KB
/
AAT-Smart-LED-demo-board.ino
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
/******************************************************************************************
AG935 demo board with an AAT003 sensor connected to a 60 smart-LED circular array via an
ATtiny85. Code written in Arduino IDE targeted at "Adafruit Trinket (ATtiny85 @ 8 MHz),"
and is portable to other Arduino boards (add delays per comments for faster processors).
Sensor "SIN" output to PB3; "COS" to PB4; array input on PB2; PWM output on PB1
(ratiometric; 0-Vcc = 0-360 deg.). Active low "CALIBRATE" jumper on PB0.
3 bytes RAM/LED ==> 120 of 512 bytes used in an ATtiny85.
Program uses ~5K flash out of 8K (5.4K available with Arduino bootloader).
Source code available on github.com. Rev. 5/2/18
*******************************************************************************************/
#include <Adafruit_NeoPixel.h> //Use NeoPixel Arduino routines for LEDs for convenience
//60 LEDs on PB2; 800 Khz LEDs:
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, 2, NEO_GRB + NEO_KHZ800);
int AATsin; //AAT signals
int AATcos;
float sinFiltered = 0.0; //Digitally filtered AAT signals
float cosFiltered = 0.0;
const float pi = 3.14159;
int angle; //Uncalibrated angle (0-59)
int angleOld; //Previous angle
bool dir = 0; //Rotation direction (cw = 1; ccw = 0)
unsigned int cycleCounter = 0; //Number of program iterations since angle changed
const int stopCycles = 25; //Sensor cycles stopped before turning off direction colors
const int arrowCycles = 4; //Loop cycles to update arrow (inverse animation speed)
unsigned int arrowPos = 0; //Arrow position away from start
char arrowPixel; //Arrow pixel animation position
unsigned char arrowPixelBrightness;
unsigned char i; //Arrow pixel index
char j; //Indicates cw or ccw arrow
const unsigned char brightness = 2; //Brightness (1-8)
const unsigned char m = 2; //Filter constant; Fc=Fsample/(m*2*pi); Fsample=~250/s
//Sensor min and max outputs (actual values determined in calibration routine)
unsigned char AATsinmin = 63; //Defaults to +-65 mV/V min amplitude offset by 128
unsigned char AATsinmax = 193;
unsigned char AATcosmin = 63;
unsigned char AATcosmax = 193;
//Uncalibrated angles where mix and max occur
unsigned char angleSinMin;
unsigned char angleSinMax;
unsigned char angleCosMin;
unsigned char angleCosMax;
//EEPROM address pointers
const unsigned char sin_offset_addr = 0; //Sin offset + 128
const unsigned char cos_offset_addr = 1; //Cos offset + 128
const unsigned char sin_pp_addr = 2; //Sin pk-pk amplitude (255 = 250 mV/V max)
const unsigned char cos_pp_addr = 3; //Cos pk-pk amplitude
void setup() {
//Full-speed clock needed for ATtiny to interface to smart LEDs
//Not needed with faster processors
CLKPR = 0x80; //Enable changing the internal clock
CLKPR = 0; //Set full speed internal clock
pinMode(0, INPUT); //Active low "CALIBRATE" jumper on PB0
digitalWrite(0, HIGH); //Turn on pull-up
//ADC setup (avoided Aruino routines to save memory)
ADCSRA &= ~(_BV(ADATE) | _BV(ADIE)); //Clear ADC auto trigger and interrupt enable
ADCSRA |= _BV(ADEN); //Enable ADC
strip.begin();
strip.show();
//Read uncalibrated angle
angle = readAngle();
angleOld = angle;
if (!digitalRead(0)) { //Jumper in place; invoke calibration routine
while (angle == angleOld) { //Reciprocating cw/ccw arrows until sensor turns
angleOld = angle;
strip.clear(); //Reset the LED array
for (i = 1; i < 30; i++) {
arrowPixel = 45 + (15 - i) * (1 - dir * 2); //Arrows centered at LED 45 (12:00)
//Brightness profile simulates an arrow
arrowPixelBrightness = 30 - arrowPos / arrowCycles - i;
//Arrow length 3
arrowPixelBrightness *= (arrowPixelBrightness > 0) && (arrowPixelBrightness < 4);
//Cube for nonlinear brightness vs. position; scale
arrowPixelBrightness *= arrowPixelBrightness * arrowPixelBrightness * brightness;
strip.setPixelColor(arrowPixel, 0, 0, arrowPixelBrightness); //Animated blue arrow
} //Finish setting arrow pixels (add delay here for faster processors)
strip.show();
arrowPos++;
if (arrowPos == arrowCycles * 27) {
arrowPos = 0; //Wrap arrow position at 11
dir = !dir;
} //Reverse arrow
angle = readAngle();
}
//Find sensor output minimums and maximums
for (cycleCounter = 1; cycleCounter < 1500; cycleCounter++) { //1500 loops (~5 sec)
strip.clear(); //Reset the LED array
angle = readAngle();
AATsin -= 384; //Subtract minimim outputs to ensure 8-bit (0-256), positive range
AATcos -= 384;
if (AATsin < AATsinmin) {
AATsinmin = AATsin;
angleSinMin = angle;
}
if (AATsin > AATsinmax) {
AATsinmax = AATsin;
angleSinMax = angle;
}
if (AATcos < AATcosmin) {
AATcosmin = AATcos;
angleCosMin = angle;
}
if (AATcos > AATcosmax) {
AATcosmax = AATcos;
angleCosMax = angle;
}
//Sensor position=White; calibration points in color
strip.setPixelColor(angle, 16 * brightness, 16 * brightness, 13 * brightness);
strip.setPixelColor(angleSinMin, 32 * brightness, 32 * brightness, 0); //SINmin=Yellow
strip.setPixelColor(angleSinMax, 32 * brightness, 0, 0); //SINmax=Red
strip.setPixelColor(angleCosMin, 0, 0, 32 * brightness); //COSmin=Blue
strip.setPixelColor(angleCosMax, 0, 32 * brightness, 0); //COSmax=Green
strip.show();
}
//Store calibration parameters
EE_write(sin_offset_addr, (AATsinmax + AATsinmin) / 2); //Offsets = average outputs
EE_write(cos_offset_addr, (AATcosmax + AATcosmin) / 2);
EE_write(sin_pp_addr, AATsinmax - AATsinmin); //pk-pk amplitudes for calibration
EE_write(cos_pp_addr, AATcosmax - AATcosmin);
} //End CALIBRATE routine
strip.clear();
cycleCounter = stopCycles;
} //End setup
void loop() {
readAngle();
//Offset correction using EEPROM parameters; add back 384 previously subtracted
AATsin -= EE_read(sin_offset_addr) + 384;
AATcos -= EE_read(cos_offset_addr) + 384;
//Digital filters--> Fc=Fsample/(m*2*pi); Fsample=~250/s
sinFiltered += (AATsin - sinFiltered) / m;
cosFiltered += (AATcos - cosFiltered) / m;
//Calculate calibrated angle; scale for 15360 = 360 degrees
angle = (atan2(sinFiltered / EE_read(sin_pp_addr), cosFiltered / EE_read(cos_pp_addr)) / pi + 1) * 7679;
analogWrite(1, angle / 60); //Scale 0-255; output PWM on PB1 (Arduino Write for simplicity)
angle = angle / 256; //Scale for 0-59 for LED array
if (angle != angleOld) {
dir = (angle > angleOld) ^ (abs(angle - angleOld) > 30); //cw=1; ccw=0; XOR fixes 59/0 crossing
strip.clear(); //Clear LEDs; set cw=Red, ccw=Green
strip.setPixelColor(angle, dir * 32 * brightness, !dir * 32 * brightness, 0);
angleOld = angle;
cycleCounter = 0;
} //Reset stop counter
if (cycleCounter >= stopCycles) { //Wash out direction colors to white if stopped
strip.setPixelColor(angle, 16 * brightness, 16 * brightness, 12 * brightness);
}
else cycleCounter++; //Increment stop counter
strip.show();
} //End main loop (add delay here for faster processors)
/*Functions*/
//EEPROM read
unsigned char EE_read(unsigned char addr) {
while (EECR & (1 << EEPE)); //Check for write in progress
EEAR = addr;
EECR |= (1 << EERE); //Start EEPROM read
return (EEDR);
}
//EEPROM write
void EE_write(unsigned char addr, unsigned char ucData) {
while (EECR & (1 << EEPE)); //Wait for completion of previous write
EECR = (0 << EEPM1) | (0 << EEPM0); //Set programming mode
EEAR = addr;
EEDR = ucData;
EECR |= (1 << EEMPE); //Write 1 to EEMPE
EECR |= (1 << EEPE);
} //Start EEPROM write by setting EEPE
//ADC subroutine
int getADC() {
ADCSRA |= _BV(ADSC); //Start conversion
while ((ADCSRA & _BV(ADSC))); //Wait for conversion
return ADC;
}
//Read uncalibrated angle (direct ADC access uses less memory than Arduino "analogRead")
//Retuns uncalibrated angle as a scaled 0-60 integer
unsigned char readAngle () {
ADMUX = 3; //Read sensor
AATsin = getADC();
ADMUX = 2;
AATcos = getADC();
return (atan2(float(AATsin - 512), float(AATcos - 512)) / pi + 1) * 30;
}