~gabe/flashlight-firmware/anduril2

« back to all changes in this revision

Viewing changes to loneoceans/gxb172/GXB172ATtiny841_V1p0_WIP.ino

  • Committer: Selene Scriven
  • Date: 2019-09-18 22:18:49 UTC
  • mfrom: (188.4.19 trunk)
  • Revision ID: bzr@toykeeper.net-20190918221849-7h4hcvl07ea3b6g6
merged updates from trunk, including gcc7/8/9 compatibility

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * GXB172 Rev A - Single-Cell ~50W 17mm Boost LED Flashlight Driver
 
3
 * Copyright (C) 2018 Gao Guangyan
 
4
 * 
 
5
 * This program is free software: you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation, either version 3 of the License, or
 
8
 * (at your option) any later version.
 
9
 * 
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 * 
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
17
 * 
 
18
 * Firmware version 1.0 (27 Mar 2018) for ATtiny841
 
19
 *  - For more information: www.loneoceans.com/labs/gxb172/
 
20
 *      - by Gao Guangyan, contact loneoceans@gmail.com
 
21
 *   
 
22
 * NOTE - THIS FIRMWARE IS A CONTINUAL WORK IN PROGRESS! 
 
23
 * NOTE - Values used in this example work for a Convoy S2+ Host
 
24
 * 
 
25
 * Code written in Arduino IDE for ease of use among hobbyists
 
26
 * 
 
27
 *      - Complied with Arduino 1.8.5 with ATtinyCore Board Library V1.1.4
 
28
 *         (http://highlowtech.org/?p=1695)
 
29
 *      - Select Tools>Board: ATTiny441/ATTiny841 (no bootloader) with 8MHz internal crystal
 
30
 *      - Board reference https://github.com/SpenceKonde/ATTinyCore
 
31
 *     (Currently using V1.1.4 of ATTinyCore)
 
32
 *     Settings: Board Attiny441/841 no bootloader, BOD LTO Disabled, ATtiny841, Clk 8MHz internal
 
33
 *      - Credit for Felias-fogg's SoftI2CMaster 
 
34
 *      - Credit for PID Library V1.2.1 by Brett Beauregard
 
35
 *     PID files need to be in the same directory during compile
 
36
 *     Some parts not required removed from code to reduce compile size
 
37
 * 
 
38
 * Complie using Arduino IDE (https://forum.arduino.cc/index.php?topic=131655.0)
 
39
 * Flash 841 using AVR ISP2 or Atmel ICE or similar (e.g. via Atmel Studio 7).
 
40
 * 
 
41
 *      - Set fuses to Internal 8MHz Oscillator, Low.CKDIV8 unchecked.
 
42
 *      - Fuses are: 0xFF,DF,C2 (EXT, HI, LOW)
 
43
 *  
 
44
 *  "Sketch uses 6852 bytes (83%) of program storage space.
 
45
 *   Global variables use 128 bytes (25%) of dynamic memory, leaving 384 bytes for local variables."
 
46
 *  To complie, enable Verbose Output, click Verify in Arduino IDE. Get .hex from displayed dir. 
 
47
 */
 
48
 
 
49
#define F_CPU 8000000
 
50
#define ADDRLEN 1
 
51
#define I2C_TIMEOUT 1000
 
52
#define SDA_PORT PORTA
 
53
#define SDA_PIN 6
 
54
#define SCL_PORT PORTA
 
55
#define SCL_PIN 4
 
56
 
 
57
#include <SoftI2CMaster.h>
 
58
#include <EEPROM.h>
 
59
#include "PID_v1.h"
 
60
 
 
61
// #######################################
 
62
// ##          SET DEFINITIONS          ##
 
63
// #######################################
 
64
 
 
65
// Pin Definitions (Based on Arduino IDE)
 
66
 
 
67
const uint8_t Pin_PWM = 3;      // PA7 PP15 OC2B
 
68
const uint8_t Pin_LedDbg = 7;   // PA3 PP2
 
69
const uint8_t Pin_Enable = 10;  // PA0 PP5
 
70
const uint8_t Pin_Batt = 1;     // PA1 ADC1 PP4
 
71
const uint8_t Pin_OTCA = 2;     // PA2 ADC2 PP3
 
72
const uint8_t Pin_OTCD = 8;     // PA2 ADC2 PP3
 
73
const uint8_t Pin_PB1 = 1;      // PB1 PP12
 
74
const uint8_t Pin_PB0 = 0;      // PB0 PP11
 
75
 
 
76
uint8_t bVreadings = 0;
 
77
uint8_t runningPID = 0;
 
78
int currentBrightness = 0;              // the current LED brightness out of 1023
 
79
 
 
80
// Flashlight brightness States
 
81
int brightnessValues[5] = {0,-1,45,182,852}; // Default - Modify if desired
 
82
 
 
83
uint8_t NUMSTATES = 5;        // Modify if desired
 
84
uint8_t currentState = 0;
 
85
uint8_t olderState = 128;     // Init with a big number at first, will be updated when program is run
 
86
uint8_t memory = 1;           // If memory mode is on or not. 1 = ON by default
 
87
uint8_t runningCandlelight;
 
88
// runningCandlelight is set to 0 during state changes so we can get out of candlelightmode.
 
89
 
 
90
// Variables for Mode Memory and OTC
 
91
 
 
92
volatile int eepos = 0;
 
93
volatile int oldpos;
 
94
 
 
95
#define EEPLEN          255   // Modify this depending on micro used, some only have 128 bytes
 
96
#define CAP_SHORT       250   // Short Press  (modify depending on OTC cap), ADC val out of 1024
 
97
#define CAP_LONG        100   // Medium Press (modify depending on OTC cap), functionality not used yet..
 
98
 
 
99
// Define Low Voltage Values
 
100
// Battery is read across 1kR low across 5.7kR network at VCC=2.5V.
 
101
 
 
102
#define LOW_BATTERY       210   // ~2.93V
 
103
#define CRITICAL_BATTERY  193   // ~2.69V
 
104
 
 
105
// Define TMP103 Values
 
106
 
 
107
#define TMP103G 0x76
 
108
#define TEMPERATURE_REGISTER 0x00
 
109
#define MAX_TEMPERATURE 128
 
110
#define MIN_TEMPERATURE -55 
 
111
 
 
112
double SET_TEMPERATURE = 60;    // PID will regulate at this temperature
 
113
int CRITICAL_TEMPERATURE = 90;  // If temp exceeds this, shut down flashlight
 
114
 
 
115
 
 
116
// Define PID Variables
 
117
 
 
118
double Setpoint, Input, Output, OldOutput;
 
119
double Kp=1, Ki=1, Kd=1;
 
120
PID tempPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
 
121
#define PID_LOW_LIMIT 60                // Adjust to prevent too much undershoot
 
122
 
 
123
// #######################################
 
124
// ##     METHODS FOR EEPROM MEMORY     ##
 
125
// #######################################
 
126
 
 
127
// Wear leveling adapted from JCapSolutions STAR_off_time.c
 
128
// Note - be sure to clear EEPROM before using
 
129
 
 
130
void save_state(){
 
131
  uint8_t eep;
 
132
  oldpos = eepos;
 
133
  eepos = (eepos+1);
 
134
  if (eepos > EEPLEN){
 
135
    eepos = 0;
 
136
  }
 
137
  eep = currentState;
 
138
  EEPROM.write(eepos,eep);
 
139
  EEPROM.write(oldpos,0xff);
 
140
}
 
141
 
 
142
void restore_state(){
 
143
  // Find the saved state from the EEPROM and restore
 
144
  uint8_t eep;
 
145
  for (int p=0; p<EEPLEN; p++){
 
146
    eep = EEPROM.read(p);
 
147
    if (eep!=0xff){
 
148
      eepos = p;
 
149
      currentState = eep;
 
150
      break;
 
151
    }
 
152
  }
 
153
  if (currentState >= NUMSTATES){
 
154
    currentState = 0;
 
155
  }
 
156
}
 
157
 
 
158
void next_state(){
 
159
  bVreadings = 0;
 
160
  currentState += 1;
 
161
  if (currentState >= NUMSTATES){
 
162
    currentState = 0;
 
163
  }
 
164
  save_state();
 
165
}
 
166
 
 
167
void drop_state(){
 
168
  bVreadings = 0;
 
169
  currentState -= 1;
 
170
  if (currentState<0){
 
171
    currentState = 0;
 
172
  }
 
173
  save_state();
 
174
}
 
175
 
 
176
// #######################################
 
177
// ##      METHODS FOR TMP103 READ      ##
 
178
// #######################################
 
179
 
 
180
uint8_t read_register(uint8_t reg){
 
181
  uint8_t returned_data;
 
182
  i2c_start_wait((TMP103G<<1)|I2C_WRITE);
 
183
  i2c_write(reg);
 
184
  i2c_rep_start((TMP103G<<1)|I2C_READ);
 
185
  returned_data = i2c_read(true); 
 
186
  i2c_stop(); 
 
187
  return returned_data;
 
188
}
 
189
 
 
190
int getTemperature(){
 
191
  // Reads TMP103 and returns temperature in celsius
 
192
  int temp = read_register(TEMPERATURE_REGISTER);           
 
193
  temp = constrain(temp, MIN_TEMPERATURE, MAX_TEMPERATURE);
 
194
  return temp;
 
195
}
 
196
 
 
197
// #######################################
 
198
// ##         GENERAL METHODS           ##
 
199
// #######################################
 
200
 
 
201
int doubleAnalogRead(uint8_t pin){
 
202
  int adcValue = analogRead(pin);
 
203
  adcValue = analogRead(pin);
 
204
  return adcValue;
 
205
}
 
206
 
 
207
void runCandlelight(){
 
208
  // Candlelight mode - needs a lot of work.., currently not good!
 
209
  runningCandlelight = 1;
 
210
  while (runningCandlelight == 1){
 
211
    checkBattery();
 
212
    checkCriticalTemperature();
 
213
    pwmPinWrite((4+random(2)-random(2)));
 
214
    delay(random(70)+random(50)+random(20));
 
215
  }
 
216
}
 
217
 
 
218
void checkBattery(){
 
219
  /*
 
220
  checkBattery() reads the battery voltage a few times:
 
221
  If it's low for a few times (to smooth readings
 
222
    e.g. bVreadings times in a row) it then drops state.
 
223
  If the state is 0, it checks for critical battery instead.
 
224
  If it's below critical battery, turn light off.
 
225
  */
 
226
 
 
227
  int bV = doubleAnalogRead(Pin_Batt);
 
228
  if (currentState == 0){
 
229
    // Lowest state already, monitor for critical battery
 
230
    if (bV < CRITICAL_BATTERY){
 
231
      bVreadings++;
 
232
    }
 
233
    else{
 
234
      bVreadings = 0;
 
235
    }
 
236
    if (bVreadings >= 10){
 
237
      // Battery is Critically low
 
238
      ErrorFlashBatteryCritical();
 
239
    }
 
240
  }
 
241
  else{
 
242
    // Current state is > 0, check for low battery
 
243
    if (bV < LOW_BATTERY){
 
244
      bVreadings++;
 
245
    }
 
246
    else{
 
247
      bVreadings = 0;
 
248
    }
 
249
    if (bVreadings>=10){
 
250
      // Battery is low, drop state by 1 and flash error message
 
251
      runningCandlelight = 0;
 
252
      ErrorFlashBatteryLow();
 
253
      drop_state();
 
254
    }
 
255
  }
 
256
}
 
257
 
 
258
// #######################################
 
259
// ##       ERROR HANDLING              ##
 
260
// #######################################
 
261
 
 
262
void checkCriticalTemperature(){
 
263
  // Check if temperature is Critical. If it is, turn off.
 
264
  int temp = getTemperature();
 
265
    if (temp>=CRITICAL_TEMPERATURE){
 
266
    ErrorFlashCriticalTemperature();
 
267
  }
 
268
}
 
269
 
 
270
// Method for flashing certain number of times
 
271
void flash(uint8_t flashes){
 
272
  uint8_t i = 0;
 
273
  rampFromValToOff(currentBrightness,2);
 
274
  digitalWrite(Pin_Enable,LOW);
 
275
  delay(200);
 
276
  pwmPinWrite(2);
 
277
 
 
278
  while (i<flashes){
 
279
    digitalWrite(Pin_Enable,HIGH);
 
280
    delay(233);
 
281
    digitalWrite(Pin_Enable,LOW);
 
282
    delay(300);
 
283
    i++;
 
284
  }
 
285
}
 
286
 
 
287
void ErrorFlashBatteryLow(){
 
288
  flash(3);
 
289
}
 
290
 
 
291
void ErrorFlashBatteryCritical(){
 
292
  // Critical Battery
 
293
  // Turns off LED completely, requires hard turn-off and on for safety.
 
294
  flash(5);
 
295
  currentState = 0;
 
296
  save_state();
 
297
  while (1){
 
298
    // loop forever
 
299
  }
 
300
}
 
301
 
 
302
void ErrorFlashCriticalTemperature(){
 
303
  // Critical Temperature
 
304
  // Turns off LED completely, requires hard turn-off and on for safety.
 
305
  flash(4);
 
306
  currentState = 0;
 
307
  save_state();
 
308
  while (1){
 
309
    // loop forever
 
310
  }
 
311
}
 
312
 
 
313
// #########################################
 
314
// ## METHODS FOR CHANGING LED BRIGHTNESS ##
 
315
// #########################################
 
316
 
 
317
void pwmPinWrite(uint16_t val){
 
318
  // 10 bit brightness value
 
319
  if (val <=0){
 
320
    digitalWrite(Pin_PWM,LOW);
 
321
  }
 
322
  if (val >= 1023){
 
323
    digitalWrite(Pin_PWM,HIGH);
 
324
  }
 
325
  TCCR2A &= ~(1<<COM2B0);
 
326
  TCCR2A |= (1<<COM2B1);
 
327
  OCR2B = val;
 
328
  currentBrightness = val;
 
329
}
 
330
 
 
331
void rampFromValToVal(int fval, int tval, int speed){
 
332
  // speed = 1 = fastest
 
333
  int i = fval;
 
334
  if (fval<tval){
 
335
    while (i < tval){
 
336
      i+=1;
 
337
      pwmPinWrite(i);
 
338
      delay(speed);
 
339
    }
 
340
  }
 
341
  else {
 
342
    while (i>tval){
 
343
      i-=1;
 
344
      pwmPinWrite(i);
 
345
      delay(speed);
 
346
    }
 
347
  }
 
348
  pwmPinWrite(tval);
 
349
}
 
350
 
 
351
void rampToVal(int pwm, int speed){
 
352
  // speed = 1 = fastest
 
353
  int i= 0;
 
354
  if (pwm<0){
 
355
    pwm = 0;  // handles cases like candlelight mode etc
 
356
  }
 
357
  digitalWrite(Pin_Enable,HIGH);
 
358
  while (i<(pwm-5)){
 
359
      i+=5;
 
360
      pwmPinWrite(i);
 
361
      delay(speed);
 
362
    }
 
363
  pwmPinWrite(pwm);
 
364
}
 
365
 
 
366
void rampFromValToOff(int pwm, int speed){
 
367
  // speed = 1 = fastest
 
368
  int i = pwm;
 
369
  while (i>0){
 
370
      i-=speed;
 
371
      pwmPinWrite(i);
 
372
      delay(speed);
 
373
    }
 
374
  digitalWrite(Pin_Enable,LOW);
 
375
}
 
376
 
 
377
void runLED(byte s){
 
378
  // run the LED
 
379
  int newBrightness = brightnessValues[s];
 
380
 
 
381
  // Initialize with lowest brightness
 
382
  pwmPinWrite(0);
 
383
  digitalWrite(Pin_Enable,HIGH);
 
384
 
 
385
  if (newBrightness == -1){
 
386
    runCandlelight();
 
387
  }
 
388
 
 
389
  // if current is greater than ~500mA, enable PID
 
390
  if (newBrightness > 70){
 
391
    tempPID.SetOutputLimits(PID_LOW_LIMIT,(double)newBrightness);
 
392
    Output = (double)newBrightness;
 
393
    tempPID.Initialize();
 
394
    runningPID = 1;
 
395
  }
 
396
 
 
397
  // If battery voltage is below 3.7V, do not run mode above ~3 Amps (514/1023)
 
398
  // Flash error message and drop one state to alert user
 
399
 
 
400
  int bV = doubleAnalogRead(Pin_Batt);
 
401
  if (bV < 265 && newBrightness > 514){
 
402
    drop_state();
 
403
    ErrorFlashBatteryLow();
 
404
  }
 
405
 
 
406
  // Else all is ok
 
407
  else{
 
408
    rampToVal(newBrightness,8);
 
409
  }
 
410
}
 
411
 
 
412
 
 
413
void handleModeGroups(){
 
414
 
 
415
  // Allows us to set some mode groups with X1 and X2 jumpers
 
416
  // Default unbriged = HIGH (input pullups)
 
417
  /*
 
418
  PB0 PB1
 
419
  X1  X2   Modes (current in mA)              Set Temp   Crit Temp (deg C)
 
420
  1   1    Low   Candle  250   1000   5000    60C        90C      (default mode)
 
421
  1   0    Low   50      250   1500           60C        90C
 
422
  0   1    Low   50      250   1000   4200    60C        90C
 
423
  0   0    Low   50      250   1000   5500    60C        90C        
 
424
  Increase the current at your own risk ;)...
 
425
  */
 
426
 
 
427
  int x1 = digitalRead(Pin_PB0);
 
428
  int x2 = digitalRead(Pin_PB1);
 
429
 
 
430
  if(x1 == HIGH && x2 == LOW){
 
431
    NUMSTATES = 4;
 
432
    brightnessValues[1] = 8;
 
433
    //brightnessValues[2] = 45;
 
434
    brightnessValues[3] = 275;
 
435
    if (currentState>=4){
 
436
      currentState = 0;
 
437
    }
 
438
  } 
 
439
  else if(x1 == LOW && x2 == HIGH){
 
440
    brightnessValues[1] = 8;
 
441
    brightnessValues[4] = 726;
 
442
  }
 
443
  else if(x1 == LOW && x2 == LOW){
 
444
    brightnessValues[1] = 8;
 
445
    brightnessValues[4] = 938;
 
446
  }
 
447
}
 
448
 
 
449
// #######################################
 
450
// ##           INITILIZATION           ##
 
451
// #######################################
 
452
 
 
453
void setup(){
 
454
  analogReference(DEFAULT);
 
455
  pinMode(Pin_LedDbg,OUTPUT);
 
456
  pinMode(Pin_OTCD,INPUT);
 
457
  
 
458
  // First up let's read ADC Value on OTC
 
459
  int otc_val = doubleAnalogRead(Pin_OTCA);
 
460
 
 
461
  // Next restore state and take action based on OTC reading
 
462
  // Long Press not implemented yet.. TODO
 
463
  restore_state();
 
464
  if(otc_val>CAP_SHORT){
 
465
    next_state();
 
466
  }
 
467
  else{
 
468
    // Long press, or reset to the first mode
 
469
    if (!memory){
 
470
      currentState = 0;
 
471
    }
 
472
  }
 
473
 
 
474
  // Now Setup Pins
 
475
  pinMode(Pin_PWM,OUTPUT);
 
476
  pinMode(Pin_Enable,OUTPUT);
 
477
  pinMode(Pin_OTCD, OUTPUT);
 
478
  pinMode(Pin_PB0,INPUT_PULLUP);
 
479
  pinMode(Pin_PB1,INPUT_PULLUP);
 
480
  pinMode(Pin_Batt,INPUT);
 
481
  
 
482
  digitalWrite(Pin_OTCD,HIGH);
 
483
  digitalWrite(Pin_Enable,LOW);
 
484
  digitalWrite(Pin_PWM,LOW);
 
485
 
 
486
  handleModeGroups();
 
487
 
 
488
  save_state();
 
489
 
 
490
  // 7.8kHz 10bit PWM setup
 
491
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;
 
492
  TCCR2B &= ~(1 << WGM23);  // Timer B clear bit
 
493
  TCCR2B |=  (1 << WGM22);  // set bit
 
494
  TCCR2A |= (1 << WGM21);   //  Timer A set bit
 
495
  TCCR2A |= (1 << WGM20);   //  set bit
 
496
 
 
497
  // If I2C does not initialize, flash error message
 
498
  if (!i2c_init()) ErrorFlashCriticalTemperature();
 
499
 
 
500
  // Initialize PID
 
501
  Input = getTemperature();   // Initialize starting value to be starting Temperature instead of 0
 
502
  Setpoint = SET_TEMPERATURE; // Set our PID to be the desired Set Temperature
 
503
  tempPID.SetMode(AUTOMATIC);
 
504
 
 
505
}
 
506
 
 
507
// #######################################
 
508
// ##      MAIN APPLICATION LOOP        ##
 
509
// #######################################
 
510
 
 
511
void loop(){
 
512
 
 
513
  // Run LED depending on state
 
514
  if (currentState!=olderState){
 
515
    olderState = currentState; 
 
516
    runLED(currentState);   
 
517
  }
 
518
 
 
519
  // Now compute PID or run regular error handling
 
520
 
 
521
  if (runningPID == 1){
 
522
    checkCriticalTemperature();
 
523
    checkBattery();
 
524
    delay(25);
 
525
    Input = getTemperature();
 
526
    tempPID.Compute();
 
527
    pwmPinWrite(Output);
 
528
  }
 
529
 
 
530
  else{
 
531
    // not running PID; light is on lower power levels
 
532
    checkCriticalTemperature();
 
533
    delay(25);
 
534
    checkBattery();
 
535
    // Delay a little since we don't need to keep doing readings
 
536
    delay(425); 
 
537
  }
 
538
}