380 lines
10 KiB
C++
380 lines
10 KiB
C++
#include <string.h>
|
|
#include <Arduino.h>
|
|
#include <SPI.h>
|
|
#include <Adafruit_NeoPixel.h>
|
|
#include "Adafruit_BluefruitLE_SPI.h"
|
|
|
|
#if SOFTWARE_SERIAL_AVAILABLE
|
|
#include <SoftwareSerial.h>
|
|
#endif
|
|
|
|
#include "BluefruitConfig.h"
|
|
|
|
const uint8_t PROGMEM gamma[] = GAMMA_CORRECTION;
|
|
|
|
#define REQUIRE_SERIAL false
|
|
#define BT_SCAN_MS 50
|
|
#define MIN_FIRMWARE "0.7.0"
|
|
#define FACTORYRESET true
|
|
|
|
#define SHIFTING_STOPPED 0
|
|
#define SHIFTING_FORWARD 1
|
|
#define SHIFTING_REVERSE 2
|
|
|
|
#define PATTERN_OFF 0
|
|
#define PATTERN_RAINBOW 1
|
|
#define PATTERN_RED 2
|
|
|
|
// Updated with bluetooth connection state
|
|
volatile bool conn = false;
|
|
|
|
const char msg_pattern_off[] PROGMEM = "pattern off";
|
|
const char msg_pattern_rainbow[] PROGMEM = "pattern rainbow";
|
|
const char msg_pattern_invalid[] PROGMEM = "pattern invalid";
|
|
const char msg_speed_out_of_range[] PROGMEM = "out of range (0,200)";
|
|
const char msg_speed_changed[] PROGMEM = "speed changed";
|
|
|
|
// Interrupts are needed to TX without deadlocking, so instead of doing it
|
|
// inside another interrupt, save the MSG_ ID here for loop() to do it
|
|
volatile int to_rx = NULL;
|
|
|
|
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
|
|
|
|
void bluetooth_setup(void){
|
|
if ( !ble.begin(VERBOSE_MODE) ) {
|
|
Serial.println(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
|
|
while(1); // halt
|
|
}
|
|
if ( FACTORYRESET ) {
|
|
if ( ! ble.factoryReset() ){
|
|
Serial.println(F("Couldn't factory reset"));
|
|
while(1); // halt
|
|
}
|
|
}
|
|
if ( !ble.isVersionAtLeast(MIN_FIRMWARE) ){
|
|
Serial.print(F("Callback requires at least"));
|
|
Serial.println(F(MIN_FIRMWARE));
|
|
while(1); // halt
|
|
}
|
|
pinMode(BLUEFRUIT_SPI_IRQ, INPUT_PULLUP);
|
|
//ble.echo(false); // disable command echo
|
|
//ble.verbose(false); // disable debug info
|
|
ble.sendCommandCheckOK(F("AT+GAPDEVNAME=Suit LEDs")); // Change name displayed in bluetooth scans
|
|
ble.sendCommandCheckOK(F("AT+HWMODELED=2")); // Show TX/RX activity on LED
|
|
|
|
//ble.info();
|
|
ble.setConnectCallback(on_connect);
|
|
ble.setDisconnectCallback(on_disconnect);
|
|
ble.setBleUartRxCallback(BleUartRX);
|
|
ble.setMode(BLUEFRUIT_MODE_DATA);
|
|
}
|
|
|
|
void on_connect(void){
|
|
// Status light will go solid blue because of AT+HWMODELED=2
|
|
Serial.println(F("Bluetooth Connected"));
|
|
conn = true;
|
|
}
|
|
void on_disconnect(void){
|
|
// Blue status light will shut off because of AT+HWMODELED=2
|
|
Serial.println(F("Bluetooth Disconnected"));
|
|
conn = false;
|
|
}
|
|
|
|
void BleUartRX(char payload[], uint16_t payload_len){
|
|
// Red status light should flicker because of AT+HWMODELED=2
|
|
if (payload_len < 3){
|
|
Serial.print("packet length ");
|
|
Serial.print(payload_len);
|
|
Serial.println(" is too short");
|
|
return;
|
|
}
|
|
// first byte is the command
|
|
uint8_t cmd_byte = payload[0];
|
|
// last byte is the CRC
|
|
uint8_t crc_byte = payload[payload_len-1];
|
|
// all other bytes are the data for that command
|
|
uint16_t data_len = payload_len-2;
|
|
uint8_t data[data_len];
|
|
uint16_t payload_index;
|
|
uint16_t data_index;
|
|
for (payload_index=1, data_index=0; payload_index<payload_len-1; payload_index++, data_index++){
|
|
data[data_index] = payload[payload_index];
|
|
}
|
|
// Print parts
|
|
Serial.print(F("[RX] cmd=" ));
|
|
Serial.print((char)cmd_byte);
|
|
Serial.print(F(" crc="));
|
|
Serial.print(crc_byte);
|
|
Serial.print(F(" data="));
|
|
Serial.write(data, data_len);
|
|
Serial.println();
|
|
// check CRC
|
|
uint8_t crc = 0;
|
|
for (uint16_t i=0; i<payload_len-1; i++){
|
|
crc += payload[i];
|
|
}
|
|
crc = ~crc;
|
|
if (crc_byte != crc){
|
|
Serial.print("CRC mismatch; expected ");
|
|
Serial.println(crc);
|
|
return;
|
|
}
|
|
// run whichever command was requested
|
|
if (cmd_byte == 's'){
|
|
set_wait(data, data_len);
|
|
} else if (cmd_byte == 'p') {
|
|
set_pattern(data, data_len);
|
|
} else if (cmd_byte == 'a') {
|
|
set_amplitude(data, data_len);
|
|
} else {
|
|
Serial.println(F("Unrecognized cmd"));
|
|
}
|
|
}
|
|
|
|
class Strip {
|
|
private:
|
|
uint8_t pin;
|
|
uint8_t num_pixels;
|
|
unsigned long last;
|
|
uint8_t offset = 0;
|
|
uint8_t shifting = SHIFTING_REVERSE;
|
|
|
|
public:
|
|
Adafruit_NeoPixel pixel;
|
|
uint16_t wait = 0;
|
|
uint8_t dim = 1;
|
|
char pattern = PATTERN_RAINBOW;
|
|
uint8_t amplitude = 0;
|
|
|
|
Strip(uint8_t led_pin, uint8_t strip_len) {
|
|
pin = led_pin;
|
|
num_pixels = strip_len;
|
|
last = millis();
|
|
pixel = Adafruit_NeoPixel(num_pixels, pin, NEO_GRBW + NEO_KHZ800);
|
|
pixel.begin();
|
|
}
|
|
|
|
void set_shift_direction(uint8_t new_shift){
|
|
if (new_shift == SHIFTING_FORWARD || new_shift == SHIFTING_REVERSE){
|
|
offset = num_pixels - offset - 1;
|
|
} else {
|
|
offset = 0;
|
|
}
|
|
shifting = new_shift;
|
|
}
|
|
|
|
void update(void){
|
|
unsigned long now = millis();
|
|
if (now - wait > last){
|
|
last = now;
|
|
add_offset();
|
|
}
|
|
if (pattern == PATTERN_RAINBOW){
|
|
rainbow();
|
|
} else if (pattern == PATTERN_RED){
|
|
red();
|
|
} else {
|
|
off();
|
|
}
|
|
pixel.show();
|
|
}
|
|
|
|
private:
|
|
void off(void){
|
|
for(uint8_t i=0; i<num_pixels; i++){
|
|
pixel.setPixelColor(i, pixel.Color(0,0,0));
|
|
}
|
|
}
|
|
void add_offset(void){
|
|
/* Increments the offset counter. See get_offset_loc() */
|
|
offset++;
|
|
if (offset == num_pixels){
|
|
offset = 0;
|
|
}
|
|
}
|
|
uint8_t get_offset_loc(uint8_t index){
|
|
/* index is the location on the strip where a pixel would be if offset
|
|
* was 0. Offset will be from 0 to num_pixels-1 */
|
|
if (offset == 0){
|
|
return index;
|
|
}
|
|
if (shifting == SHIFTING_FORWARD){
|
|
uint16_t loc = offset + index;
|
|
if (loc >= num_pixels){
|
|
loc -= num_pixels;
|
|
}
|
|
return (uint8_t)loc;
|
|
} else if (shifting == SHIFTING_REVERSE){
|
|
uint16_t loc = num_pixels - offset + index;
|
|
if (loc >= num_pixels){
|
|
loc -= num_pixels;
|
|
}
|
|
return (uint8_t)loc;
|
|
}
|
|
return index; // SHIFTING_STOPPED
|
|
}
|
|
void set_pixel_rgb(uint8_t index, uint8_t rgb[]){
|
|
set_pixel_rgb(index, rgb[0], rgb[1], rgb[2]);
|
|
}
|
|
void set_pixel_rgb(uint8_t index, uint8_t red, uint8_t green, uint8_t blue){
|
|
pixel.setPixelColor(
|
|
get_offset_loc(index),
|
|
pixel.Color(
|
|
pgm_read_byte(&gamma[red / dim]),
|
|
pgm_read_byte(&gamma[green / dim]),
|
|
pgm_read_byte(&gamma[blue / dim])
|
|
)
|
|
);
|
|
}
|
|
void set_pixel_rgbw(uint8_t index, uint8_t rgb[]){
|
|
set_pixel_rgbw(index, rgb[0], rgb[1], rgb[2], rgb[3]);
|
|
}
|
|
void set_pixel_rgbw(uint8_t index, uint8_t red, uint8_t green, uint8_t blue, uint8_t white){
|
|
pixel.setPixelColor(
|
|
get_offset_loc(index),
|
|
pixel.Color(
|
|
pgm_read_byte(&gamma[red / dim]),
|
|
pgm_read_byte(&gamma[green / dim]),
|
|
pgm_read_byte(&gamma[blue / dim]),
|
|
pgm_read_byte(&gamma[white / dim])
|
|
)
|
|
);
|
|
}
|
|
void rainbow(void){
|
|
uint8_t rgb[] = {255, 0, 0}; // starting RGB value
|
|
uint8_t phase_len = num_pixels / 6; // how long each rainbow phase is
|
|
uint8_t bump = 255 / phase_len; // how much to increase between pixels
|
|
set_pixel_rgb(0, rgb); // first pixel
|
|
for (uint8_t index=1; index < num_pixels; index++){
|
|
uint8_t phase = index / phase_len; // which section we're in
|
|
if (phase % 2 == 0){ // even-index phases add; odd-indexed subtracts
|
|
// (r,g,b) index for each phase in order is 1,0,2,1,0,
|
|
rgb[(6 - phase + 1) % 3] += bump;
|
|
} else {
|
|
rgb[(6 - phase + 1) % 3] -= bump;
|
|
}
|
|
if (index % phase_len == 0) { // this is the transition between phases
|
|
// max out the change from the previous phase
|
|
if (phase % 2 == 0){
|
|
rgb[(6 - phase + 2) % 3] = 0;
|
|
} else {
|
|
rgb[(6 - phase + 2) % 3] = 255;
|
|
}
|
|
}
|
|
set_pixel_rgb(index, rgb);
|
|
}
|
|
}
|
|
void red(void){
|
|
float bump = 255.0 / (num_pixels / 2 + 1);
|
|
uint8_t half_point = num_pixels / 2 + 1;
|
|
float red = 0;
|
|
set_pixel_rgb(0, 1, 0, 0);
|
|
//set_pixel_rgbw(0, 0, 0, 0, 255);
|
|
for (uint8_t index=1; index < num_pixels; index++){
|
|
if (index < half_point){
|
|
red += bump;
|
|
} else {
|
|
red -= bump;
|
|
}
|
|
uint8_t red_int = (uint8_t)red;
|
|
//uint8_t white_int = (255 - red_int + 1) / 2;
|
|
set_pixel_rgb(index, red_int, 0, 0);
|
|
//set_pixel_rgbw(index, red_int, 0, 0, white_int);
|
|
}
|
|
}
|
|
};
|
|
|
|
volatile Strip strip1 = Strip(5, 144);
|
|
volatile Strip strip2 = Strip(6, 144);
|
|
volatile Strip strip3 = Strip(11, 24);
|
|
volatile Strip strip4 = Strip(13, 144);
|
|
|
|
void set_wait(char data[], uint16_t len){
|
|
String wait = "";
|
|
for (uint16_t index=0; index<len; index++){
|
|
wait += (char)data[index];
|
|
}
|
|
uint16_t wait_int = wait.toInt();
|
|
if (wait_int >= 0 && wait_int <= 200){
|
|
strip1.wait = wait_int;
|
|
strip2.wait = wait_int;
|
|
to_rx = msg_speed_changed;
|
|
} else {
|
|
to_rx = msg_speed_out_of_range;
|
|
}
|
|
}
|
|
|
|
void set_pattern(char data[], uint16_t len){
|
|
if (len != 1){
|
|
to_rx = msg_pattern_invalid;
|
|
} else if (data[0] == 'r'){
|
|
strip1.pattern = PATTERN_RED;
|
|
strip2.pattern = PATTERN_RED;
|
|
to_rx = msg_pattern_rainbow;
|
|
} else if (data[0] == 'o') {
|
|
strip1.pattern = PATTERN_OFF;
|
|
strip2.pattern = PATTERN_OFF;
|
|
to_rx = msg_pattern_off;
|
|
} else {
|
|
to_rx = msg_pattern_invalid;
|
|
}
|
|
}
|
|
|
|
void set_amplitude(char data[], uint16_t len){
|
|
if (len == 1){
|
|
strip1.amplitude = data[0];
|
|
strip2.amplitude = data[0];
|
|
//Serial.println((uint8_t)data[0]);
|
|
Serial.println(255 - (uint8_t)data[0]);
|
|
} else {
|
|
Serial.println(F("invalid len for amplitude"));
|
|
}
|
|
}
|
|
|
|
void do_rx_send(void){
|
|
/* if to_rx is set to a message, try to send it over bluetooth */
|
|
if (to_rx == NULL){
|
|
return;
|
|
}
|
|
char* msg = flash_msg(to_rx);
|
|
to_rx = NULL;
|
|
if (conn){
|
|
Serial.print(F("[TX] "));
|
|
Serial.println(msg);
|
|
ble.write(msg);
|
|
} else {
|
|
Serial.print(F("Disconnected. Couldn't TX: "));
|
|
Serial.println(msg);
|
|
}
|
|
free(msg);
|
|
}
|
|
|
|
char * flash_msg(const char* addr){
|
|
/* return a char* array from flash memory; be sure to free() it after */
|
|
uint8_t buff_size = strlen_P(addr);
|
|
char * buffer = (char *) malloc (buff_size);
|
|
for (uint8_t index=0; index < buff_size; index++){
|
|
buffer[index] = pgm_read_byte_near(addr + index);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
void setup(void) {
|
|
if (REQUIRE_SERIAL){
|
|
while (!Serial);
|
|
Serial.begin(115200);
|
|
}
|
|
bluetooth_setup();
|
|
strip3.wait = 30;
|
|
Serial.println(F("Ready"));
|
|
}
|
|
|
|
void loop(void) {
|
|
ble.update(BT_SCAN_MS);
|
|
do_rx_send();
|
|
strip1.update();
|
|
strip2.update();
|
|
strip3.update();
|
|
strip4.update();
|
|
}
|