float avg_b = ((float)b*ratio )+ ((float)b2*(1-ratio)); float avg_g = ((float)g*ratio )+ ((float)g2*(1-ratio)); float avg_r = ((float)p*ratio )+ ((float)p2*(1-ratio));the floating point variable 'ratio' controls the weight of each pixel. Notice that ratio is always between 0 and 1. So if ratio is 0.3 then 1-ratio = 0.7 and we will take 70% of the second pixel and 30% of the first. The code below is for the Joy Gamer so you will need to download the necessary libraries available at our basic Joy Gamer tutorial .
/* LucidTronix Joy Gamer SD Navigator * If the compiler gives you an error about sketch being too big make sure to * COMMENT OUT both the USE_ACCELEROMETER and the USE COLOR defines in the JoyGamer.h file. * They should look like this: //#define USE_ACCELEROMETER //#define USE_COLOR * For more instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/27 */#include#include // Core graphics library#include // Hardware-specific library#include #include Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);File bmpFile1;File bmpFile2;// information we extract about the bitmap fileint bmpWidth, bmpHeight;int bmpWidth2, bmpHeight2;uint8_t bmpDepth, bmpImageoffset;uint8_t bmpDepth2, bmpImageoffset2;float ratio = 0.5;int img_index1 = 12;int img_index2 = 16;int num_images = 60;void setup(void) { jg.initialize(); jg.start_sd_card(); bmp_load_and_draw_2_images("face36.bmp","face34.bmp");}void loop() { bmpdraw_2_images();}void bmp_load_and_draw_2_images(char* filename1, char* filename2) { bmpFile1 = SD.open(filename1); bmpFile2 = SD.open(filename2); if (!bmpFile1) { jg.draw_string(12,62, "Coodn't find image", ST7735_RED); } if (!bmpReadHeader(bmpFile1,0)) { jg.draw_string(12,82, "Bad image" , ST7735_RED); } if (!bmpFile2) { jg.draw_string(12,62, "Coodn't find image 2" , ST7735_RED); } if (!bmpReadHeader(bmpFile2,1)) { jg.draw_string(12,82, "Bad image 2" , ST7735_RED); } bmpdraw_2_images();}/*********************************************/// This procedure reads 2 bitmaps and draws an average to the screen// its sped up by reading many pixels worth of data at a time// instead of just one pixel at a time. increading the buffer takes// more RAM but makes the drawing a little faster. 20 pixels' worth// is probably a good place#define BUFFPIXEL 20void bmpdraw_2_images() { bmpFile1.seek(bmpImageoffset); bmpFile2.seek(bmpImageoffset2); uint32_t time = millis(); uint16_t p, p2; uint8_t g, b; uint8_t g2, b2; int i, j; unsigned int file_pos = bmpImageoffset; uint8_t sdbuffer[3 * BUFFPIXEL]; // 3 * pixels to buffer uint8_t buffidx = 3*BUFFPIXEL; uint8_t sdbuffer2[3 * BUFFPIXEL]; // 3 * pixels to buffer //set up the 'display window' tft.setAddrWindow(0, 0, bmpWidth-1, bmpHeight-1); for (i=0; i< bmpHeight; i++) { // bitmaps are stored with the BOTTOM line first so we have to move 'up' for (j=0; j = 3*BUFFPIXEL) { bmpFile1.read(sdbuffer, 3*BUFFPIXEL); bmpFile2.read(sdbuffer2, 3*BUFFPIXEL); buffidx = 0; } // convert pixel from 888 to 565 b = sdbuffer[buffidx++]; // blue g = sdbuffer[buffidx++]; // green p = sdbuffer[buffidx++]; // red // convert pixel from 888 to 565 buffidx -= 3; b2 = sdbuffer2[buffidx++]; // blue g2 = sdbuffer2[buffidx++]; // green p2 = sdbuffer2[buffidx++]; // red file_pos += 3; float avg_b = ((float)b*ratio )+ ((float)b2*(1-ratio)); float avg_g = ((float)g*ratio )+ ((float)g2*(1-ratio)); float avg_r = ((float)p*ratio )+ ((float)p2*(1-ratio)); b = (int) avg_r; g = (int) avg_g; p = (int) avg_b; p >>= 3; p <<= 6; g >>= 2; p |= g; p <<= 5; b >>= 3; p |= b; tft.drawPixel(j, bmpHeight - i, p); } if( digitalRead(jg.joystick_btn_pin) == HIGH){ reset_image(&img_index1, &bmpFile1, file_pos); } if(digitalRead(jg.btn_pin) == LOW) { reset_image(&img_index2, &bmpFile2, file_pos); } float cur_val = analogRead(0); ratio = cur_val / 1024.0; }}void reset_image(int *index, File *a_file, int file_pos){ *index = ++*index % num_images; a_file->close(); String img2 = "face"; img2 += String(*index); img2 += ".bmp"; char img2c[11] ; for(int i = 0; i < 10; i++){ img2c[i] = img2.charAt(i); } if (*index < 10 ) { for(int i = 10; i >= 5; i--){ img2c[i] = img2c[i-1]; } img2c[4] = '0'; } img2c[10] = 0; *a_file = SD.open(img2c); a_file->seek(file_pos); }boolean bmpReadHeader(File f, int mode) { // read header uint32_t tmp; if (read16(f) != 0x4D42) { // magic bytes missing return false; } // read file size tmp = read32(f); //Serial.print("size 0x"); Serial.println(tmp, HEX); // read and ignore creator bytes read32(f); if (mode == 0 ) bmpImageoffset = read32(f); else if (mode == 1 ) bmpImageoffset2 = read32(f); //Serial.print("offset "); Serial.println(bmpImageoffset, DEC); // read DIB header tmp = read32(f); //Serial.print("header size "); Serial.println(tmp, DEC); if (mode == 0 ) { bmpWidth = read32(f); bmpHeight = read32(f); } else if (mode == 1 ) { bmpWidth2 = read32(f); bmpHeight2 = read32(f); } if (read16(f) != 1) return false; if (mode == 0 ) bmpDepth = read16(f); else if (mode == 1 ) bmpDepth2 = read16(f); //Serial.print("bitdepth "); Serial.println(bmpDepth, DEC); if (read32(f) != 0) { // compression not supported! return false; } return true;}/*********************************************/// These read data from the SD card file and convert them to big endian // (the data is stored in little endian format!)// LITTLE ENDIAN!uint16_t read16(File f) { uint16_t d; uint8_t b; b = f.read(); d = f.read(); d <<= 8; d |= b; return d;}// LITTLE ENDIAN!uint32_t read32(File f) { uint32_t d; uint16_t b; b = read16(f); d = read16(f); d <<= 16; d |= b; return d;}
if (millis() - last_update > 1000){ last_update = millis(); stime = " "; byte a_hour = clock.hour24(); String a_hour_s = String(a_hour); int hour_int = a_hour_s.toInt(); String ampm = "am"; if (!show_24_hours && hour_int > 12){ ampm = "pm"; hour_int -= 12; a_hour_s = String(hour_int); } if (a_hour == 0) stime += String(12); else stime += a_hour_s; stime += ":"; byte a_min = clock.minute(); if (a_min < 10) stime += String(0); stime += String(a_min); stime += ":"; byte a_sec = clock.second(); if (a_sec < 10) stime += String(0); stime += String(a_sec); if(!show_24_hours) stime += ampm; hm.set_message(stime); }
To talk to the RTC chip we use the standard Arduino wire library. You will also need libraries for the MCP79410, the Heart Matrix, and the MSTimer2 all downloadable below!
/* LucidTronix Heart Matrix. * A clock using the heart matrix's * on board MCP79410 real time clock chip * See the tutorial at: * http://lucidtronix.com/tutorials/36 */ #include#include #include #include String stime = "";unsigned long last_update = 0;boolean show_24_hours = false;HeartMatrix hm = HeartMatrix(5,6,7); // dataPin is on 5, Latch is on 6, and clock is on 7MCP79410 clock = MCP79410();void displayer2(){ hm.displayer();}void setup(){ Wire.begin(); //clock.setDateTime(__DATE__,__TIME__); MsTimer2::set(1,displayer2); MsTimer2::start(); pinMode(10, INPUT);} void loop(){ hm.on(); hm.set_scroll_wait(max(30, analogRead(0)/6)); if (millis() - last_update > 1000){ last_update = millis(); stime = " "; byte a_hour = clock.hour24(); String a_hour_s = String(a_hour); int hour_int = a_hour_s.toInt(); String ampm = "am"; if (!show_24_hours && hour_int > 12){ ampm = "pm"; hour_int -= 12; a_hour_s = String(hour_int); } if (a_hour == 0) stime += String(12); else stime += a_hour_s; stime += ":"; byte a_min = clock.minute(); if (a_min < 10) stime += String(0); stime += String(a_min); stime += ":"; byte a_sec = clock.second(); if (a_sec < 10) stime += String(0); stime += String(a_sec); if(!show_24_hours) stime += ampm; hm.set_message(stime); } if(digitalRead(10) == HIGH && millis() - last_update > 500){ last_update = millis(); show_24_hours = !show_24_hours; }}
/* LucidTronix write messages from * the serial port on the Heart Matrix. * For instructions details and schematic * http://www.lucidtronix.com/tutorials/32 * If debug is true outputs whatever was received * On serial1 to the standard USB serial. */ #include#include String message = " Hi ";bool debug = true;HeartMatrix hm = HeartMatrix(5,6,7); // dataPin is on 5, Latch is on 6, and clock is on 7void displayer2(){ hm.displayer();}void setup(){ if(debug) Serial.begin(9600); // initialize Serial Serial1.begin(9600); // initialize Serial1 MsTimer2::set(1,displayer2); MsTimer2::start(); hm.set_message(" Hello I'm the Heart Matrix from LucidTroniX! ");}void loop() { hm.on(); while (Serial1.available() ){ char c = Serial1.read(); if (c == '/') message = ""; else { String s = String(c); message += s; hm.set_message(message); } if(debug){ Serial.print("got the letter:"); Serial.println(c); } } }
const int buffer_size = 15; int noises[buffer_size]; const int num_cols = 10; int volumes[num_cols];The audio waveform is detected by keeping a buffer of sound values from the electret microphone. Every delay_speed we shift the buffer down and record a new sound. The volume or amplitude is detected by finding how much the noise buffer varies. We use the individual pixel access which our Heart Matrix library allows to display the waveform like a bar graph. The minimum and maximum variances are kept track of in the lines:
max_var = max(var, max_var); min_var = min(var, min_var);We map the sound level value to lie between 0 and 8 so that it will correspond to a row index in the LED Matrix.
var = map(var, min_var, max_var, 0, 9);Then we iterate over the buffer of volumes and light up the LEDs according to the sound value in the buffer. We go from left to right across the Heart Matrix so that the newest sounds are shown on the right and the oldest ones on the left. (The same direction you read in English).This is the code:
for (int i = 0 ; i < num_cols; i++){ for (int j = 0 ; j < volumes[i]; j++){ hm.set_pixel(i, j, true); } for (int j = volumes[i] ; j < 8; j++){ hm.set_pixel(i, j, false); } }
As in our
electret microphone breakout board tutorial
, we calculate the noise at any given moment in time by finding the variance of the sound buffer. Importantly, we use unsigned long integers for the variance because this number can get very big and overflow the size of a regular int data type.
/* LucidTronix Heart Matrix. * A volume visualizwer using the Heart Matrix's * on board LM386 amplifier chip and the electret microphone * See the tutorial at: * http://lucidtronix.com/tutorials/35 */ #include#include // dataPin is on 5, Latch is on 6, and clock is on 7HeartMatrix hm = HeartMatrix(5,6,7); // sound global variablesconst int buffer_size = 15;int noises[buffer_size];const int num_cols = 10;int volumes[num_cols];int cur_index = 0;unsigned long last_shift = 0;int mic_pin = 1;int delay_speed = 40;unsigned long max_var = 0;unsigned long min_var = 99999999;void setup() { Serial.begin(9600); MsTimer2::set(1,displayer2); MsTimer2::start(); for (int i = 0 ; i < num_cols; i++) volumes[i] = 8; for (int i = 0 ; i < buffer_size; i++) noises[i] = 512; hm.animate();}void loop() { hm.on(); sound_display();}void displayer2(){ hm.displayer();}void sound_display(){ // Get a value from the electret microphone noises[cur_index] = analogRead(mic_pin) ; cur_index++; if(cur_index == buffer_size) cur_index = 0; // Control the scroll speed with a potentiometer on analog pin 0 delay_speed = max(10, (analogRead(0)/4)); // Shift the waveform over one column if (millis() - last_shift > delay_speed){ last_shift = millis(); unsigned int avg = average(noises, buffer_size); unsigned long var = variance(noises, buffer_size); max_var = max(var, max_var); min_var = min(var, min_var); var = map(var,min_var , max_var, 0, 9); for (int i = 1 ; i < num_cols; i++){ volumes[i-1] = volumes[i]; } volumes[num_cols-1] = var; } // Write the wave form to LED display for (int i = 0 ; i < num_cols; i++){ for (int j = 0 ; j < volumes[i]; j++){ hm.set_pixel(i, j, true); } for (int j = volumes[i] ; j < 8; j++){ hm.set_pixel(i, j, false); } }}int average(int* array, int length){ int sum = 0; int i; for(i = 0; i < length ; i++){ sum += array[i]; } int avg = sum / length ; return avg;}unsigned long variance(int* array, int length){ long sum = 0; long avg = average(array, length); for(int i = 0; i < length ; i++){ sum += (array[i] - avg)*(array[i] - avg); } unsigned long var = sum / length; return var;}
#include "I2C.h"#include "MMA8453Q.h"MMA8453Q accelerometer;Now get accelerometer readings with the functions getX(), getY(), and getZ(), as in:
int x = accelerometer.getX();The values can be positive or negative and range from -512 to 512. See the entire example sketch below.
#include#include MMA8453Q accelerometer;void setup(){ Serial.begin(9600);} void loop(){ Serial.print("X:"); Serial.print(accelerometer.getX()); Serial.print(", Y:"); Serial.print(accelerometer.getY()); Serial.print(", Z:"); Serial.print(accelerometer.getZ()); Serial.println(); delay(300);}
hm.set_message(" Your message here! ");
Notice how we set the scroll speed in the line:
hm.set_scroll_wait(max(20, analogRead(0)/4));Here we are making the thumb wheel potentiometer control the speed the text is scrolling. We use the max function so that it the delay will never be less than 20 milliseconds. Remember the larger the number you give to the hm.set_scroll_wait function the slower the text goes. This is the inverse of speed. See the entire sketch below:
/* LucidTronix Heart Matrix LEO LED Display. * Text on the Heart Matrix * For instructions details and schematic, See: * http://www.lucidtronix.com/tutorials/57 */#include#include HeartMatrix hm = HeartMatrix(5,6,7); // dataPin is on 5, Latch is on 6, and clock is on 7void displayer2(){ hm.displayer();}void setup(){ MsTimer2::set(1,displayer2); MsTimer2::start(); hm.set_message(" Hello I'm the Heart Matrix from LucidTroniX! ");} void loop(){ hm.on(); hm.set_scroll_wait(max(20, analogRead(0)/4)); }
/* LucidTronix Wearable Wayfinder * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/56 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include #include #include // for leo:Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);WearableWayfinder ww = WearableWayfinder(&tft);int cur_note = 0;int old_pot = 0;char notes[] = {'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };void setup(){ Wire.begin(); // Start the I2C interface. ww.initialize(); //ww.set_date_time(__DATE__, __TIME__);}void loop(){ ww.draw_string(5, 4, "Time", ww.stroke.color_16(), 1); ww.show_time(10, 15); ww.draw_string(5, 36, "Date", ww.stroke.color_16(), 1); ww.show_date(10, 45); ww.draw_string(5, 66, "Compass", ww.stroke.color_16(), 1); ww.show_heading(10, 75); ww.draw_string(5, 91, "Accelerometer", ww.stroke.color_16(), 1); ww.show_accelerometer(10, 100); ww.draw_string(5, 136, "Potentiometer", ww.stroke.color_16(), 1); int pot = analogRead(0); if(pot != old_pot) tft.fillRect(10, 145, 50, 10, ww.background.color_16()); ww.print_integer(10, 145, pot, 1); old_pot = pot; if(digitalRead(ww.btn1_pin) == HIGH){ ww.playNote(notes[cur_note++], 300); if (cur_note == 8) cur_note = 0; } if(digitalRead(ww.btn2_pin) == HIGH){ ww.stroke = Color(random(255), random(255), random(255)); } }
void color_clock(int ax, int ay){ int hour_angle = cur_time.hour()*30; int minute_angle = cur_time.minute()*6; int second_angle = cur_time.second()*6; if (cur_min != cur_time.minute() ){ cur_min = cur_time.minute(); tft.fillScreen(ST7735_WHITE); ww.draw_numbers(ST7735_BLACK); } if (cur_sec != cur_time.second() ){ last_sec = millis(); cur_sec = cur_time.second(); } millisecond_six = constrain(((millis()-last_sec) / 167),0,6); int second_angle_hi_res = (cur_sec*6) + millisecond_six; ww.draw_orbiter(ax,ay,hour_angle, 15,3); ww.draw_ray(ax,ay,hour_angle, 15); ww.draw_orbiter(ax,ay,minute_angle, 25,2); ww.draw_ray(ax,ay,minute_angle, 25); ww.draw_orbiter(ax,ay,second_angle_hi_res, 35,5); }
See the whole sketch below:
/* LucidTronix Wearable Wayfinder Color Clock * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/29 */#include#include stroke // Core graphics library#include // Hardware-specific library#include #include #include #include #include #include #include // for leo:Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);WearableWayfinder ww = WearableWayfinder(&tft);int speakerPin = 6;int potPin = 0;int cur_sec = 0;int cur_min = 0;int millisecond_six = 0;unsigned int last_sec = 0;unsigned int last_press = 0;int old_val = 0;char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };int cur_note = 0;boolean alarm_on = false;boolean set_minute = false;DateTime cur_time, alarm_time;void setup(){ Wire.begin(); // Start the I2C interface. ww.initialize(); //ww.set_date_time(__DATE__, __TIME__); alarm_time = ww.now();}void loop(){ cur_time = ww.now(); ww.show_time(2,4); ww.show_time(80,4, alarm_time); if(set_minute) tft.drawLine(96, 12, 106, 12, Color(0,255,0).color_16()); else { tft.drawLine(80, 12, 90, 12, ww.background.color_16()); if (alarm_time.hour()>9) tft.drawLine(80, 12, 90, 12, Color(255,0,0).color_16()); else tft.drawLine(85, 12, 90, 12, Color(255,0,0).color_16()); } ww.show_date(4, 145); color_clock(64,80); check_buttons(); if (alarm_on){ ww.draw_string(2, 14,"Alarm is ON", ww.stroke.color_16(), 1); if (alarm_time.hour() == cur_time.hour() && alarm_time.minute() == cur_time.minute()){ ww.playNote(names[cur_note++], 300); if (cur_note == 8) cur_note = 0; } } else { analogWrite(speakerPin, 0); ww.draw_string(2, 14,"Alarm is OFF", ww.stroke.color_16(), 1); }} void color_clock(int ax, int ay){ int hour_angle = cur_time.hour()*30; int minute_angle = cur_time.minute()*6; int second_angle = cur_time.second()*6; if (cur_min != cur_time.minute() ){ cur_min = cur_time.minute(); tft.fillScreen(ST7735_WHITE); ww.draw_numbers(ST7735_BLACK); } if (cur_sec != cur_time.second() ){ last_sec = millis(); cur_sec = cur_time.second(); } millisecond_six = constrain(((millis()-last_sec) / 167),0,6); int second_angle_hi_res = (cur_sec*6) + millisecond_six; ww.draw_orbiter(ax,ay,hour_angle, 15,3); ww.draw_ray(ax,ay,hour_angle, 15); ww.draw_orbiter(ax,ay,minute_angle, 25,2); ww.draw_ray(ax,ay,minute_angle, 25); ww.draw_orbiter(ax,ay,second_angle_hi_res, 35,5); }void check_buttons(){ if ( digitalRead(ww.btn1_pin) == HIGH && millis() - last_press > 800){ tft.fillRect(0, 14, 128, 12, ST7735_WHITE); alarm_on = !alarm_on; ww.draw_numbers(ST7735_BLACK); last_press = millis(); } if (digitalRead(ww.btn2_pin) == HIGH && millis() - last_press > 800){ set_minute = !set_minute; tft.drawLine(80, 12, 110, 12, ST7735_WHITE); last_press = millis(); } int val = analogRead(potPin); if (abs(val - old_val) > 1){ tft.fillRect(64, 0, 128, 12, ST7735_WHITE); if(set_minute){ val = map(val,0,1024,0,60); alarm_time = DateTime(cur_time.year(), cur_time.month(), cur_time.day(), alarm_time.hour(), val, 0 ); } else { //set hour val = map(val,0,1024,0,24); alarm_time = DateTime(cur_time.year(), cur_time.month(), cur_time.day(), val, alarm_time.minute(), 0 ); } } old_val = analogRead(potPin);}
int get_bit_from_rules(int a, int b, int c) { if (a == 1 && b == 1 && c == 1) return rules[cur_rule][0]; if (a == 1 && b == 1 && c == 0) return rules[cur_rule][1]; if (a == 1 && b == 0 && c == 1) return rules[cur_rule][2]; if (a == 1 && b == 0 && c == 0) return rules[cur_rule][3]; if (a == 0 && b == 1 && c == 1) return rules[cur_rule][4]; if (a == 0 && b == 1 && c == 0) return rules[cur_rule][5]; if (a == 0 && b == 0 && c == 1) return rules[cur_rule][6]; if (a == 0 && b == 0 && c == 0) return rules[cur_rule][7]; return 0;}
There are 8 different possible arrangements of 3 pixels (2^3). We check all possibilities and then return the bit for the child row as determined by the current rule.
uint8_t scanline[scanline_size];uint8_t nextline[scanline_size];Then we get the tree pixels from the parent row in the lines:
if (i > 0) left = get_bit_from_index(i-1); // Left neighbor state int me = get_bit_from_index(i); // Current state int right = get_bit_from_index(i+1); // Right neighbor stateThe functions:
boolean get_bit_from_index(int index){ int byte_index = index / 8; int bit_index = 7 - (index % 8); byte cur_b = scanline[byte_index]; if ( (cur_b & (1 << bit_index)) != 0x00 ) return 1; else return 0;}void set_bit_from_index(int index, boolean abit){ int byte_index = index / 8; int bit_index = 7 - (index % 8); byte cur_b = nextline[byte_index]; if ( abit) cur_b |= (1 << bit_index); else cur_b &= ~(1 << bit_index) ; nextline[byte_index] = cur_b;}
are used to get and set the individual bits from the array of bytes scanline and nextline. This is more efficient than wasting a whole byte to just store one binary value. See the entire code below. And make sure you have all the necessary libraries installed from the
basic Wearable Wayfinder tutorial
before you try to compile :).
/* LucidTronix Wearable Wayfinder Cellular Automata Animation * Code by Samwell Freeman August 2014 * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/9 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include #include #include // for leo: Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);WearableWayfinder ww = WearableWayfinder(&tft);const int scanline_size = 20;uint8_t scanline[scanline_size];uint8_t nextline[scanline_size];int cur_rule = 0;const int num_rules = 8;boolean rules[num_rules][8] = { {0,1,0,1,1,0,1,0}, {0,0,0,1,1,1,1,0}, {1,0,1,1,0,1,1,0}, {0,1,1,1,1,1,1,0}, {0,1,0,1,0,1,1,0}, {1,0,0,1,1,0,0,1}, {1,1,0,0,0,0,0,1}, {0,0,0,1,1,0,1,0} }; int cur_y = 0;int mode = 0;int num_modes = 4;unsigned int last_press = 0;void setup(){ ww.initialize(); int bx = 0; int by = 0; for (int i = 0; i < scanline_size; i++) { scanline[i] = 0; if ( i == scanline_size/2) scanline[i] = 0x08; }}void loop(){ for (int i = 0; i < (scanline_size*8)-1; i++) { int left = 0; if (i > 0) left = get_bit_from_index(i-1); // Left neighbor state int me = get_bit_from_index(i); // Current state int right = get_bit_from_index(i+1); // Right neighbor state boolean abit = get_bit_from_rules(left,me,right); // Compute next generation state based on ruleset set_bit_from_index(i, abit); if (mode == 0){ int cur_r = 15; int cur_g = 2; int cur_b = random(200, 250); ww.background = Color(cur_r, cur_g, cur_b); cur_r = 230; cur_g = 62; cur_b = 0; ww.fill = Color(cur_r, cur_g, cur_b); } else if (mode == 1){ int cur_r = 0; int cur_g = 0; int cur_b = 0; ww.background = Color(cur_r, cur_g, cur_b); cur_r = random(23,55); cur_g = 62; cur_b = 230; ww.fill = Color(cur_r, cur_g, cur_b); } else if (mode == 2){ int cur_r = 5; int cur_g = random(150,222); int cur_b = 215; ww.background = Color(cur_r, cur_g, cur_b); cur_r = 219; cur_g = 200; cur_b = 30; ww.fill = Color(cur_r, cur_g, cur_b); } if (abit) tft.drawPixel(cur_y, i, ww.fill.color_16()); else tft.drawPixel(cur_y, i, ww.background.color_16()); if( digitalRead(ww.btn1_pin) == HIGH && millis() - last_press > 500 ){ cur_rule = ++cur_rule%num_rules; for (int i = 0; i < scanline_size; i++) { nextline[i] = 0; if ( i == scanline_size/2) nextline[i] = 0x08; } last_press = millis(); break; } if( digitalRead(ww.btn2_pin) == HIGH ){ ww.clear_screen(ww.background); for (int i = 0; i < scanline_size; i++) { nextline[i] = 0; if ( i == scanline_size/2) nextline[i] = 0x08; } mode = ++mode%num_modes; break; } } cur_y++; if (cur_y > 128) cur_y = 0; for (int i = 0; i < scanline_size; i++) { scanline[i] = nextline[i]; }}boolean get_bit_from_index(int index){ int byte_index = index / 8; int bit_index = 7 - (index % 8); byte cur_b = scanline[byte_index]; if ( (cur_b & (1 << bit_index)) != 0x00 ) return 1; else return 0;}void set_bit_from_index(int index, boolean abit){ int byte_index = index / 8; int bit_index = 7 - (index % 8); byte cur_b = nextline[byte_index]; if ( abit) cur_b |= (1 << bit_index); else cur_b &= ~(1 << bit_index) ; nextline[byte_index] = cur_b;}int get_bit_from_rules(int a, int b, int c) { if (a == 1 && b == 1 && c == 1) return rules[cur_rule][0]; if (a == 1 && b == 1 && c == 0) return rules[cur_rule][1]; if (a == 1 && b == 0 && c == 1) return rules[cur_rule][2]; if (a == 1 && b == 0 && c == 0) return rules[cur_rule][3]; if (a == 0 && b == 1 && c == 1) return rules[cur_rule][4]; if (a == 0 && b == 1 && c == 0) return rules[cur_rule][5]; if (a == 0 && b == 0 && c == 1) return rules[cur_rule][6]; if (a == 0 && b == 0 && c == 0) return rules[cur_rule][7]; return 0;}
Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);This allows us to directly access all of the functionality of the Adafruit graphics library for the ST7735 screen directly by calling functions on our tft variable. However, we also pass a pointer to this variable to the JoyGamer. Don't worry if you don't know what pointers are, but thats what we get when we put the ampersand before a variable. Since the JoyGamer knows about the tft LCD screen our library can also create graphics. So when you are writing code for the joy gamer you can use functions from the JoyGamer library and/or from the Adafruit graphics library. This is why we have to include all those libraries at the top of this file.
/* LucidTronix Joy Gamer * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/48 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);void setup(){ jg.initialize(); jg.start_sd_card();}void loop(){ jg.show_accelerometer(10, 40); tft.fillRect(49, 75, 40, 22, jg.background.color_16()); jg.draw_string(10, 75, "Joy X:", jg.stroke.color_16(), 1); jg.print_integer(50, 75, analogRead(0), 1); jg.draw_string(10, 85, "Joy Y:", jg.stroke.color_16(), 1); jg.print_integer(50, 85, analogRead(1), 1);}
Wire.begin(); gyro.initialize(2000); Serial.begin(9600);In the main loop we get X, Y, and Z orientations with the get functions, for example:
x = gyro.getX();Then we spit these values out on the serial port wait 300 milliseconds and repeat. See the whole example sketch and grab the library from the download section below.
/* LucidTronix L3G4200D Gyroscope Library * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/63 */ #include#include L3G4200D gyro;int x, y, z;void setup(){ Wire.begin(); gyro.initialize(2000); Serial.begin(9600);}void loop(){ x = gyro.getX(); Serial.print("X:"); Serial.print(x); y = gyro.getY(); Serial.print(" Y:"); Serial.print(y); z = gyro.getZ(); Serial.print(" Z:"); Serial.println(z); delay(300); //Just here to slow down the serial to make it more readable}
jg.bmp_load_and_draw_image("sample.bmp");
Make sure you have the SD card inserted properly, and loaded with the bitmap file "sample.bmp". You can of course load whatever images you want. The images do have to be in bitmap format and at largest 128 by 160 pixels. We wrote a little program to convert images into the bmp format, you can download it below.
/* LucidTronix Joy Gamer * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/48 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);void setup(){ jg.initialize(); jg.start_sd_card(); jg.bmp_load_and_draw_image("sample.bmp");}void loop() {}
//#define USE_ACCELEROMETER//#define USE_COLORThis way we don't load libraries we won't use and we can fit all the SD navigator code on the device. Otherwise you will get the nasty sketch too big error.
enum modes_t{ DISPLAY_DIRECTORY, DISPLAY_BITMAP, DISPLAY_TEXT};modes_t mode = DISPLAY_DIRECTORY;
Then in the main loop we have switch statement on the current mode. In the setup function we open the root directory and set it as the cur_file in the function open_file(). This function sets the mode appropriately given the type of file fed in as an argument.
/* LucidTronix Joy Gamer SD Navigator * If the compiler gives you an error about sketch being too big make sure to * COMMENT OUT both the USE_ACCELEROMETER and the USE COLOR defines in the JoyGamer.h file. * They should look like this: //#define USE_ACCELEROMETER //#define USE_COLOR * For more instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/21 */#include#include // Core graphics library#include // Hardware-specific library#include #include Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);enum modes_t{ DISPLAY_DIRECTORY, DISPLAY_BITMAP, DISPLAY_TEXT};modes_t mode = DISPLAY_DIRECTORY;int cursor_x = 64;int cursor_y = 64;File cur_file;File selected_file;int cur_dir_files;const int max_files = 12;String files[max_files];int line_height = 12;int char_width = 6;unsigned long last_switch = 0;void setup(){ jg.initialize(); jg.start_sd_card(); open_file("/");}void loop(){ switch(mode){ case DISPLAY_DIRECTORY: handle_cursor(); for(int i = 0; i < cur_dir_files; i++){ if(cursor_y > i*10+ 10 && cursor_y < i*10 + 20){ jg.draw_string(10, i*10 + 10, files[i], ST7735_MAGENTA); if(digitalRead(jg.joystick_btn_pin) == HIGH) { open_file(files[i]); break; } } else { jg.draw_string(10, i*10 + 10, files[i], ST7735_BLACK); } if(digitalRead(jg.btn_pin) == LOW && millis() - last_switch > 500) { open_file("/"); break; } } break; case DISPLAY_TEXT: jg.clear_screen(ST7735_WHITE); jg.clear_screen(); display_text_file(); break; case DISPLAY_BITMAP: jg.bmpdraw(cur_file); delay(2000); open_file("/"); break; }}void handle_cursor(){ tft.fillCircle(cursor_x, cursor_y, 5, ST7735_WHITE); int delta_x = map(analogRead(0), 0, 1024, -6, 6); int delta_y = map(analogRead(1), 0, 1024, -8, 8); cursor_x = constrain(cursor_x + delta_x, 5, 123); cursor_y = constrain(cursor_y + delta_y, 5, 155); tft.fillCircle(cursor_x, cursor_y, 5, ST7735_GREEN);}void open_file(String filename){ jg.clear_screen(ST7735_WHITE); cur_file.seek(0); cur_file.close(); delay(100); char buffer[13]; filename.toCharArray(buffer, 13); cur_file = SD.open(buffer); filename.toLowerCase(); if(cur_file.isDirectory()){ mode = DISPLAY_DIRECTORY; load_dir(cur_file); } else if(filename.lastIndexOf(".bmp") != -1){ mode = DISPLAY_BITMAP; } else { mode = DISPLAY_TEXT; } last_switch = millis();}void load_dir(File dir){ for(int i = 0; i < max_files; i++) files[i] = ""; jg.clear_screen(ST7735_WHITE); dir.rewindDirectory(); File entry = dir.openNextFile(); cur_dir_files = 0; while(entry && SD.exists(entry.name()) && max_files > cur_dir_files){ String filename = entry.name(); if(filename[0] != '.' && filename[0] != '_' && filename.lastIndexOf('~') == -1){ // don't show hidden files files[cur_dir_files] = filename; cur_dir_files++; } entry.close(); entry = dir.openNextFile(); } dir.rewindDirectory();}void display_text_file(){ int aax = 2; int aay = 3; tft.setTextColor(ST7735_BLACK); while(cur_file && cur_file.available() && aay < 150){ char cur_c = cur_file.read(); if(cur_c == 10 || cur_c == 13 || (cur_c == ' ' && aax > 85)){ aay += line_height; aax = 2; } else { tft.setCursor(aax, aay); tft.print(cur_c); aax += char_width; if (aax >= 118) { aax = 2; aay += line_height; } } delay(10); if(digitalRead(jg.btn_pin) == LOW && millis() - last_switch > 500) { open_file("/"); break; } } delay(1500);}
pic_name = base_name + cur_pic + String(".bmp");
We display that image wait 3 seconds and do it again. All the necessary libraries can be downloaded from our main
Joy Gamer Tutorial
.
/* LucidTronix Joy Gamer SD Navigator * If the compiler gives you an error about sketch being too big make sure to * COMMENT OUT both the USE_ACCELEROMETER and the USE COLOR defines in the JoyGamer.h file. * They should look like this: //#define USE_ACCELEROMETER //#define USE_COLOR * For more instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/65 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include // for leo:Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);//for uno etc.. //Adafruit_ST7735 tft = ST7735(10, 9, 8);// for mega//Adafruit_ST7735 tft = ST7735(53, 9, 8);JoyGamer jg = JoyGamer(&tft);int cur_pic = 0;const int num_pics = 19;String base_name = "exp";String pic_name = base_name + "2.bmp";void setup(){ jg.initialize(); jg.clear_screen(); jg.start_sd_card();}void loop() { cur_pic = random(0, num_pics); pic_name = base_name + cur_pic + String(".bmp"); jg.bmp_load_and_draw_image(pic_name); delay(3000);}
if (ww.btn_1_pressed()){ ww.clear_screen(); Serial1.println("Btn 1 pressed"); ww.draw_string(5, 96, "Btn 1 pressed", ww.stroke.color_16(), 1); }
This is because the Wayfinder is based on the Arduino Leonardo and the Serial port refers to the built-in Serial line for communication over USB.
When you spin the wayfinder's potentiometer or press its buttons we send messages over bluetooth to the Serial Monitor. You can send messages to the Wayfinder by typing them into the Serial Monitor and pressing return/enter.
/* LucidTronix Wearable Wayfinder * Blue Tooth Transeiver * written by samwell freeman for LucidTronix * For instructions, and documentation See: * http://www.lucidtronix.com/tutorials/64 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include #include #include // for leo:Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);WearableWayfinder ww = WearableWayfinder(&tft);int old_pot = 0;const int buffer_size = 18;char buffer_in[buffer_size];int char_index = 0;void setup() { Serial1.begin(115200); //initialize Serial1 Serial.begin(115200); //initialize Serial1 for(int i = 0; i < buffer_size; i++) buffer_in[i] = 0; ww.initialize(); ww.draw_string(5, 6, "BLUE", Color(30, 35, 255).color_16(), 3); ww.draw_string(15, 36, "TOOTH", Color(30, 35, 255).color_16(), 3); ww.draw_string(2, 76, "transiever", Color(30, 135, 235).color_16(), 2); delay(2500); ww.clear_screen();}void loop() { if (Serial1.available() > 0){ char cur_char; while((cur_char = Serial1.read()) != -1){ buffer_in[char_index] = cur_char; char_index = ++char_index % buffer_size; } Serial1.print("received string:"); Serial1.println(buffer_in); tft.fillRect(1, 145, 127, 10, ww.background.color_16()); ww.draw_string(2, 145, buffer_in, ww.stroke.color_16(), 1); } if (ww.btn_1_pressed()){ ww.clear_screen(); Serial1.println("Btn 1 pressed"); ww.draw_string(5, 106, "Btn 1 pressed", ww.stroke.color_16(), 1); } if (ww.btn_2_pressed()){ ww.clear_screen(); Serial1.println("Btn 2 pressed"); ww.draw_string(5, 106, "Btn 2 pressed", ww.stroke.color_16(), 1); } int pot = analogRead(0); if(abs(pot-old_pot) > 5) { old_pot = pot; tft.fillRect(10, 45, 50, 10, ww.background.color_16()); ww.draw_string(5, 36, "Potentiometer", ww.stroke.color_16(), 1); ww.print_integer(10, 45, pot, 1); Serial1.print("Wheel Reeds:"); Serial1.println(pot); }}
directoryName = "/Users/samfriedman/Pictures/small/"; newFileName = "tl_";Change the directoryName path to point to the folder with images that you want to make bitmaps from. The bitmaps will be numbered and have the prefix of the variable newFileName, so you may want to adjust that variable as well. Re-compile the project, run it and press 'd'.
#include "ofApp.h"//--------------------------------------------------------------void ofApp::setup(){ofSetLogLevel(OF_LOG_VERBOSE);directoryName = "/Users/samfriedman/Pictures/small/"; newFileName = "tl_";}//--------------------------------------------------------------void ofApp::update(){}//--------------------------------------------------------------void ofApp::draw(){ofDrawBitmapString("Press spacebar to open an image, \"s\" to save the processed output", 20, 15);for (unsigned int i=0; i
struct point{ float x; float y; float sizer; Color c;};
The functions are start_game(), draw_game() and draw_stats(). The start_game function clears the screen and resets the ball's position. The draw_stats() function draws the score and the remaining lives to the top of the screen. The draw_game() function does most of the work reading in the joy stick data and translating that into a position for the paddle. Then update the ball's position using the ball speed vector.
/* LucidTronix JoyGamer Joy Bounce * For instructions, details and schematic, See: * http://www.lucidtronix.com/tutorials/55 */#include#include // Core graphics library#include // Hardware-specific library#include #include #include #include #include Adafruit_ST7735 tft = Adafruit_ST7735(SS, 9, 8);JoyGamer jg = JoyGamer(&tft);struct point{ float x; float y; float sizer; Color c;};int score = 0;int lives = 5; // dont go over 5 without adjusting paddle color codeunsigned long last_press, last_level_up;point paddle, ball, ball_speed;void setup(){ jg.initialize(); start_game();}void loop() { draw_game(); delay(5);}// reset the ball and updates ball speed and padddle size;void start_game(){ jg.clear_screen(); paddle.x = 64; paddle.y = 150; paddle.c = Color(255 - (lives*50), 50, 50); paddle.sizer = 40 - min(36, (score/50)); ball.x = 64; ball.y = 64; ball.c = Color(random(0,245), random(0,245), random(0,245)); ball.sizer = max(2, 8 - score /50); long randx = random(60, 500) + score; long randy = random(150, 500) + score/3; ball_speed.x = (float)randx / 1000.0; ball_speed.y = (float)randy / 300.0; ; draw_stats(); }void draw_game(){ tft.fillCircle(ball.x, ball.y, ball.sizer, jg.background.color_16()); int dx = map(analogRead(jg.joy_x), 0, 1024, -3, 4); if(dx != 0) tft.fillRect(paddle.x-1, paddle.y-1, paddle.sizer+1, 6, jg.background.color_16()); ball.x += ball_speed.x; ball.y += ball_speed.y; if((dx > 0 && paddle.x < 128-paddle.sizer) || (dx < 0 && paddle.x > 0)) paddle.x += dx; tft.fillCircle(ball.x, ball.y, ball.sizer, ball.c.color_16()); tft.fillRect(paddle.x, paddle.y, paddle.sizer, 5, paddle.c.color_16()); if(bounce_ball_y()) { ball_speed.y *= -1; // avoid double bounces by pushing the ball above the paddle when necessary tft.fillCircle(ball.x, ball.y, ball.sizer, jg.background.color_16()); ball.y = min(paddle.y-1, ball.y); } if(ball.x < ball.sizer || ball.x > 128 - ball.sizer) ball_speed.x *= -1; if(ball.y > paddle.y + 6){ if(lives <= 0) { jg.clear_screen(); jg.draw_string(3, 10, "Game Over.", Color(150, 20, 50).color_16(), 2); jg.draw_string(10, 60, "Death becomes you.", Color(0, 50, 250).color_16(), 1); jg.draw_string(10, 74, "Press Joystick", Color(0, 50, 250).color_16()); jg.draw_string(10, 88, "to Start Over", Color(0, 50, 250).color_16()); while(digitalRead(jg.joystick_btn_pin) == LOW){ delay(40); } lives = 5; score = 0; start_game(); last_level_up = millis(); } else { lives--; start_game(); last_level_up = millis(); } } if(millis() - last_level_up > 4000){ score += 50; last_level_up = millis(); ball_speed.x *= 1.1; ball_speed.y *= 1.1; paddle.sizer = 40 - min(36, (score/50)); draw_stats(); } }boolean bounce_ball_y(){ if (ball.y >= paddle.y && ball.x > paddle.x && ball.x < paddle.x+paddle.sizer) return true; if (ball.y < 12 + ball.sizer) return true; return false;}void draw_stats(){ tft.fillRect(0, 0, 128, 12, Color(250, 250, 250).color_16()); jg.draw_string(2, 2, "Score:", Color(0, 50, 250).color_16()); String s = String(score); jg.draw_string(45, 2, s, Color(0, 50, 250).color_16()); jg.draw_string(78, 2, "Lives:", Color(0, 50, 250).color_16()); String s2 = String(lives); if ( lives <= 1 ) jg.draw_string(115, 2, s2, Color(255, 10, 10).color_16()); else if ( lives <= 3 ) jg.draw_string(115, 2, s2, Color(250, 200, 0).color_16()); else jg.draw_string(115, 2, s2, Color(5, 250, 5).color_16());}