diff --git a/AS7/blink/Sketches/Blink/Blink.componentinfo.xml b/AS7/blink/Sketches/Blink/Blink.componentinfo.xml index bdd1c33c..4c1d956d 100644 --- a/AS7/blink/Sketches/Blink/Blink.componentinfo.xml +++ b/AS7/blink/Sketches/Blink/Blink.componentinfo.xml @@ -15,7 +15,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include include C @@ -26,18 +26,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include\avr\iom168pb.h + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h header C - RQeMPo0FmYHbnGhGei+xjg== + yAkfp/rOk1YuAUNFypsCqg== include/avr/iom168pb.h - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\main.c + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.c template source C Exe @@ -48,7 +48,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\main.cpp + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.cpp template source C Exe @@ -59,7 +59,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb libraryPrefix GCC @@ -71,8 +71,8 @@ ATmega_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.150/Atmel.ATmega_DFP.pdsc - 1.2.150 + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 true ATmega168PB diff --git a/AS7/blink/Sketches/Blink/Blink.cppproj b/AS7/blink/Sketches/Blink/Blink.cppproj index 9440df47..7f775db7 100644 --- a/AS7/blink/Sketches/Blink/Blink.cppproj +++ b/AS7/blink/Sketches/Blink/Blink.cppproj @@ -25,36 +25,25 @@ true exception_table 2 - 0 + 7 com.atmel.avrdbg.tool.ispmk2 - 0000B0022024 + 0000B3020609 0x1E9415 ISP - 125000 + 250000 - 125000 + 250000 ISP com.atmel.avrdbg.tool.ispmk2 - 0000B0022024 + 0000B3020609 AVRISP mkII - - \Debug\Blink.lss - - - .lss - ^\s*(?<address>[a-f0-9]*):\s*.*$ - true - address - $pc - - @@ -83,11 +72,22 @@ + + + + 125000 + + ISP + + com.atmel.avrdbg.tool.avrdragon + 00A200031957 + AVR Dragon + - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -103,7 +103,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -120,7 +120,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -134,7 +134,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include @@ -143,7 +143,7 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -159,9 +159,9 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include ../../../../../cores/blinkcore ../../../../../variants/standard + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize (-O1) @@ -179,20 +179,19 @@ - ../../../../../libraries/blinklib/src - ../../../../../cores/blinkcore - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - ../../../../../libraries/blinkstate/src - ../../../../../variants/standard - ../../../../../libraries/blinkani/src + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + ../../../../../cores/blinklib - Optimize (-O1) + Optimize for size (-Os) True True Default (-g2) True True + -include Arduino.h -include blinklib.h + True + True libm @@ -200,7 +199,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Default (-Wa,-g) @@ -213,24 +212,9 @@ - - blinkani - {e2a7902a-6b39-45b9-a00d-a47b64a34e20} - True - - - blinkcore - {dce6c7e3-ee26-4d79-826b-08594b9ad897} - True - - + blinklib - {c03aa8ca-16e3-4a37-9986-eeaa5cbcefd6} - True - - - blinkstate - {c7103c5c-377a-4589-89b5-ee449b21a39a} + {1d617d28-32b2-45ab-ba7f-ee884b221598} True diff --git a/AS7/blink/Sketches/Blink/main.cpp b/AS7/blink/Sketches/Blink/main.cpp index 54a4f662..49e570a5 100644 --- a/AS7/blink/Sketches/Blink/main.cpp +++ b/AS7/blink/Sketches/Blink/main.cpp @@ -1,60 +1,148 @@ -/* - * Prototype Step Function - * - * A button press sends step from a single Blink - * All Blinks call their step function - * - * Step is special in that it leaves getNeighborState as the value before step happened... - */ +#include -#include "blinklib.h" -#include "blinkstate.h" -#include "blinkani.h" +#define F_CPU 8000000 -#define STEP_SEND_DURATION 200 -#define STEP_BUFFER 100 +#include +//#include "bitfun.h" -Timer stepSendTimeout; -Timer stepReceiveTimeout; +//#include "adc.h" + +#include "Serial.h" + +ServicePortSerial sp; -enum state { - BREATHE, - STEP -}; void setup() { - // put your setup code here, to run once: - blinkStateBegin(); - blinkAniBegin(); - setValueSentOnAllFaces(BREATHE); + + sp.begin(); + } + +// Returns the previous conversion result (call adc_startConversion() to start a conversion). +// Blocks if you call too soon and conversion not ready yet. + +uint8_t adc_readLastVccX10(void) { // Return Vcc x10 + + while (TBI(ADCSRA,ADSC)) ; // Wait for any pending conversion to complete + + uint8_t lastReading = (255*11 / ADCH); // Remember the result from the last reading. + + sp.println(lastReading); + + return( lastReading ); + +} + +#define DELAY_BLINK_MS 200 // Delay between flashes +#define DELAY_DIGIT_MS 750 // Delay between 10's and 1's digits +#define DELAY_READ_MS 1000 // Delay between consecutive readings + + + +enum phase_t { READ , TENS, ONES }; + +enum state_t { S_OFF , S_ON }; + +static phase_t phase=READ; +static state_t state=S_OFF; + +static Timer next; + +static uint8_t c=0; + +static uint8_t lastReading=24; + void loop() { - // put your main code here, to run repeatedly: - if( buttonSingleClicked() ) { - if( stepSendTimeout.isExpired() ) { - step(); + /* + if (next.isExpired()) { + + if (state==S_ON) { + + setColor( OFF ); + + state=S_OFF; + + } else { + setColor( BLUE ); + + state=S_ON; + + } + + next.set(500); + } - } - - // check neighbors for step - FOREACH_FACE(f) { - if( getLastValueReceivedOnFace(f) == STEP ) { - if( stepReceiveTimeout.isExpired() ) { - step(); - } + + return; + */ + + if (phase==READ) { + + adc_startConversion(); + + lastReading = adc_readLastVccX10(); + + c = lastReading/10; // Tens digit + + if (c==0) c=10; + + phase = TENS; + + state = S_OFF; + + next.set(2000); // Show immediately + } - } - - if( stepSendTimeout.isExpired() ) { - setValueSentOnAllFaces(BREATHE); - } -} -void step() { - setValueSentOnAllFaces(STEP); - stepSendTimeout.set(STEP_SEND_DURATION); - stepReceiveTimeout.set(STEP_SEND_DURATION + STEP_BUFFER); - blink(WHITE, 300); -} \ No newline at end of file + if (c==0) { // Done with this digit + + if (phase==TENS) { + + c = lastReading%10; // Ones digit + + if (c==0) c=10; + + phase = ONES; + + next.set( DELAY_DIGIT_MS ); + + } else { + + phase = READ; + + next.set( DELAY_READ_MS ); + } + + } else { + + if (next.isExpired()) { + + if (state==S_OFF) { + + setColor( RED ); + + state = S_ON; + + next.set( DELAY_BLINK_MS ); + + } else { // state==ON + + setColor( OFF ); + + state = S_OFF; + + c--; + + next.set( DELAY_BLINK_MS ); + + } + + } + + } + +} // loop() + + diff --git a/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.componentinfo.xml b/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.componentinfo.xml new file mode 100644 index 00000000..a309251d --- /dev/null +++ b/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.componentinfo.xml @@ -0,0 +1,86 @@ + + + + + + + Device + Startup + + + Atmel + 1.2.0 + C:/Program Files (x86)\Atmel\Studio\7.0\Packs + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include + + include + C + + + include + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h + + header + C + yAkfp/rOk1YuAUNFypsCqg== + + include/avr/iom168pb.h + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.c + template + source + C Exe + GD1k8YYhulqRs6FD1B2Hog== + + templates/main.c + Main file (.c) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\main.cpp + template + source + C Exe + CAoCZxJJ1rIlxmxWa+AEaw== + + templates/main.cpp + Main file (.cpp) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb + + libraryPrefix + GCC + + + gcc/dev/atmega168pb + + + + + ATmega_DFP + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 + true + ATmega168PB + + + + Resolved + Fixed + true + + + \ No newline at end of file diff --git a/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.cppproj b/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.cppproj new file mode 100644 index 00000000..d7e73769 --- /dev/null +++ b/AS7/blink/bareblinkOS/bareblinkOS/bareblinkOS.cppproj @@ -0,0 +1,181 @@ + + + + 2.0 + 7.0 + com.Atmel.AVRGCC8.CPP + {b750fa7b-22de-43c4-a796-f95418271edd} + ATmega168PB + none + Executable + CPP + $(MSBuildProjectName) + .elf + $(MSBuildProjectDirectory)\$(Configuration) + bareblinkOS + bareblinkOS + bareblinkOS + Native + true + false + true + true + 0x20000000 + + true + exception_table + 2 + 0 + 0 + + + + + + + + + + + + + + com.atmel.avrdbg.tool.ispmk2 + 0000B3020609 + 0x1E9415 + ISP + 250000 + + + + 250000 + + ISP + + com.atmel.avrdbg.tool.ispmk2 + 0000B3020609 + AVRISP mkII + + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize (-O1) + True + True + Default (-g2) + True + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize (-O1) + True + True + Default (-g2) + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Default (-Wa,-g) + + + + + + compile + + + + \ No newline at end of file diff --git a/AS7/blink/bareblinkOS/bareblinkOS/blinkos.h b/AS7/blink/bareblinkOS/bareblinkOS/blinkos.h new file mode 100644 index 00000000..71f09f85 --- /dev/null +++ b/AS7/blink/bareblinkOS/bareblinkOS/blinkos.h @@ -0,0 +1,103 @@ +/* + * blinkOS.h + * + * This defines a high-level interface to the blinks operating system. + * + */ + + +// Gets us uintx_t +// Not sure why, but it MUST be in the header and not the cpp +#include +#include "pixelcolor.h" + +#define IR_FACE_COUNT 6 +#define IR_FACE_BITMASK 0b00111111 // 1's in each bit for every face (will break if we ever make a nonagon or higher) + +#define PIXEL_FACE_COUNT 6 + +// TODO: Change time to uint24 _t +// Supported!!! https://gcc.gnu.org/wiki/avr-gcc#types +// typedef __uint24 millis_t; + +typedef uint32_t millis_t; + +// loopstate is passed in and out of the user program on each pass +// through the main event loop + +// I know this is ugly, but keeping them in a single byte lets us pass them by value +// and also lets us OR them together. Efficiency in the updater trumps since it +// runs every millisecond. + +#define BUTTON_BITFLAG_PRESSED 0b00000001 +#define BUTTON_BITFLAG_LONGPRESSED 0b00000010 +#define BUTTON_BITFLAG_RELEASED 0b00000100 +#define BUTTON_BITFLAG_SINGLECLICKED 0b00001000 +#define BUTTON_BITFLAG_DOUBLECLICKED 0b00010000 +#define BUTTON_BITFLAG_MULITCLICKED 0b00100000 +#define BUTTON_BITFLAG_DUMMY 0b01000000 + +struct buttonstate_t { + + uint8_t down; // 1 if button is currently down + + uint8_t bitflags; + + uint8_t clickcount; // Number of clicks on most recent multiclick + +}; + + +struct ir_data_buffer_t { + + const uint8_t *data; + uint8_t len; + uint8_t ready_flag; // 1 if new packet received. Call markread_data_buffer(); + +}; + +// Sends immediately. Blocks until send is complete. +// Higher level should provide some collision control. +// Returns 0 if it was not able to send because there was already an RX in progress on this face + +uint8_t ir_send_userdata( uint8_t face, const uint8_t *data , uint8_t len ); + +struct loopstate_in_t { + + ir_data_buffer_t ir_data_buffers[IR_FACE_COUNT]; + + buttonstate_t buttonstate; // Note that the flags in here are cleared after every pass though the loop + + uint8_t woke_flag; // Have we slept and woke? + + + millis_t millis; // elapsed milliseconds since start + + uint8_t futureexapansion[20]; // Save some space for future expansion + +}; + +// Mark a packet as consumed so the buffer can receive the next one. +// Done automatically each time loopEntry() returns, but this can let you free up the packet sooner +// for better throughput + +// Mark a packet as consumed so the buffer can receive the next one. +// Done automatically each time loopEntry() returns, but this can let you free up the packet sooner +// for better throughput + +void ir_mark_packet_read( uint8_t face ); + +struct loopstate_out_t { + + pixelColor_t colors[PIXEL_FACE_COUNT]; // Current RGB colors of all faces. 5 bits each. + // Topmost bit set indicates it changed + + uint8_t futureexapansion[20]; // Save some space for future expansion + +}; + + +// These are provided by blinklib + +//void setupEntry(); +void loopEntry( loopstate_in_t const *loopstate_in , loopstate_out_t *loopstate_out); diff --git a/AS7/blink/bareblinkOS/bareblinkOS/main.cpp b/AS7/blink/bareblinkOS/bareblinkOS/main.cpp new file mode 100644 index 00000000..6f47361a --- /dev/null +++ b/AS7/blink/bareblinkOS/bareblinkOS/main.cpp @@ -0,0 +1,156 @@ +/* + * bareblinkOS.cpp + * + * An example that runs directly on the blinkOS without blinklib + * + */ + + +#include "blinkos.h" +//#include "bootloader.h" + +//#include // memcmp() + + +void setupEntry() { + + // Blank + +}; + +Timer next_send_pull_request; + +void loopEntry() { + + if (next_send_pull_request.isExpired()) { + + // Time to send next pull request + + blinkboot_packet p; + + p.header = P + + } + +} + + + +/* +//uint8_t *cookie = (uint8_t *) "Josh is a *very* nice, good guy."; // 32 bytes long (includes trailing null) +//#define COOKIE_SIZE 32 + +uint8_t cookie[] = {0b10110111}; // Non repeating bit pattern to detect errors. +#define COOKIE_SIZE 1 + +#define TX_PROBE_TIME_MS 250 // How often to do a blind send when no RX has happened recently to trigger ping pong + +#define MISSED_RX_SHOW_TIME_MS 500 // How long to show blue when a RX timeout happens +#define RX_TIMEOUT_RX 200 // If we do not see a message in this long, then show a timeout + +// Note these all init to 0's, which is what we want. + +struct face_t { + + millis_t next_tx_ms; // Next time to transmit + millis_t last_rx_ms; // Last time a good packet seen + + uint8_t errorFlag; +}; + +face_t faces[IR_FACE_COUNT]; + +void loopEntry( loopstate_in_t const *loopstate_in , loopstate_out_t *loopstate_out) { + + millis_t now_ms = loopstate_in->millis; + + // Clear out any errors on button press + + if ( loopstate_in->buttonstate.bitflags & BUTTON_BITFLAG_PRESSED ) { + + for( uint8_t f=0; f< IR_FACE_COUNT; f++ ) { + + if ( faces[f].errorFlag ) { + + faces[f].errorFlag=0; + + } + + } + + } + + + for( uint8_t f=0; f< IR_FACE_COUNT; f++ ){ + + // Look for received packet.... + + if ( loopstate_in->ir_data_buffers[f].ready_flag ) { + + if ( (loopstate_in->ir_data_buffers[f].len == COOKIE_SIZE ) && !memcmp( loopstate_in->ir_data_buffers[f].data , cookie , COOKIE_SIZE) ) { + + // Got a good packet! + + faces[f].last_rx_ms = now_ms; + + faces[f].next_tx_ms = now_ms; // pong + + } else { + + // Either len or cookie was wrong, so data transmission error + + faces[f].errorFlag = 1; + + } + + } + + // Update shown color + + if (faces[f].errorFlag) { + + // Red on error + loopstate_out->colors[f] = pixelColor_t( 20 , 0, 0 , 1 ); + + } else { + + millis_t elapsed_ms = now_ms - faces[f].last_rx_ms; // Elapsed time since last RX + + if (elapsed_ms < 200) { + + // Less than 200ms since last good RX, so show green + loopstate_out->colors[f] = pixelColor_t( 0 , 20, 0 , 1 ); + + } else if ( elapsed_ms < 500 ) { + + // More then 200ms since last good RX, so show red for 300ms + loopstate_out->colors[f] = pixelColor_t( 0 , 0, 20 , 1 ); + + + } else { + + // Been long enough that we assume no one is there + loopstate_out->colors[f] = pixelColor_t( 0 , 0, 0 , 1 ); + + } + + } + + + if ( faces[f].next_tx_ms < now_ms ) { + + // Time to send on this face + + if (ir_send_userdata( f , cookie , COOKIE_SIZE )) { + + // Schedule a blind send in case we don't see a ping soon + faces[f].next_tx_ms = now_ms + TX_PROBE_TIME_MS; + } + } // for(f) + + + } + +} + +*/ \ No newline at end of file diff --git a/AS7/blink/bareblinkOS/bareblinkOS/pixelcolor.h b/AS7/blink/bareblinkOS/bareblinkOS/pixelcolor.h new file mode 100644 index 00000000..264e5de3 --- /dev/null +++ b/AS7/blink/bareblinkOS/bareblinkOS/pixelcolor.h @@ -0,0 +1,61 @@ +/* + * pixel.h + * + * All the functions for showing colors on the 6 RGB LEDs on the tile face. + * Also timekeeping functions since we use the same timer as for the LEDs. + * + */ + +#ifndef RGB_PIXELCOLOR_H_ +#define RGB_PIXELCOLOR_H_ +// Each pixel has 32 brightness levels for each of the three colors (red,green,blue) +// These brightness levels are normalized to be visually linear with 0=off and 31=max brightness + +// Note use of anonymous union members to let us switch between bitfield and int +// https://stackoverflow.com/questions/2468708/converting-bit-field-to-int + +union pixelColor_t { + + struct { + uint8_t reserved:1; + uint8_t r:5; + uint8_t g:5; + uint8_t b:5; + }; + + uint16_t as_uint16; + + pixelColor_t(); + pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in ); + pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in , uint8_t reserverd_in ); + +}; + +inline pixelColor_t::pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in ) { + + r=r_in; + g=g_in; + b=b_in; + +} + +inline pixelColor_t::pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in , uint8_t reserverd_in ) { + + r=r_in; + g=g_in; + b=b_in; + reserved = reserverd_in; + +} + + +inline pixelColor_t::pixelColor_t() { + + r=0; + g=0; + b=0; + +} + + +#endif /* RGB_PIXELS_H_ */ \ No newline at end of file diff --git a/AS7/blink/blink.atsln b/AS7/blink/blink.atsln index 188e14cd..e9d4bf45 100644 --- a/AS7/blink/blink.atsln +++ b/AS7/blink/blink.atsln @@ -3,18 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Atmel Studio Solution File, Format Version 11.00 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blinkcore", "blinkcore\blinkcore.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}" -EndProject -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blinklib", "blinklib\blinklib.cppproj", "{C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6}" -EndProject Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Blink", "Sketches\Blink\Blink.cppproj", "{C3728EB0-DD95-4D36-9694-C16A953BBF73}" EndProject -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blinkstate", "blinkstate\blinkstate.cppproj", "{C7103C5C-377A-4589-89B5-EE449B21A39A}" - ProjectSection(ProjectDependencies) = postProject - {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6} = {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6} - EndProjectSection +Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blinklib", "..\blinklib\blinklib\blinklib.cppproj", "{1D617D28-32B2-45AB-BA7F-EE884B221598}" EndProject -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blinkani", "blinkani\blinkani.cppproj", "{E2A7902A-6B39-45B9-A00D-A47B64A34E20}" +Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "bareblinkOS", "bareblinkOS\bareblinkOS\bareblinkOS.cppproj", "{B750FA7B-22DE-43C4-A796-F95418271EDD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,26 +15,18 @@ Global Release|AVR = Release|AVR EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.ActiveCfg = Debug|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.Build.0 = Debug|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR - {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6}.Debug|AVR.ActiveCfg = Debug|AVR - {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6}.Debug|AVR.Build.0 = Debug|AVR - {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6}.Release|AVR.ActiveCfg = Release|AVR - {C03AA8CA-16E3-4A37-9986-EEAA5CBCEFD6}.Release|AVR.Build.0 = Release|AVR {C3728EB0-DD95-4D36-9694-C16A953BBF73}.Debug|AVR.ActiveCfg = Debug|AVR {C3728EB0-DD95-4D36-9694-C16A953BBF73}.Debug|AVR.Build.0 = Debug|AVR {C3728EB0-DD95-4D36-9694-C16A953BBF73}.Release|AVR.ActiveCfg = Release|AVR {C3728EB0-DD95-4D36-9694-C16A953BBF73}.Release|AVR.Build.0 = Release|AVR - {C7103C5C-377A-4589-89B5-EE449B21A39A}.Debug|AVR.ActiveCfg = Debug|AVR - {C7103C5C-377A-4589-89B5-EE449B21A39A}.Debug|AVR.Build.0 = Debug|AVR - {C7103C5C-377A-4589-89B5-EE449B21A39A}.Release|AVR.ActiveCfg = Release|AVR - {C7103C5C-377A-4589-89B5-EE449B21A39A}.Release|AVR.Build.0 = Release|AVR - {E2A7902A-6B39-45B9-A00D-A47B64A34E20}.Debug|AVR.ActiveCfg = Debug|AVR - {E2A7902A-6B39-45B9-A00D-A47B64A34E20}.Debug|AVR.Build.0 = Debug|AVR - {E2A7902A-6B39-45B9-A00D-A47B64A34E20}.Release|AVR.ActiveCfg = Release|AVR - {E2A7902A-6B39-45B9-A00D-A47B64A34E20}.Release|AVR.Build.0 = Release|AVR + {1D617D28-32B2-45AB-BA7F-EE884B221598}.Debug|AVR.ActiveCfg = Debug|AVR + {1D617D28-32B2-45AB-BA7F-EE884B221598}.Debug|AVR.Build.0 = Debug|AVR + {1D617D28-32B2-45AB-BA7F-EE884B221598}.Release|AVR.ActiveCfg = Release|AVR + {1D617D28-32B2-45AB-BA7F-EE884B221598}.Release|AVR.Build.0 = Release|AVR + {B750FA7B-22DE-43C4-A796-F95418271EDD}.Debug|AVR.ActiveCfg = Debug|AVR + {B750FA7B-22DE-43C4-A796-F95418271EDD}.Debug|AVR.Build.0 = Debug|AVR + {B750FA7B-22DE-43C4-A796-F95418271EDD}.Release|AVR.ActiveCfg = Release|AVR + {B750FA7B-22DE-43C4-A796-F95418271EDD}.Release|AVR.Build.0 = Release|AVR EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AS7/blink/blinkani/blinkani.cppproj b/AS7/blink/blinkani/blinkani.cppproj deleted file mode 100644 index 134566b2..00000000 --- a/AS7/blink/blinkani/blinkani.cppproj +++ /dev/null @@ -1,181 +0,0 @@ - - - - 2.0 - 7.0 - com.Atmel.AVRGCC8.CPP - {e2a7902a-6b39-45b9-a00d-a47b64a34e20} - ATmega168PB - none - StaticLibrary - CPP - lib$(MSBuildProjectName) - .a - $(MSBuildProjectDirectory)\$(Configuration) - - - blinkani - blinkani - blinkani - Native - true - false - true - true - 0x20000000 - - true - exception_table - 2 - 0 - 0 - - - - - - - - - - - - - - - - - - - - - - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" - True - True - True - True - False - True - True - - - NDEBUG - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - Optimize for size (-Os) - True - True - True - True - True - - - NDEBUG - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - Optimize for size (-Os) - True - True - True - - - libm - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - - - - - - - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" - True - True - True - True - False - True - True - - - DEBUG - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - Optimize (-O1) - True - True - Default (-g2) - True - True - True - - - DEBUG - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - ../../../../libraries/blinklib/src - ../../../../cores/blinkcore - - - Optimize (-O1) - True - True - Default (-g2) - True - - - libm - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - Default (-Wa,-g) - - - - - - compile - blinkani.cpp - - - compile - blinkani.h - - - - - blinklib - {c03aa8ca-16e3-4a37-9986-eeaa5cbcefd6} - True - - - - \ No newline at end of file diff --git a/AS7/blink/blinkcore/blinkcore.componentinfo.xml b/AS7/blink/blinkcore/blinkcore.componentinfo.xml index 7eb162fb..6b69d582 100644 --- a/AS7/blink/blinkcore/blinkcore.componentinfo.xml +++ b/AS7/blink/blinkcore/blinkcore.componentinfo.xml @@ -15,7 +15,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include include C @@ -26,18 +26,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include\avr\iom168pb.h + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h header C - RQeMPo0FmYHbnGhGei+xjg== + yAkfp/rOk1YuAUNFypsCqg== include/avr/iom168pb.h - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.c + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.c template source C Lib @@ -48,7 +48,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.cpp + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.cpp template source C Lib @@ -59,7 +59,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb libraryPrefix GCC @@ -71,8 +71,8 @@ ATmega_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.150/Atmel.ATmega_DFP.pdsc - 1.2.150 + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 true ATmega168PB diff --git a/AS7/blink/blinkcore/blinkcore.cppproj b/AS7/blink/blinkcore/blinkcore.cppproj index c5877aa9..07774e86 100644 --- a/AS7/blink/blinkcore/blinkcore.cppproj +++ b/AS7/blink/blinkcore/blinkcore.cppproj @@ -22,10 +22,10 @@ false true true - + 0x20000000 true - + exception_table 2 0 @@ -50,7 +50,7 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -65,7 +65,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -81,7 +81,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -95,7 +95,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include @@ -104,7 +104,7 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -120,7 +120,7 @@ ../../../../variants/standard - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize (-O1) @@ -138,9 +138,7 @@ ../../../../variants/standard - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - ../../../../libraries/blinklib - ../../../../libraries/blinkstate + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize (-O1) @@ -148,6 +146,7 @@ True Default (-g2) True + True libm @@ -155,7 +154,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Default (-Wa,-g) @@ -187,9 +186,9 @@ compile callbacks.h - + compile - effects.cpp + debug.h compile @@ -199,10 +198,6 @@ compile ir.h - - compile - ir_callback.h - compile main.cpp @@ -223,29 +218,13 @@ compile power.h - - compile - run.h - compile shared.h - - compile - sp.cpp - - + compile - sp.h - - - compile - timer.cpp - - - compile - timer.h + timers.h compile @@ -255,10 +234,6 @@ compile utils.h - - compile - WMath.cpp - compile hardware.h diff --git a/cores/blinkcore/WMath.cpp b/AS7/blink/blinklib/WMath.cpp similarity index 98% rename from cores/blinkcore/WMath.cpp rename to AS7/blink/blinklib/WMath.cpp index 9fb072f4..d6e9dd00 100644 --- a/cores/blinkcore/WMath.cpp +++ b/AS7/blink/blinklib/WMath.cpp @@ -4,21 +4,20 @@ Part of the Wiring project - http://wiring.org.co Copyright (c) 2004-06 Hernando Barragan Modified 13 August 2006, David A. Mellis for Arduino - http://www.arduino.cc/ - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ extern "C" { @@ -55,4 +54,4 @@ long map(long x, long in_min, long in_max, long out_min, long out_max) } unsigned int makeWord(unsigned int w) { return w; } -unsigned int makeWord(unsigned char h, unsigned char l) { return (h << 8) | l; } +unsigned int makeWord(unsigned char h, unsigned char l) { return (h << 8) | l; } \ No newline at end of file diff --git a/AS7/blink/blinkani/blinkani.componentinfo.xml b/AS7/blink/blinklib/blinkos.componentinfo.xml similarity index 86% rename from AS7/blink/blinkani/blinkani.componentinfo.xml rename to AS7/blink/blinklib/blinkos.componentinfo.xml index d62ce723..6b69d582 100644 --- a/AS7/blink/blinkani/blinkani.componentinfo.xml +++ b/AS7/blink/blinklib/blinkos.componentinfo.xml @@ -15,7 +15,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include include C @@ -26,18 +26,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include\avr\iom168pb.h + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h header C - RQeMPo0FmYHbnGhGei+xjg== + yAkfp/rOk1YuAUNFypsCqg== include/avr/iom168pb.h - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.c + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.c template source C Lib @@ -48,18 +48,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.cpp + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.cpp template source C Lib - Trxx3epc7RJCPDUK4d6aPQ== + Ztu2zWWQnYWBswuEN+3iHQ== templates/library.cpp Main file (.cpp) - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb libraryPrefix GCC @@ -71,8 +71,8 @@ ATmega_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.150/Atmel.ATmega_DFP.pdsc - 1.2.150 + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 true ATmega168PB diff --git a/AS7/blink/blinklib/blinklib.cppproj b/AS7/blink/blinklib/blinkos.cppproj similarity index 59% rename from AS7/blink/blinklib/blinklib.cppproj rename to AS7/blink/blinklib/blinkos.cppproj index 661e3ded..27db1707 100644 --- a/AS7/blink/blinklib/blinklib.cppproj +++ b/AS7/blink/blinklib/blinkos.cppproj @@ -15,7 +15,7 @@ blinklib - blinklib + blinkos blinklib Native true @@ -50,7 +50,7 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -65,7 +65,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -81,7 +81,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -95,7 +95,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include @@ -104,65 +104,65 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" - True - True - True - True - False - True - True - - - DEBUG - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - ../../../../variants/standard - ../../../../cores/blinkcore - ../../../../libraries/blinklib/src - - - Optimize (-O1) - True - True - Default (-g2) - True - True - True - - - DEBUG - - - - - ../../../../cores/blinkcore - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - ../../../../variants/standard - ../../../../libraries/blinklib/src - ../../../../libraries/blinkstate/src - - - Optimize (-O1) - True - True - Default (-g2) - True - - - libm - - - - - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include - - - Default (-Wa,-g) - + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + DEBUG + + + + + ../../../../variants/standard + ../../../../cores/blinkcore + ../../../../libraries/blinklib/src + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize most (-O3) + True + True + Default (-g2) + True + True + True + + + DEBUG + + + + + ../../../../cores/blinkcore + ../../../../variants/standard + ../../../../libraries/blinklib/src + ../../../../libraries/blinkstate/src + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize (-O1) + True + True + Default (-g2) + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Default (-Wa,-g) + @@ -174,18 +174,6 @@ compile ArduinoTypes.h - - compile - blinklib.cpp - - - compile - blinklib.h - - - compile - chainfunction.h - compile irdata.cpp @@ -210,13 +198,17 @@ compile Serial.h - - - - blinkcore - {dce6c7e3-ee26-4d79-826b-08594b9ad897} - True - + + compile + blinkstate.cpp + + + compile + blinkstate.h + + + compile + \ No newline at end of file diff --git a/AS7/blink/blinklib/blinklib.componentinfo.xml b/AS7/blink/blinkstate/blinklib.componentinfo.xml similarity index 87% rename from AS7/blink/blinklib/blinklib.componentinfo.xml rename to AS7/blink/blinkstate/blinklib.componentinfo.xml index 7eb162fb..6b69d582 100644 --- a/AS7/blink/blinklib/blinklib.componentinfo.xml +++ b/AS7/blink/blinkstate/blinklib.componentinfo.xml @@ -15,7 +15,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include include C @@ -26,18 +26,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include\avr\iom168pb.h + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h header C - RQeMPo0FmYHbnGhGei+xjg== + yAkfp/rOk1YuAUNFypsCqg== include/avr/iom168pb.h - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.c + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.c template source C Lib @@ -48,7 +48,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.cpp + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.cpp template source C Lib @@ -59,7 +59,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb libraryPrefix GCC @@ -71,8 +71,8 @@ ATmega_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.150/Atmel.ATmega_DFP.pdsc - 1.2.150 + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 true ATmega168PB diff --git a/AS7/blink/blinkstate/blinkstate.cppproj b/AS7/blink/blinkstate/blinklib.cppproj similarity index 88% rename from AS7/blink/blinkstate/blinkstate.cppproj rename to AS7/blink/blinkstate/blinklib.cppproj index 60ca22dc..b7d26379 100644 --- a/AS7/blink/blinkstate/blinkstate.cppproj +++ b/AS7/blink/blinkstate/blinklib.cppproj @@ -15,7 +15,7 @@ blinkstate - blinkstate + blinklib blinkstate Native true @@ -36,21 +36,21 @@ - - - - - - - - - + + + + + + + + + - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -65,7 +65,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -81,7 +81,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize for size (-Os) @@ -95,7 +95,7 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include @@ -104,7 +104,7 @@ - -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb" + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" True True True @@ -119,8 +119,8 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include ../../../../variants/standard + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize (-O1) @@ -137,9 +137,9 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include ../../../../libraries/blinklib/src ../../../../cores/blinkcore + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Optimize (-O1) @@ -154,22 +154,12 @@ - %24(PackRepoDir)\atmel\ATmega_DFP\1.2.150\include + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include Default (-Wa,-g) - - - compile - blinkstate.cpp - - - compile - blinkstate.h - - \ No newline at end of file diff --git a/AS7/blink/blinkstate/blinkstate.componentinfo.xml b/AS7/blinklib/blinklib/blinklib.componentinfo.xml similarity index 86% rename from AS7/blink/blinkstate/blinkstate.componentinfo.xml rename to AS7/blinklib/blinklib/blinklib.componentinfo.xml index 7b557755..e530ca93 100644 --- a/AS7/blink/blinkstate/blinkstate.componentinfo.xml +++ b/AS7/blinklib/blinklib/blinklib.componentinfo.xml @@ -15,7 +15,7 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include include C @@ -26,18 +26,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\include\avr\iom168pb.h + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h header C - RQeMPo0FmYHbnGhGei+xjg== + yAkfp/rOk1YuAUNFypsCqg== include/avr/iom168pb.h - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.c + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.c template source C Lib @@ -48,18 +48,18 @@ - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\templates\library.cpp + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.cpp template source C Lib - Rl1kiKQ0FRcw/3wUl/e2IQ== + XxwIOzelgjmEqvoplf+/zA== templates/library.cpp Main file (.cpp) - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.150\gcc\dev\atmega168pb + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb libraryPrefix GCC @@ -71,8 +71,8 @@ ATmega_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.150/Atmel.ATmega_DFP.pdsc - 1.2.150 + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 true ATmega168PB diff --git a/AS7/blinklib/blinklib/blinklib.cppproj b/AS7/blinklib/blinklib/blinklib.cppproj new file mode 100644 index 00000000..2982acac --- /dev/null +++ b/AS7/blinklib/blinklib/blinklib.cppproj @@ -0,0 +1,246 @@ + + + + 2.0 + 7.0 + com.Atmel.AVRGCC8.CPP + {1d617d28-32b2-45ab-ba7f-ee884b221598} + ATmega168PB + none + StaticLibrary + CPP + lib$(MSBuildProjectName) + .a + $(MSBuildProjectDirectory)\$(Configuration) + + + blinklib + blinklib + blinklib + Native + true + false + true + true + 0x20000000 + + true + exception_table + 2 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize (-O1) + True + True + Default (-g2) + True + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + ../../../../libraries/blinkos/src + + + Optimize (-O1) + True + True + Default (-g2) + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Default (-Wa,-g) + + + + + + compile + Arduino.h + + + compile + ArduinoTypes.h + + + compile + blinklib.cpp + + + compile + blinklib.h + + + compile + DummySerial.h + + + compile + main.cpp + + + compile + Print.cpp + + + compile + Print.h + + + compile + run.h + + + compile + Serial.cpp + + + compile + Serial.h + + + compile + blinkbios_shared_button.h + + + compile + blinkbios_shared_functions.h + + + compile + blinkbios_shared_irdata.h + + + compile + blinkbios_shared_millis.h + + + compile + blinkbios_shared_pixel.h + + + compile + sp.cpp + + + compile + sp.h + + + compile + startup.S + + + compile + Timer.cpp + + + + \ No newline at end of file diff --git a/AS7/blinkos/blinkos/blinkos.componentinfo.xml b/AS7/blinkos/blinkos/blinkos.componentinfo.xml new file mode 100644 index 00000000..037973f8 --- /dev/null +++ b/AS7/blinkos/blinkos/blinkos.componentinfo.xml @@ -0,0 +1,86 @@ + + + + + + + Device + Startup + + + Atmel + 1.2.0 + C:/Program Files (x86)\Atmel\Studio\7.0\Packs + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include + + include + C + + + include + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\include\avr\iom168pb.h + + header + C + yAkfp/rOk1YuAUNFypsCqg== + + include/avr/iom168pb.h + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.c + template + source + C Lib + 2/NZD/YM3hWVTzqme3bDXw== + + templates/library.c + Main file (.c) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\templates\library.cpp + template + source + C Lib + k0XODgvtMPZr5NdihYUf6A== + + templates/library.cpp + Main file (.cpp) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb + + libraryPrefix + GCC + + + gcc/dev/atmega168pb + + + + + ATmega_DFP + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATmega_DFP/1.2.209/Atmel.ATmega_DFP.pdsc + 1.2.209 + true + ATmega168PB + + + + Resolved + Fixed + true + + + \ No newline at end of file diff --git a/AS7/blinkos/blinkos/blinkos.cppproj b/AS7/blinkos/blinkos/blinkos.cppproj new file mode 100644 index 00000000..6a4f451c --- /dev/null +++ b/AS7/blinkos/blinkos/blinkos.cppproj @@ -0,0 +1,222 @@ + + + + 2.0 + 7.0 + com.Atmel.AVRGCC8.CPP + {75c30a81-edd6-4de5-8d7e-d379aa89f81a} + ATmega168PB + none + StaticLibrary + CPP + lib$(MSBuildProjectName) + .a + $(MSBuildProjectDirectory)\$(Configuration) + + + blinkos + blinkos + blinkos + Native + true + false + true + true + + + true + + 2 + 0 + 0 + + + + + + + + + + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize for size (-Os) + True + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + + + + + + + -mmcu=atmega168pb -B "%24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\gcc\dev\atmega168pb" + True + True + True + True + False + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Optimize (-O1) + True + True + Default (-g2) + True + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + ../../../../cores/blinkcore + ../../../../variants/standard + + + Optimize (-O1) + True + True + Default (-g2) + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATmega_DFP\1.2.209\include + + + Default (-Wa,-g) + + + + + + compile + blinkos.cpp + + + compile + blinkos.h + + + compile + blinkos_button.cpp + + + compile + blinkos_button.h + + + compile + blinkos_headertypes.h + + + compile + blinkos_irdata.cpp + + + compile + blinkos_irdata.h + + + compile + blinkos_pixel.cpp + + + compile + blinkos_pixel.h + + + compile + blinkos_timer.cpp + + + compile + blinkos_timer.h + + + compile + bootloader.h + + + compile + pixelcolor.h + + + + + blinkcore + {dce6c7e3-ee26-4d79-826b-08594b9ad897} + True + + + + \ No newline at end of file diff --git a/Getting-started.md b/Getting-started.md new file mode 100644 index 00000000..38ce3370 --- /dev/null +++ b/Getting-started.md @@ -0,0 +1,66 @@ + +## Getting Started + +This core requires at least Arduino IDE v1.6.2, where v1.6.5+ is recommended.
+* [Programmers](#programmers) +* **[How to install](#how-to-install)** + - [Boards Manager Installation](#boards-manager-installation) + - [Manual Installation](#manual-installation) +* **[Getting started](#getting-started)** + +* **[Digging deeper](#digging-deeper)** + - [Hardware Abstraction Layer](#hardware-abstraction-layer) + +## Programmers + +Since there is no bootloader in a tile, all code must be programmed rather than downloaded. + +Each Blink has a standard 6-pin ISP connector. The arrow marks pin #1. + +You can use any AVR programmer supported by AVRDUDE and the Arduino IDE. + +This one is cheap... +http://amzn.to/2u5ZHw6 + +This one is fast and comes with nice cables... +http://amzn.to/2u5Wo7M + +Just connect your programmer and select it in the "Programmers" menu, and connect the 6-pin ISP to the tile. I like to use this pogo pin fixture to make the connections... + +https://www.sparkfun.com/products/11591 + +...but you can also just hold a 6-pin header onto the tile, or solder on a 6-pin header if you want to leave the programmer attached. + +If you do not have a programmer, you can also use an Arduino and a couple of wires. + +You can then use the "Sketch-Upload to Programmer" menu choice or just press the Play button to compile your code and program it into the connected tile. (Both the menu option and the button do the same thing with a tile.) + +## How to install +Click on the "Download ZIP" button in the upper right corner of this repo on Github... + +https://github.com/bigjosh/Move38-Arduino-Platform + +You can switch to the `dev` branch if you want to live on the bleeding edge and get new features quickly. + +Extract the ZIP file, and move the extracted files to the location "**~/Documents/Arduino/hardware/Move38-manual/avr**". Create the folder if it doesn't exist. This readme file should be located at "**~/Documents/Arduino/hardware/Move38-manual/avr/README.md**" when you are done. + +Open Arduino IDE, and a new category in the boards menu called "Move38-manual" will show up. + +##### Notes + +* We called the "vendor/maintainer" folder `Move38-manual` so that you can also use the boards manager and you will be able to tell the two apart in the boards menu. + +* You must manually create the `avr` folder and you must also manually move the files out from this repo into this folder. We could not automatically have the folds inside the repo match the Arduino required folder layout because in in the boards manager, the architecture is in the JSON file rather than the folder structure. Arg. + +* The "**~/Documents/Arduino/hardware/Move38-manual/avr**" folder is a Git repo and is also set up for easy editing in Atmel Studio with a solution inside the `\AS7` sub-folder. + +* If you want to leave the git repo in a directory that is not inside the Arduino tree (I do), then you can add a directory symlink from the Arduino tree to the repo tree. This repo needs to be at `arduino\hardware\move38\avr` where `arduino` is your Arduino home directory as set in `File->Preferences->Sketchbook Location` in the IDE. You have to manually create `hardware` and then `move38` and then put the `avr` symlink in then. On my windows machine the command for that was `D:\Documents\Arduino\hardware\move38\avr>mklink /D avr "D:\Github\Move38-Arduino-Platform"` + +## Helllo Blink! + +* Open the **Tools > Board** menu item, and select `Blink` from the `Move38` submenu. +* Select what kind of programmer you're using under the **Programmers** menu. +* Select "File->Examples->Examples for Blink->Getting Started" and choose "F-ColorWheel". +* Hit the Play button. + +The IDE should compile the code and program the Blinks tile... and you should see pretty blinking lights! diff --git a/Layers.md b/Layers.md new file mode 100644 index 00000000..7887b605 --- /dev/null +++ b/Layers.md @@ -0,0 +1,50 @@ +## Layers + +### 1. Cold power up + +This typically only happens when a new battery is installed or when the foreground software locks up. + +The chip resets all the ports according to the the datasheet and then jumps to the reset vector, which typically points to the GCC bootstrap code. This code initializes global variables and does a bit of hardware setup. + +Here is an example on how to run code before the bootstrap (very esoteric)... + +https://github.com/bigjosh/Ognite-firmware/blob/master/Atmel%20Studio/Candle0005.c#L440 + +### 2. main() + +After all the C setup is complete, the bootstrap jumps to the `main()` function. This function is in `cores/blinkcore/main.c` + +Here we... + +1. Initialize the binks hardware systems and turn them on. +2. Jump to the `run()` function. +3. Re-run `run()` forever if it returns. + +The blinks `main()` function is weakly defined, so you can make your own if you want to take over before the blinks hardware gets started up. + +### 3. run() + +Once all the hardware is up and running, then `run()` function takes over. This code lives in `libraries\blinklib\src\blinklib.cpp`. + +Here we... + +1. Call the user-supplied `setup()` function. +2. Repeatedly... + a. Clears the cached `millis()` snapshot + b. Calls the user-suppled `loop()` function + c. Atomically updates the values shown on the LEDs to match any changes made in `loop()` + d. Processes new IR data that has been recieved, and potentially sends IR data. + + +Then `run()` function is weakly defined, so you can override it if you want more control over these. + +### 4. `setup()` and `loop()` + +These are the idiomatic entry points provided by Arduino sketches. + +`setup()` is called once, typically right after a tile has had a battery change. + +`loop()` is called at the current frame rate, which is currently about 18 times per second. You `loop()` function should complete before the next frame starts, so should not have any blocking code in it. The tile sleeps for the time between when `loop()` returns and the next frame, so the faster you can complete your work and return from loop, the longer batteries will last. + + + \ No newline at end of file diff --git a/Production.MD b/Production.MD new file mode 100644 index 00000000..24bbbc62 --- /dev/null +++ b/Production.MD @@ -0,0 +1,75 @@ +## Production Programming + +Each virgin blink needs to be programmed with... + +1. The correct FUSE settings +2. The BIOS code that lives in the flash bootloader area +3. The application code that lives in the application flash area. + +### Fuses + +BOOTSZ = 1024W_1C00 +BOOTRST = [X] +RSTDISBL = [ ] +DWEN = [ ] +SPIEN = [X] +WDTON = [ ] +EESAVE = [ ] +BODLEVEL = DISABLED +CKDIV8 = [X] +CKOUT = [ ] +SUT_CKSEL = INTRCOSC_8MHZ_6CK_14CK_65MS + +EXTENDED = 0xF8 (valid) +HIGH = 0xDF (valid) +LOW = 0x62 (valid) + +Note that the `high` and `low` fuses are the same as their default values from the factory, but the `extended` fuse must be reprogrammed from the default value. + +### Bootloader + +The bootloader code comes from a different repo and can be found in the [`bootloaders`](/bootloaders) directory here. + +### Application + +The application code is generated by compiling a sketch in the Arduino IDE. + +## Combining the bootloader and application + +You can minimize the number of production programming steps by combining the application code and bootloader into a single HEX file using the `Sketch->Export Compiled Binary` option from the IDE menus. This will create a file called `SKETCHNAME.ino.with_bootloader.standard.hex` in the current sketch folder that contains both the application and the bootloader. + +### Example + +Here is and example command that will program a blink using the `AVRDUDE` utility and the `USBtiny` programmer... + +``` +avrdude -B 5 -v -patmega168pb -cusbtiny -Uflash:w:SKETCHNAME.ino.with_bootloader.standard.hex:i -Uefuse:w:0xf8:m -u +``` + +`-B` sets the clock programming speed. This must be less than 1/4 of the chip clock speed, which is 1Mhz by default, but here we make it slightly slower to account for chips that might have an extra slow internal clock. +`-v` verifies the programming when complete +`-p` specifies the chip model +`-Uflash` specifies the file to program into flash memory (both bootloader and application in this case) +`-Uefuse` specifies the new value for the extended fuse (note that the high and low fuses are left with their default values) +`-u` disables AVRDUDE's normal behavior that blocks changing fuses. + +Note that this assumes that `avrdude` is in your search path and that it can find its config file. See `avrdude` docs for details. + +### Optimizing + +We can slightly reduce cycle using a command like this... + +``` +avrdude -B 4 -D -v -patmega168pb -cusbtiny -Uflash:w:D-TimerFlashX.ino.with_bootloader.standard.hex:i -Uefuse:w:0xf8:m -u +``` + +`-D` skips erasing the flash before programming it. If you are programming virgin chips then the flash should be blank. +`-B` lowered to 4 slightly increases the programmer clock. Some out of spec chips might fall to program at this speed. + +Note that these new options are both safe because the verify will fail if anything goes wrong. + +### QA + +Some programming errors may pass functional testing, but still contain latent errors, so you must check that the programming cycle verified successfully before passing a programmed unit. + +You can use the error code returned by AVRDUDE to check if the verify succeeded. \ No newline at end of file diff --git a/README.md b/README.md index f979f876..2a8cb374 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,25 @@ An Arduino core for the Blinks gaming tile. More info at... http://move38.com +## Roadmap This core requires at least Arduino IDE v1.6.2, where v1.6.5+ is recommended.
* [Programmers](#programmers) * **[How to install](#how-to-install)** * **[Getting started](#getting-started)** -* **[Digging deeper](#digging-deeper)** - - [Hardware Abstraction Layer](#hardware-abstraction-layer) +Covers installing this repo so that you can write and download code into a Blinks tile using the Arduino IDE. -## Programmers +Ends with loading a `HelloBlink` program it a tile. -Since there is no bootloader in a tile, all code must be programmed rather than downloaded. +### Writing games -You can use any AVR programmer supported by AVRDUDE and the Arduino IDE. +The best way to start writing games is to work your way though the examples in the "File->Examples->Examples for Blink Tile" menu in the Arduino IDE (after you have installed this repo as described in the Getting Started above). + -Just connect your programmer and select it in the "Programmers" menu, and connect the 6-pin ISP to the tile. If you do not have a programmer, you can also use an Arduino and a couple of wires. More detailed instructions to come. +### [Service Port](Service Port.MD) -You can then use the "Sketch-Upload to Programmer" menu choice or just press the Play button to compile your code and program it into the connected tile. (Both the menu option and the button do the same thing with a tile.) +Describes the service port connector on each blink. Lets you add `print` statements to your programs, which can be very helpful during development. ## How to install @@ -37,16 +38,11 @@ In the future, we'll offer a simplified Arduino Boards Manager install path. * The "**~/Documents/Arduino/hardware/Move38-manual/avr**" folder is a Git repo and is also set up for easy editing in Atmel Studio with a solution inside the `\AS7` sub-folder. -## Getting started with Move38 Blinks on Arduino +### API Layers -* Open the **Tools > Board** menu item, and select `Blinks` from the `Move38` submenu. -* Select what kind of programmer you're using under the **Programmers** menu. -* Select "File->Examples->Examples for Blink Tile" and choose "HelloBlink". -* Hit the Play button. +The blinks hardware can do incredible things, and you can have unfettered access to it at any level you want. This documents describes those layers from bare metal up. -The IDE should compile the code and program the Blinks tile... and you should see pretty blinking lights! -### Digging Deeper #### Hardware Abstraction Layer diff --git a/Service Port.MD b/Service Port.MD index 5bd44a1c..234ae3fd 100644 --- a/Service Port.MD +++ b/Service Port.MD @@ -10,6 +10,64 @@ Each Blink tile has a service port to make developing and debugging easier. For * See print statements * Accept type in values and commands -* Output precise timing signals to an oscilloscope +* Output precise timing signals to an oscilloscope + + +### Seeing output + +When writing normal game code, you can use the the service port as a serial connection to the host computer. + +#### Connection + +Get yourself a Blinks Dev Candy board, which converts the service port connector into a 6-pin header that connects to a USB serial converter like this... + +http://amzn.to/2ptPUv5 + +#### Use + +* Connect the USB serial port adapter to the computer +* Open Arduino IDE +* Pick the correct serial port from `Tools->Port` +* Open the monitor with "Tools->Serial Port Monitor" +* Pick `1000000 baud`. (Yes, it is that fast! :) ) + +You will now see anything that the tile prints out. + +You can also use any other serial monitor program with the same port and baud settings. + +#### Instrumentation + +The easiest way to get prints out the serial port is to use the `ServicePortSerial` class like this... + +~~~ +/* + * Prints a welcoming message to the servie port. + * + * To see the message, connect a serial terminal to the Blinks service port. + * More info on how to do that here... + * https://github.com/bigjosh/Move38-Arduino-Platform/blob/master/Service%20Port.MD + */ + +#include "Serial.h" + +ServicePortSerial Serial; + +void setup() { + + Serial.begin(); + +} + + +void loop() { + + Serial.println("Hello Serial port!"); + +}; +~~~ + +#### Input -*MORE INFO ON CONNECTION AND USE COMING* \ No newline at end of file +The Serial class also has several read functions that let you input data from the serial terminal. Check out the Tile World example to see one way to do this. + +Note that that by default Arduino serial monitor does not send what you type until you press enter. \ No newline at end of file diff --git a/boards.txt b/boards.txt index 70f97f67..d01eb81e 100644 --- a/boards.txt +++ b/boards.txt @@ -1,65 +1,46 @@ -########################################## -## -## This file to use the Move38 Blinks platform -## with the Ardiuno IDE -## http://move38.com -## -## Orginally based on ATMEGA168PA settings from MiniCore -## https://github.com/MCUdude/MiniCore -## -########################################## +# See: http://code.google.com/p/arduino/wiki/Platforms -tile.name=Blinks Tile -tile.upload.maximum_size=15872 -tile.upload.maximum_data_size=1024 +menu.cpu=Processor -# This line seems to have no effect? -tile.upload.tool=nothing +############################################################## -tile.program.tool=avrdude +blink.name=Blink -tile.build.core=blinkcore -tile.build.board=AVR_Blink -tile.build.variant=standard +blink.vid.0=0x2341 +blink.pid.0=0x0043 +blink.vid.1=0x2341 +blink.pid.1=0x0001 +blink.vid.2=0x2A03 +blink.pid.2=0x0043 +blink.vid.3=0x2341 +blink.pid.3=0x0243 +blink.upload.tool=avrdude +blink.upload.protocol=arduino -# Note that we actually boot up with DIV8 fuse programmed, so -# only running at 1Mhz. Then we switch the prescaller to /2 in init() -# so we are at 4Mhz by the time user code runs. -# This is done becuase 8Mhz might need a higher voltage than our battery can -# supply. +# This is the maximum size of a program image. It comes from +# `blinkbios.h` and is the result of `FLASH_PAGES_PER_IMAGE` * `FLASH_BYTES_PER_PAGE` +# Used only for printing the "Sketch uses... of program stroage space..." message. +blink.upload.maximum_size=5888 +blink.upload.maximum_data_size=1024 +blink.upload.speed=115200 -tile.build.f_cpu=4000000L +#blink.bootloader.tool=avrdude +#blink.bootloader.low_fuses=0xFF +#blink.bootloader.high_fuses=0xDE +#blink.bootloader.extended_fuses=0xFD +#blink.bootloader.unlock_bits=0x3F +#blink.bootloader.lock_bits=0x0F -tile.build.mcu=atmega168pb +# Note that the folder `bootloaders` seems to be hardcoded into the Ardunio IDE -tile.ltoarcmd=avr-gcc-ar +blink.bootloader.file=BlinkBIOS.hex +blink.build.mcu=atmega168pb +blink.build.f_cpu=8000000L +blink.build.board=AVR_BLINK +blink.build.core=blinklib +blink.build.variant=standard -# -------------------------------------------------------------------- -# This is tmeporary to support the old tiles without logos. Soon there will be none of them. +############################################################## -legacytile.name=Legacy No-Logo Blinks Tile -legacytile.upload.maximum_size=15872 -legacytile.upload.maximum_data_size=1024 - -# This line seems to have no effect? -legacytile.upload.tool=nothing - -legacytile.program.tool=avrdude - -legacytile.build.core=blinkcore -legacytile.build.board=AVR_Blink -legacytile.build.variant=legacy - -# Note that we actually boot up with DIV8 fuse programmed, so -# only running at 1Mhz. Then we switch the prescaller to /2 in init() -# so we are at 4Mhz by the time user code runs. -# This is done becuase 8Mhz might need a higher voltage than our battery can -# supply. - -legacytile.build.f_cpu=4000000L - -legacytile.build.mcu=atmega168pb - -legacytile.ltoarcmd=avr-gcc-ar \ No newline at end of file diff --git a/bootloaders/BlinkBIOS.hex b/bootloaders/BlinkBIOS.hex new file mode 100644 index 00000000..2209e6b1 --- /dev/null +++ b/bootloaders/BlinkBIOS.hex @@ -0,0 +1,280 @@ +:102E00000AB400FCFDCF90938900809388008AB5B6 +:102E100081608ABD08959B01F894409131015091E1 +:102E2000320160913301709134017894420F531F45 +:102E3000611D711DFC01408351836283738308957A +:102E4000CF93C62F7AD5882349F08DE55DD58C2F99 +:102E50005BD585E78C1B58D5CF9146C5CF9108959A +:102E6000CF92DF92FF920F931F93CF93DF93062FA2 +:102E7000F42E152F62D58823F1F08BED45D5CF2D9B +:102E8000D12F6E0180E2C80ED11C1BEDF9908F2D61 +:102E90003BD51F0DCC16DD06C9F7802F35D582ED49 +:102EA000801B811B31D5DF91CF911F910F91FF9036 +:102EB000DF90CF9019C5DF91CF911F910F91FF90B7 +:102EC000DF90CF900895CF93DF93C0E0D1E0DE0193 +:102ED000EC912E2F229526952695237011968C9194 +:102EE0001197982F9770990F990F922B8695869529 +:102EF0008695E695EF71F0E0EC5BF74CE491ED8799 +:102F0000E92FF0E0EC5BF74CE491EE87E82FF0E07E +:102F1000EC5BF74CE491EF871296249681E0AC309D +:102F2000B807B1F6DF91CF910895E0E0F1E011929A +:102F300082E0EE37F807D8F38091310190913201A9 +:102F4000A0913301B0913401805E9D4EA54FBF4FDB +:102F50008093360190933701A0933801B0933901E3 +:102F6000B2CFEDE4F1E0108212AA1092B1011092FA +:102F7000E30110921502109247020895F894EEE3CF +:102F8000F1E01182148216821582128611867894DD +:102F9000089582E00895FF920F931F93CF93DF93DC +:102FA000F62E072F142FC9D4882361F087E3ACD401 +:102FB000CF2DD02F112321F08991A6D41150FACF13 +:102FC00093D481E0DF91CF911F910F91FF900895ED +:102FD000182FC12FDE63DF738D2F90E0B5D52FEF53 +:102FE00080E792E0215080409040E1F700C000006F +:102FF000B8DF80E090E0A8D52FEF80E792E0215085 +:1030000080409040E1F700C00000ABDFC15021F0EC +:1030100080913F0180FFDECFC6E0C15021F080915A +:103020003F0180FF0CC080913F0180FFD2CFF89418 +:1030300080E18093600088E080936000FFCF80E0B3 +:1030400090E082D52FEF80E792E0215080409040C1 +:10305000E1F700C0000085DF80E090E075D52FEF3C +:1030600080E792E0215080409040E1F700C00000EE +:1030700078DFD3CF14BE0FB6F894A8958091600086 +:10308000886180936000109260000FBEC1E6D0E0BE +:1030900010E8188301E008832FEF80E792E02150C9 +:1030A00080409040E1F700C000003FDF85B7982FD7 +:1030B000916095BF826085BF10BF84E880936400F3 +:1030C000E8E6F0E08081846080835F9A729A589A83 +:1030D000599A269A689A279A699A549A529A5E9AA5 +:1030E0005D9A569A559A00936E0082E08093700024 +:1030F000D0D380E0E82E81E0F82EF70100A784B15C +:103100008F6384B987B1806C87B978946BD425D5E7 +:10311000F894188318827894F70110A6D1D46FD44C +:10312000CF93DF93CDB7DEB7CB5BD1090FB6F89461 +:10313000DEBF0FBECDBFC455DF4F8883CC5AD04011 +:1031400012D581E08093790280E090E0FDD4C455EF +:10315000DF4F2881CC5AD0402223F9F0412C512C4A +:10316000E0E0F0E08491480E511C3196E11537E122 +:10317000F307C1F768E873E1CE018F559F4F4BDE2F +:103180004EE2C655DF4F4883CA5AD0401FE300E0E5 +:1031900088EBF82E96E0B92E43C01DE4812E11E095 +:1031A000912EE12CF12C12E3BE2C1E9D60011F9D7F +:1031B000D00C1124F601E35BFE4F8081882309F4D3 +:1031C000AAC083818B3009F0A6C08281843009F0C7 +:1031D000A2C0C40149D3882309F49DC0F601E05B75 +:1031E000FE4F4180528001E00E0D063019F400E0E0 +:1031F000B5E0BB2E81E090E00B2C01C0880F0A9453 +:10320000EAF7182F10951F7360E070E0CE018F551C +:103210009F4F01DEC655DF4F1882CA5AD040F12CAD +:10322000CB55DF4F188219821A821B82C55AD040B3 +:10323000A5DECB54DF4F4882C55BD040CA54DF4F78 +:103240005882C65BD04087EC282E24182518C95414 +:10325000DF4F2882C75BD04053E0C755DF4F58830C +:10326000C95AD040C555DF4F1882CB5AD0407AEBAF +:10327000E72E8B2D90E0E2E3E89F9001E99F300D6F +:103280001124A901435B5E4F1A01FA013396C1551F +:10329000DF4FF983E883CF5AD040A901405B5E4FEE +:1032A000CF54DF4F59834883C15BD040FC01EE0F00 +:1032B000FF1FCD54DF4FF983E883C35BD0404F5FDE +:1032C0005F4FC854DF4F59834883C85BD040E050FC +:1032D000FF4FC355DF4FF983E883CD5AD0403E01FD +:1032E000F1E86F0E711C9301205E3F4FC654DF4F13 +:1032F00039832883CA5BD040C455DF4F3881CC5A0C +:10330000D040332319F0112309F406C280913F0104 +:1033100080FF0DC001C23FEFE31AF30A42E3840EBF +:10332000911C56E0E516F10409F03ECF63CFC45579 +:10333000DF4F8881CC5AD040811198C0F101808143 +:10334000882309F493C0C155DF4FE881F981CF5A32 +:10335000D04080818B3D09F060C0F10182818332D1 +:1033600009F05BC0C10180D2882309F456C0CF5454 +:10337000DF4FE881F981C15BD04081A18F1160C02E +:10338000CD54DF4FE881F981C35BD040E050FF4F5F +:10339000818187709F2D9295990F907E892B8183D3 +:1033A0008F2D8370A1E0B0E0AC0FBD1FF0E28F9FC6 +:1033B000A00DB11D112490E2C854DF4FE881F981BE +:1033C000C85BD04001900D929A95E1F78330A1F44B +:1033D000BE016F5F7F4FC655DF4F8881CA5AD0400C +:1033E00098D3C655DF4F2881CA5AD0402F5FC655A3 +:1033F000DF4F2883CA5AD040F39438EBF3122CC025 +:10340000C355DF4FE881F981CD5AD0408081806C6F +:10341000808383E08183A12C17C0C155DF4FE881F1 +:10342000F981CF5AD0408081883E51F4F1018281E8 +:10343000823031F4C10118D2AA24A394811101C0B1 +:10344000A12CF8EBFF120CC0111108C069EB8B2DF9 +:10345000F7DC68EBF62E07C0A12C02C058EBF52E66 +:103460006F2D8B2DEDDCF101108201C0A12CC02E3F +:10347000D12CF2E3FC9D4001FD9D900C1124F40140 +:10348000E35BFE4F8081882309F45CC083818D3526 +:1034900009F04DC08281833009F049C0CF01E4D1E9 +:1034A000882309F444C021E030E0002E01C0220F3F +:1034B0000A94EAF7C555DF4F4881CB5AD040422BDA +:1034C000C555DF4F4883CB5AD040F401E05BFE4F37 +:1034D000E180C601880F991F57EB5E15D8F4E9EB20 +:1034E000EE120BC020951223FC01E050FF4F20810B +:1034F000206C20832181276009C0FC01E050FF4F30 +:103500002081206C20832181287F236021839924BE +:10351000939401C0912CFC01E050FF4F8181877092 +:103520009E2D9295990F907E892B818301C0912CBD +:1035300022E32C9DF0012D9DF00D1124E35BFE4F45 +:10354000108201C0912CEF1408F03DC08E2D86959D +:103550008695C655DF4F3881CA5AD0408317B8F4D4 +:1035600040E2E49EC0011124D30186199709FC01B1 +:10357000EA0FFB1FE491ED93C654DF4FE881F98118 +:10358000CA5BD040AE17BF0791F7A3010BC08E2DC9 +:10359000837041E050E04C0F5D1FF0E28F9F400DC3 +:1035A000511D11246E2D802F5BDC68E770E0CE0189 +:1035B0008B559F4F30DC23E0C755DF4F2883C95A16 +:1035C000D0403AEBE32EF894409131015091320112 +:1035D00060913301709134017894CB55DF4F88812D +:1035E0009981AA81BB81C55AD04084179507A60747 +:1035F000B70718F09110C1C08BC0C755DF4FE881E5 +:10360000C95AD040E111AEC0F894409131015091B7 +:10361000320160913301709134017894CF55DF4FBE +:1036200088819981AA81BB81C15AD04084179507AE +:10363000A607B70748F481E090E00C2C01C0880F82 +:103640000A94EAF780951823F601EE0FFF1FE05069 +:10365000FF4F818187708183112309F459C0212F85 +:1036600030E00F5F063009F400E0C901002E02C00F +:10367000959587950A94E2F780FFF3CF33E0C7551D +:10368000DF4F3883C95AD040802F57D18823A9F003 +:103690008BE03AD1CB54DF4F8881C55BD04034D129 +:1036A000CA54DF4F8881C65BD0402ED1C954DF4F4A +:1036B0008881C75BD04028D117D1E02FF0E0EE0F12 +:1036C000FF1FE050FF4F8181886F8183C555DF4F19 +:1036D0004881CB5AD040842F90E0002E02C09595AF +:1036E00087950A94E2F780FD40C062E370E0CE0166 +:1036F0008B559F4F90DBC755DF4F5881C95AD0403B +:103700005150C755DF4F5883C95AD0408AEBE82E35 +:10371000DADBAA2009F4F0CD109279022FDC98EBC5 +:10372000F91226C080E090E0E0E0F0E02491820F02 +:10373000911D3196E11527E1F207C1F748165906A8 +:10374000B9F403E010E0812FF8D0882329F088EE47 +:10375000DBD08AEED9D0C8D01F5F1630A1F7015058 +:1037600089F74DD1992031F06FCF68E770E0BFCF76 +:1037700083E02EDC111188CFCBCFC755DF4FF88106 +:10378000C95AD040FF2309F43FCF5ECFF89404E03C +:103790000EBF0FEF0DBF7894C3DC80E0F7DF90D150 +:0637A00081E0F4DF15DCFE +:1038000031C00000FDCF0000FBCF0000F9CF000069 +:10381000C2CB000028C1000027C10000F7C1000092 +:103820008BC10000BCCF00009EC10000F1C00000B1 +:10383000ABC1000028C10000ACCB0000B3CF00003A +:10384000E5C10000FFFEFDFBFAF8F5F2EEEAE6E066 +:10385000DAD3CCC3BAB0A5998C7E6F5F4E3B2813E8 +:103860000D090301112406DCFC012281339690E04E +:10387000215018F08191980FFBCF81E0923D09F023 +:1038800080E008952E9870982F9871985C985A98B7 +:1038900008958FEF87BD88BD16BC95B5906895BD1E +:1038A00095B5906495BD93EA94BD93E29093B00072 +:1038B00090E89093B3008093B4001092B200E1EBD3 +:1038C000F0E080818064808383B5806883BD83B5A8 +:1038D000826083BD83B5816083BD82E085BD8083C6 +:1038E00083B58F7783BD089580EB97E189DAEDE4A6 +:1038F000F1E0118213AA1092B2011092E401109229 +:1039000016021092480208950F931F93CF93DF93EE +:10391000082FC8E0D0E011E0812F802319F08CE05F +:1039200096E002C08EED9EE06BDA110F2197A1F7B1 +:10393000DF91CF911F910F910895F89442E3849FF6 +:10394000F0011124E35BFE4F95A5911103C091E0B6 +:10395000918301C090E078949923C1F121E030E097 +:10396000A90102C0440F551F8A95E2F7CA012091B0 +:1039700081002111FCCF98E190938100109280008A +:10398000109285001092840021E030E0309389008D +:103990002093880093E0909380002CE036E03093F1 +:1039A000890020938800B19A90916F009260909363 +:1039B0006F0080938A008AB581608ABD89E1809317 +:1039C000810080EB97E11CDA81E0089580E00895A2 +:1039D00080917A0086FDFCCF8091790096EC9093DF +:1039E0007A00089586EC80937A008EE680937C00BE +:1039F0008DE58A95F1F70000EBDFEADFE9CFF89477 +:103A000094DA04E00EBF0FEF0DBF78940C94000021 +:103A10001F920F920FB60F9211242F938F939F93A3 +:103A200090918A0086B1892397B993B92AE12A95A2 +:103A3000F1F700C093B986B995E09A95F1F70000C7 +:103A400017B886B90AB400FC03C01092810003C005 +:103A50008AB58E7F8ABD9F918F912F910F900FBE57 +:103A60000F901F90189518951F920F920FB60F92F6 +:103A700011248F9386B5809327018F910F900FBEED +:103A80000F901F90189590E8899FF0011124F894E9 +:103A900083E080935700E895789407B600FCFDCF4B +:103AA000F89485E080935700E895789407B600FC79 +:103AB000FDCFF89481E180935700E89578940895BC +:103AC0000F931F93CF93DF9310E020E037E101E0E5 +:103AD00080E090E0E901C80FD91F8038910569F0B6 +:103AE000FE0145915491F894FC010A01009357009E +:103AF000E895112478940296EDCF812FC4DF1F5FE3 +:103B00001E3211F09E01E4CFDF91CF911F910F91F2 +:103B10000895E0E0F0E091E0F894DB012D913D9113 +:103B2000BD01090190935700E895112478943296CD +:103B3000E038F10589F7A7CF81E080930C018091EF +:103B40000C018111FCCFBFC9E0E0F1E081939193BA +:103B500021E0EC30F207D1F7EFCF3ADF8A3710F0EF +:103B600082E036CA0895F894E1E3F1E08081918122 +:103B7000A281B381805E9D4EA54FBF4F8583968302 +:103B8000A783B08778940895F89480E880936100C3 +:103B900081E080936100789415BC74DE1092B100CE +:103BA00014BC1092B0006B9810927A002FEF80E74F +:103BB00092E0215080409040E1F700C000004F9B10 +:103BC000FECF2FEF80E792E0215080409040E1F758 +:103BD00000C0000080916D00806880936D0085E0DA +:103BE00083BF889580916D008F7780936D00FADE9A +:103BF00050DEB3DFF89480E880936100109261009A +:103C00007894BCD9B0DF1092410108951F920F92B1 +:103C10000FB60F921124EF92FF920F931F932F93E1 +:103C20003F934F935F936F937F938F939F93AF9344 +:103C3000BF93CF93DF93EF93FF9316B1109516B90F +:103C400017B985E18A95F1F7000095E09A95F1F7AB +:103C5000000017B816B978948091B20087FFBDC0F4 +:103C6000C091350180E28C0F8D3708F4B3C080918C +:103C7000310190913201A0913301B091340101964C +:103C8000A11DB11D8093310190933201A0933301A6 +:103C9000B093340140913601509137016091380161 +:103CA00070913901481759076A077B0708F46CDFE0 +:103CB00080913F0169B1562F5095551F5527551FCB +:103CC00090913E0140914201209143013091440185 +:103CD000591349C04111415067FD2FC021153105CD +:103CE00029F02150310911F41092450160914701EA +:103CF000709148016F5F7F4F709348016093470157 +:103D0000613797E1790730F080681092480110928E +:103D1000470149C0693B9BE0790710F0806443C0CC +:103D2000613B744008F43FC09091460191113BC043 +:103D3000826091E09093460136C02115310599F1DA +:103D40002150310981F590914501913011F488603D +:103D500007C0923011F4806103C0806290934001EB +:103D60001092450120C041111DC09091450167FD91 +:103D70000BC081609F3F09F09F5F10924801109235 +:103D800047012AE431E007C084602115310509F4B8 +:103D900090E0109246019093450150933E0144E11A +:103DA00091E002C044E190E080933F0140934201E2 +:103DB00030934401209343019111D5DE8091280175 +:103DC000811106C081E080932801C7DE109228018E +:103DD00083EA8C0F8093350173C080912601909106 +:103DE0002501C92FD0E0823009F441C028F488238E +:103DF00051F08130C9F062C0833009F445C084308D +:103E000009F451C05BC03EDDCC0FDD1FCC0FDD1FC0 +:103E1000C050DF4F8F858F3F29F083E28093B00041 +:103E2000539A6B9A81E04AC06B98923081F030F4DB +:103E3000992359F0913081F4709A0EC0943059F062 +:103E400040F0953049F45A9A07C02E9A05C02F9A2F +:103E500003C0719A01C05C9ACC0FDD1FCC0FDD1F2F +:103E6000C050DF4F8F858093B40082E027C08FEF72 +:103E70008093B400CC0FDD1FCC0FDD1FC050DF4F8F +:103E80008D8587BD83E01AC053988093B0008FEF73 +:103E900087BDCC0FDD1FCC0FDD1FC050DF4F8E85DF +:103EA00088BD84E00BC08FEF88BD9F5F963019F40A +:103EB00010920C0190E09093250180E08093260100 +:103EC000C7E4D2E000E2F12CEE24E3949CA5802F1D +:103ED000812309F43EC08DA59430D0F48823B1F13C +:103EE000923010F490E801C090E0282F2695922B94 +:103EF00080FF2BC08A81883248F48A81FE01E80F56 +:103F0000F11D93838A818F5F8A8338C01A8214C01F +:103F10009730E0F4803891F49A819923B9F0E882DF +:103F20008B818B3049F48A81843031F4CE019CDC62 +:103F3000882311F0FF24F39480E008C09881911148 +:103F400005C099819923C9F001C0892F8DA71CA6AE +:103F500006C0983018F49F5F9CA701C01DA6E29789 +:103F6000069581E0CB31D80709F0B0CFFF2041F0B2 +:103F700080917902811104C010DC1A8280E8E6CFBA +:103F8000FF91EF91DF91CF91BF91AF919F918F9171 +:103F90007F916F915F914F913F912F911F910F9161 +:0E3FA000FF90EF900F900FBE0F901F9018959E +:0400000300003800C1 +:00000001FF diff --git a/bootloaders/readme.MD b/bootloaders/readme.MD new file mode 100644 index 00000000..4b36726a --- /dev/null +++ b/bootloaders/readme.MD @@ -0,0 +1,13 @@ +This folder contains the BlinkBIOS bootloader HEX file. This file is created by the BlinkBIOS repo... + +https://github.com/bigjosh/Move38-BlinkBIOS + +..and then copied here after each compile by an Atmel Studio post-build event. Note that the this bootloader directory is hardcoded into the text of that event. + +This file is used by... + +1. The `upload` and `program` recipes to download a new sketch to a blink. Note that we could make upload only update the actual program and not the BIOS for faster turnaround during development, but I do not want to change this behavior now. The path to the bootloader is hardcoded into the recipes but is relative to the platform folder. +2. The `Export Binary` function which creates a single HEX file with both the current sketch and the BIOS. In this case, the folder name must be `bootloader` as this is hardcoded into the IDE. + + + \ No newline at end of file diff --git a/cores/blinkcore/IR.md b/cores/blinkcore/IR.md deleted file mode 100644 index 8f7b97ec..00000000 --- a/cores/blinkcore/IR.md +++ /dev/null @@ -1,51 +0,0 @@ -### IR Communications Protocol - -Each of the 6 edges of a blink tile has an IR LED to communicate with its adjacent neighbor. - -#### Physical layer - -Each single IR LED is used for both transmit and receive. The LED pins are connected directly to GPIO pins on the MCU. - -For transmitting, the LED is briefly driven with a forward voltage to flash it. These flashes are short to save power. - -For receiving, the LED is negatively biased to charge it up like a capacitor. Light falling on the LED makes it discharge more quickly, and by timing how long it takes to discharge we can measure the integral how much light is hitting it. [This technique](https://www.sparkfun.com/news/2161) was originally described by Forest Mimms III. - -We've chosen our LED to... - -* quickly reliably discharge below the digital trigger voltage of our MCU input pins when exposed to a >10us flash from a corresponding transmitting LED at a distance of about 1mm, - -AND - -* take >1ms to discharge in ambient light. - - -##### DDR and PORTs of IR LEDs during different states - - -| State | Anode DDR | Anode PORT | Cathode DDR | Cathode PORT | Notes | -| --- | --- | --- | --- | --- | --- | -| Charging | High (driving) | Low | High (driving) | High | Led reverse biased | -| Listening | High (driving) | Low | Low (input) | Low (no pull-up) | Cathode sees input go low when charge depleted | -| Sending | High (driving) | High | High (driving) | Low | Normal forward voltage lights LED | - -Note that Anode DDR is always driven so we never touch that after enabling IR system. - -#### Receiving - -Higher level code can use `ir_test_and_charge()` to check the digital state of the LEDs to see if they have been discharged since last charged. Any LEDs that have discharged are recharged. - -It is also possible to generate an interrupt when an LED crosses the digital threshold voltage, but this is not currently used. - -#### Transmitting - -All transmissions are a series of flashes separated by a number of gaps times called spaces. The space time is typically fixed for a given protocol. - -The gaps are precisely timed and will be accurate to within the latency of interrupts on the system, so keep interrupt disabled for as little time as possible. - -Call `ir_tx_start()` to begin transmitting a pulse train. This sends the first flash. - -Then call `ir_tx_sendpulse()` to transmit each of the remaining flashes in the pulse train. Obviously you must call `ir_tx_sendpulse()` quickly enough that you keep up with the specified number of spaces from the previous call. - -Call `ir_tx_end()` at the end of the train. This will block until the final flash is transmitted. - - \ No newline at end of file diff --git a/cores/blinkcore/Interrupts.md b/cores/blinkcore/Interrupts.md deleted file mode 100644 index adfb6ba1..00000000 --- a/cores/blinkcore/Interrupts.md +++ /dev/null @@ -1,65 +0,0 @@ -# Blinks Interrupt Usage - -## `TIMER2_OCR2A_vect` - -Fires every time Timer2 matches with 128 (max is 256). - -We needed a 256us clock to properly do IR comms, but there is no suitable prescaller that get us this rate so we had to synthesize it. - -We do this by combining the overflow interrupt on timer0 with the match interrupt on timer2. Both are running in lockstep at 512us rate, and so by setting the match on timer2 to 128, we get one interrupt every 256us. - -On each match, we call the 256us clock callbacks. - -## `TIMER0_OVF_vect` - -Fires every time the Timer0 overflows. - -This timer directly drives the RED and GREEN LEDs via PWM output. It is also synchronized with Timer2 whose output drives the BLUE LED though a charge pump. - -It is run off the 4Mhz system clock with a `/8` prescaller, giving a tick rate of 500Khz. - -The overflow happens every 256 ticks, giving an interrupt rate of ~2KHz (every 512ms). - -On each overflow, we first call the 256us clock callbacks, then the 512us clock callback. - -### Pixel Refresh - -This code lives in `blinkcore`. - -This code sets up the PWM signals to step though each of the 3 colors individually, and though all 6 RGB LEDs. - -This code takes between 9us and 16.6us - -### 256us Timer Callback - -This code lives in `blinklib` & `irdata`. - -This code... - -Checks the IR LEDs for triggers and decodes incoming data. - - -### 512us Timer Callback - -This code lives in `blinklib`. - -This code... -2. Updates the millisecond clock. -3. Checks if the button state has changed and decodes clicks and pressed. -4. Checks to see if we have timed out and should go to sleep. - -## `BUTTON_ISR` - -Currently points to an `EMPTY_INTERRUPT`. - -We need some ISR here because we use the button to wake from sleep, but it effectively only takes a few us to call and return. - -## `TIMER1_CAPT_vect` - -We use this interrupt to precisely time outgoing IR pulses. It is only used when actually sending a pulse train. - -It takes about 23us. - -## `WDT_vect` - -Currently a placeholder function. We will need this to implement partial sleeping where we want to wake up after a certain period of time. diff --git a/cores/blinkcore/README.md b/cores/blinkcore/README.md deleted file mode 100644 index ad7a08c6..00000000 --- a/cores/blinkcore/README.md +++ /dev/null @@ -1,60 +0,0 @@ -### Hardware Abstraction Layer Core Files - -These functions provide low level access to the hardware. The `Blinks` API is built on top of these functions and provides a higher level view more that is more appropriate for most game developers. - -Reasons to use this HAL: - -1. You want to do something not supported in the higher level API. In this case, you can include the `.h` file for the subsystem you need direct access to directly in your sketch, but you have to be careful not to step on the toes of the other API calls. -2. You want to make your own higher programming model. Wish you could program your Blinks using an SmallTalk actor based model? Or an ELM functional state transformation model? You can build is from scratch starting with these functions! - - -#### Conventions - - -Each subsystem has a `.h` file and a `.cpp` file. - -Most subsystems offer these base functions... - -`subsystem_init()` - On time hardware initialization run at power up. -`subsystem_enable()` - Start operation. Called after either `_init` or `_disable` -`subsystem_disable()` - Stop the subsystem to save power. Called after `_enable` - - -#### Disabling interrupts - -Both the IR communications and the display RGB LED systems are very timing sensitive, so it is important that interrupts never be turned off for too long. - -The maximum allowed interrupt latency is dependent on clock speed and other factors that are still changing, so I can't give you a hard answer now- but in general try to only turn off interrupts for a few instructions. If you need atomicity longer than that, you a higher level semaphore or something like that. - -#### Asynchronous Callbacks - -To support asynchronous callbacks without impacting interrupt latency, the HAL implements a pseudo interrupt system where the hardware interrupts are intercepted by the HAL and then re-dispatched to the callback functions. - -Interrupts are enabled while callback functions are running, but only one instance of a callback can be active at a time. If another asynchronous event occurs while a callback is already running, the new callback is held until the running callback completes. Multiple events that would trigger the same callback are batched into a single call. - -This means that... - -2. Even though interrupts are enabled, you callback will never be interrupted by another copy if itself -2. If another trigger events happens while your callback is running, it will get called again right after the running copy returns -3. If multiple trigger events happen while your callback is running, you may only get called once more when your currently running callback returns. -4. If a triggering event occurs, your callback will always get called, even if the trigger disappears before the callback is invoked. -4. You should always write callbacks so they can deal with being called even if the triggering event is no longer present. -5. Any variables that are accessed only within your callback do not need to be `volatile` since there will only ever be one copy of the callback running. -6. Any variables that are access by the foreground should be declared as `volatile` from the foreground process's point of view since these could be updated asynchronously by the callback. -7. Any variables that are shared between different callbacks should be declared as `volatile` from the reading callback's point of view since that callback could be interrupted by another callback. - -#### The DEBUG subsystem - -It can be hard to debug code on a little board with no screen or keyboard. The `DEBUG` subsystem gives you at least a couple of bits of IO to interact with your program during debugging. - -To use the `DEBUG` subsystem you must `#define DEBUG` a the top of your source file(s). - -The debug system uses pins #6 and #19 for communications. Hopefully these will be broken out on future PCB versions. - - -#### Other files - -`Arduino.h` is automatically included in all Arduino sketches and defines some of the the Arduino API functions that make sense on Blinks. - -`WMath.cpp` is taken from the Arduino core. It provides wrappers for some `random()` functions from `stdlib` and the `map()` function. - \ No newline at end of file diff --git a/cores/blinkcore/SP.md b/cores/blinkcore/SP.md deleted file mode 100644 index 2187c3d3..00000000 --- a/cores/blinkcore/SP.md +++ /dev/null @@ -1,94 +0,0 @@ -# Service Port - - -## Features - -Newer boards have a service port that features... - -* Super high speed, low latency, bi-directional serial port -* Aux pin that can be used for either digital IO or as an analog input - -These can be very helpful for... - -* debugging code on the tile since you get a printf() (via the serial port) -* collecting very accurate timing info on a scope (via the a Aux pin as digital out) -* intuitively to adjusting things like colors (using a potentiometer connected to the the aux pin as analog in) - -## Connections - -To use the port, you probably want to solder on a 4 pin 1.25mm pitch connector like this one... - -https://www.digikey.com/products/en?keywords=wm1733-nd - -The pins are... - -|Pin|Function|Direction| -|---|---|---| -|G|Ground|(na)| -|T|Serial TX|Out from tile| -|R|Serial RX|Into Tile| -|A|Aux pin|digital in or out with or without pull-up, or analog in to Tile| - -At some point I'll make a nice board that breaks out the service port connector into nice serial and aux links with all the level shifting built in. - -## Voltage conversions - -Note that all the pins are referenced to whatever the tile battery voltage happens to be, so level shifting may be required. - -### Serial TX -I use a FDTI USB serial converter that will see a '1' for any voltage above 2 volts, so the RX pin on that can be connected directly to the service port `T` pin. Otherwise you could use a MOSFET or packaged voltage converter like this... - -http://amzn.to/2zjSx8t - -### Serial RX - -Because of the ESD protection diode on the pin of the AVR, you can connect the service port 'R' pin to a 3.3V (or even a 5V) output TX pin of a serial port as long as you put a current limiting resistor in there. I am using a 3.3K resistor which should limit the current to less than 1mA. - -There is a downside to this in that that trickle current will keep the AVR somewhat powered up even with the battery out. Since the current is limited, it will undervoltage if it tries to light an LED so as a practical matter this means I need to unplug the service port when changing the battery. - -Again, a proper level convert like above will solve these problems. - -### Aux - -#### Digital Output - -This will just be whatever the battery voltage is when set to out a `1`. If you are just connecting up to a scope then this works great. - -#### Digital Input - -Like the serial RX pin, you could drive this with a voltage higher than the battery voltage though a large current limiting resistor or use a level converter. - -You could also enable the optional pull-up on this pin and then use an open collector (or dry contact switch) to pull low. - -#### Analog input - -In the mode, you get 8-bits that tell you the voltage on the aux pin relative to the battery voltage, so 0 means 0 volts, and 128 means the voltage on the aux pin is 1/2 the battery voltage. - -#### Touch sensing - -The aux pin is also connected electrically to the Move30 logo on the PCB, so this opens up the possibly to do CapSense touch sensing on this pad by charging the pad using aux digital out to `1`, and then switching to analog input and seeing how much the voltage drops each time you do a reading. This is an indication of the capacitance connected to the pin - which will be much higher when there is a finger connected to a person touching it. - -# On Latency - -## Serial - -The serial port is implemented using the on-chip USART, so works completely in the background. - -The `sp_serial_tx()` function will first check so see if there is already a pending transmit in progress, and if so will wait for it to complete. The test adds a couple cycles before the send even if the coast is clear, and the call could block for a dozen cycles if there is a transmit in progress that just started. - -if you are working on timing sensitive stuff, you can also use the `SP_SERIAL_TX_NOW()` function. This function does not do any checking- it immediately (and blindly) writes the byte to the serial port, and takes only a single instruction to execute. If the buffer is not ready (there is already a byte waiting to be transferred into the shift register) then the new byte will be ignored. This is not a scary as it sounds since you can just be sure to always leave at least a dozen instructions between sequential calls to give a byte time to drain out before sending the next one. This will likely happen easily in practice since you will be doing other stuff between consecutive serial writes. - -## Aux out - -The `SP_AUX_0()` and `SP_AUX_0()` also always complete in a single cycle, so are great for timing. - -To benchmark how long a function takes, you can put a `SP_AUX_1()` at the entry and a `SP_AUX_0()` at the exit and then connect a scope to the aux pin. Measuring the pulse width will give you instant and minimally intrusive metrics on on the min/avg/max run time for the function! - -# Connecting a potentiometer for analog input. - -Some things are each to do analog-ly. So much easier to turn a dial than to keep clicking up and down for stuff like color normalization. The analog in function of the aux pin should be great for this stuff. - -Since the input is referenced to the battery voltage, we need a good place to get at the reference voltage. - -As long as you init() the service port serial and are not actively sending data, then the pin will be an an idle "space", which is driven at the battery voltage. In this case, you should be able to attach a large pot (>1K) between the `T` and `G` and then connect the pot tap to the `A` pin and read the position of of pot with `sp_aux_analogRead()`. Untested, but should work! - diff --git a/cores/blinkcore/adc.cpp b/cores/blinkcore/adc.cpp deleted file mode 100644 index 8c39ce20..00000000 --- a/cores/blinkcore/adc.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Internal analog to digital converter - * - * We connect the ADC to the battery so we can check its voltage. - * - */ - - -#include "hardware.h" - -#include "adc.h" - -#include "shared.h" - -#include "bitfun.h" - - -// Set up adc to read Vcc -// https://wp.josh.com/2014/11/06/battery-fuel-guage-with-zero-parts-and-zero-pins-on-avr/ - -void adc_init(void) { - - ADMUX = - _BV(REFS0) | // Reference AVcc voltage - _BV( ADLAR ) | // Left adjust result so only one 8 bit read of the high register needed - _BV( MUX3 ) | _BV( MUX2 ) | _BV( MUX1 ) // Measure internal 1.1V bandgap voltage - ; - - - // Set the prescaller based on F_CPU - // Borrowed from https://github.com/arduino/Arduino/blob/2bfe164b9a5835e8cb6e194b928538a9093be333/hardware/arduino/avr/cores/arduino/wiring.c#L353 - - // set a2d prescaler so we are inside the desired 50-200 KHz range. - #if F_CPU >= 16000000 // 16 MHz / 128 = 125 KHz - SBI(ADCSRA, ADPS2); - SBI(ADCSRA, ADPS1); - SBI(ADCSRA, ADPS0); - #elif F_CPU >= 8000000 // 8 MHz / 64 = 125 KHz - SBI(ADCSRA, ADPS2); - SBI(ADCSRA, ADPS1); - CBI(ADCSRA, ADPS0); - #elif F_CPU >= 4000000 // 4 MHz / 32 = 125 KHz - SBI(ADCSRA, ADPS2); - CBI(ADCSRA, ADPS1); - SBI(ADCSRA, ADPS0); - #elif F_CPU >= 2000000 // 2 MHz / 16 = 125 KHz - SBI(ADCSRA, ADPS2); - CBI(ADCSRA, ADPS1); - CBI(ADCSRA, ADPS0); - #elif F_CPU >= 1000000 // 1 MHz / 8 = 125 KHz - CBI(ADCSRA, ADPS2); - SBI(ADCSRA, ADPS1); - SBI(ADCSRA, ADPS0); - #else // 128 kHz / 2 = 64 KHz -> This is the closest you can get, the prescaler is 2 - CBI(ADCSRA, ADPS2); - CBI(ADCSRA, ADPS1); - SBI(ADCSRA, ADPS0); - #endif - -} - - -// Enable ADC and prime the pump (it takes 2 conversions to get accurate results) - -void adc_enable(void) { - - // enable a2d conversions - SBI(ADCSRA, ADEN); - SBI( ADCSRA , ADSC); // Kick off a primer conversion (the initial one is noisy) - while (TBI(ADCSRA,ADSC)) ; // Wait for 1st conversion to complete - - SBI( ADCSRA , ADSC); // Kick off 1st real conversion (the initial one is noisy) - - - -} - -// Disable and power down the ADC to save power - -void adc_disable(void) { - - CBI(ADCSRA, ADEN); // Disable ADC unit - -} - - -// Start a new conversion. Read the result ~1ms later by calling adc_readLastVccX10(). -// 1ms is safe, but if you need faster then conversion will actually be ready in -// 13 CPU cycles * ADC prescaller (25 cycles for 1st conversion) - -void adc_startConversion(void) { - SBI( ADCSRA , ADSC); // Start next conversion, will complete silently in 13 cycles (25 cycles for 1st) -} - -// Returns the previous conversion result (call adc_startConversion() to start a conversion). -// Blocks if you call too soon and conversion not ready yet. - -uint8_t adc_readLastVccX10(void) { // Return Vcc x10 - - while (TBI(ADCSRA,ADSC)) ; // Wait for any pending conversion to complete - - uint8_t lastReading = (11 / ADCH); // Remember the result from the last reading. - - return( lastReading ); - -} - - - diff --git a/cores/blinkcore/adc.h b/cores/blinkcore/adc.h deleted file mode 100644 index 5722bc69..00000000 --- a/cores/blinkcore/adc.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Internal analog to digital converter - * - * We connect the ADC to the battery so we can check its voltage. - * - */ - -#ifndef ADC_H_ -#define ADC_H_ - -#include - -// Initialize the ADC and start the first conversion - -void adc_init(void); - -// Enable ADC and prime the pump (it takes 2 conversions to get accurate results) - -void adc_enable(void); - -// Start a new conversion. Read the result ~1ms later by calling adc_readLastVccX10(). -// 1ms is safe, but if you need faster then conversion will actually be ready in -// 13 CPU cycles * ADC prescaller (25 cycles for 1st conversion) - -void adc_startConversion(void); - -// Returns the previous conversion result. -// Blocks if conversion not ready yet. -// The value is the voltage of the battery * 10. -// So 30 = 3.0 volts - -uint8_t adc_readLastVccX10(void); // Return Vcc x10 - -// Disable and power down the ADC to save power - -void adc_disable(void); - -#endif /* ADC_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/bitfun.h b/cores/blinkcore/bitfun.h deleted file mode 100644 index 469d45d1..00000000 --- a/cores/blinkcore/bitfun.h +++ /dev/null @@ -1,12 +0,0 @@ - - -#ifndef BITFUN_H_ -#define BITFUN_H_ - -// Bit manipulation macros - #define SBI(x,b) (x|= (1< - -#include "button.h" -#include "bitfun.h" - -// Callback that is called when the button state changes. -// Note that you could get multiple consecutive calls with the -// Same state if the button quickly toggles back and forth quickly enough that -// we miss one phase. This is particularly true if there is a keybounce exactly when -// and ISR is running. - -// Use BUTTON_DOWN() to check buttonstate when called. - -// Confirmed that all the pre/postamble pushes and pops compile away if this is left blank - -// Weak reference so it (almost) compiles away if not used. -// (looks like GCC is not yet smart enough to see an empty C++ virtual invoke. Maybe some day!) -// REMOVED WEAK REFERENCE! THE COMPILER SOMETIMES WOULD LINK THE WEAK RATHER THAN THE STRONG! WTF GCC?!?! -// So for now, you must supply empty stubs. :/ - - -struct ISR_CALLBACK_BUTTON : CALLBACK_BASE { - - static const uint8_t running_bit = CALLBACK_BUTTON_RUNNING_BIT; - static const uint8_t pending_bit = CALLBACK_BUTTON_PENDING_BIT; - - static inline void callback(void) { - - button_callback_onChange(); - - } - -}; - - -void button_init(void) { - // Pin change interrupt setup - SBI( PCICR , BUTTON_PCI ); // Enable the pin group -} - -// Enable pullup and interrupts on button - -void button_enable(void) { - - // GPIO setup - SBI( BUTTON_PORT , BUTTON_BIT); // Leave in input mode, enable pull-up - -} - - -// Disable pull-up -// You'd want to do this to save power in the case where the -// button is stuck down and therefore shorting out the pull-up - -void button_disable(void) { - - - CBI( BUTTON_PORT , BUTTON_BIT); // Leave in input mode, disable pull-up - -} - - -// Returns 1 if button is currently down - -uint8_t button_down(void) { - - return BUTTON_DOWN(); - -} - - - -ISR(BUTTON_ISR) -{ - ISR_CALLBACK_BUTTON::invokeCallback(); -} - -// Enable callback to button_callback_onChange on button change interrupt -// Typically used to wake from sleep, but could also be used for low latency -// button decoding - but remember that there is not 1:1 mapping of changes on the -// button pin to calls to the ISR, so timer-based button decoding likely easier -// and more efficient. - -void button_ISR_on(void) { - // Pin change interrupt setup - SBI( BUTTON_MASK , BUTTON_PCINT); // Enable pin in Pin Change Mask Register - -} - -void button_ISR_off(void) { - // disable pin change interrupt - CBI( BUTTON_MASK , BUTTON_PCINT); // Disable pin in Pin Change Mask Register -} diff --git a/cores/blinkcore/button.h b/cores/blinkcore/button.h deleted file mode 100644 index b25749f4..00000000 --- a/cores/blinkcore/button.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * button.h - * - */ - -#ifndef BUTTON_H_ -#define BUTTON_H_ - -#include - -// Call once at power up. - -void button_init(void); - -// Enable pullup on button -// TODO: We need a way to disable pull-ups in case the button is stuck down in a pocket or drawer. - -void button_enable(void); - -// Returns 1 if button is currently down - -uint8_t button_down(void); - -// Disable pull-up -// You'd want to do this to save power in the case where the -// button is stuck down and therefore shorting out the pull-up - -void button_disable(void); - -// User supplied callback that is called when the button state changes. -// Note that you could get multiple consecutive calls with the -// Same state if the button quickly toggles back and forth quickly enough that -// we miss one phase. This is particularly true if there is a keybounce exactly when -// an ISR is running. - -// I know that you think you want to know if the button was up or down passed to the callback -// but that is false security. You never know how long the button will be down for, and you don't -// know the interrupt latency between the button change that triggers the interrupt and when -// you are going to get to sample it. Heck, even if there was 0 interrupt latency, the button could -// still contact long enough to trigger the interrupt but not to show up on the pin read. -// Much more rigorous to think in terms of state changes than absolute values. - - -void button_callback_onChange(void); - - -// Enable callback to button_callback_onChange on button change interrupt -// Typically used to wake from sleep, but could also be used for low latency -// button decoding - but remeber that there is not 1:1 mapping of changes on the -// button pin to calls to the ISR, so timer-based button decoding likely easier -// and more efficient. - -// Call after button_enable() - -void button_ISR_on(void); - -// Call before button_disable() - -void button_ISR_off(void); - - -#endif /* BUTTON_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/callbacks.h b/cores/blinkcore/callbacks.h deleted file mode 100644 index 8d72318d..00000000 --- a/cores/blinkcore/callbacks.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * callbacks.h - * - * The user provides these functions to be called by the platform. - * - */ - -#ifndef CALLBACKS_H_ -#define CALLBACKS_H_ - -#include "bitfun.h" - -#include -#include - -// Some helpers for making asynchronous callbacks without leaving interrupts off for too long -// We do this by catching the hardware interrupt and then enabling interrupts before calling the -// callback. We then use a flag to prevent re-entering the callback. If the flag is set when the -// callback completes, then we call into it again so it can see that another interrupt happened -// while it was running. This effectively combines all interrupts while the callback is running into -// a single trailing call. Note that the process repeats if another interrupt happens during that -// trailing call. - -// TODO: Make these ISR naked so we can really optimize reentrant interrupts into just -// an SBI and a iret. - -// Keep all these callback flags in register GPIOR) because we can SBI and CLI -// on this register with a single instruction that does not change anything else. - -#define CALLBACK_REG GPIOR0 - -// Each reentrant interrupt gets two bits in this register -// The `running` bit means that the callback is currently running, so do not re-eneter it -// The `pending` bit means that another interrupt has happened while the running one was executing so -// we need to call again when it finishes. - - -#define CALLBACK_BUTTON_RUNNING_BIT 0 -#define CALLBACK_BUTTON_PENDING_BIT 1 - -#define CALLBACK_IR_RUNNING_BIT 2 -#define CALLBACK_IR_PENDING_BIT 3 - -#define CALLBACK_TIMER_RUNNING_BIT 4 -#define CALLBACK_TIMER_PENDING_BIT 5 - -#define CALLBACK_PIXEL_FRAME_RUNNING_BIT 6 -#define CALLBACK_PIXEL_FRAME_PENDING_BIT 7 - - - -// Below is a glorified macro so we do not need to retype the invovkeCallback -// code for each call back - but we still get very clean and short ISR code -// at compile time without function pointers. - -// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern - -template -struct CALLBACK_BASE { - - // Fill in the callback function and bits here. - - static void inline callback(void); - - static const uint8_t running_bit; - static const uint8_t pending_bit; - - // General code for making a call back - - static void inline invokeCallback(void) { - - if ( TBI( CALLBACK_REG , T::running_bit ) ) { - - // Remember that we need to rerun when current one is finished. - - SBI( CALLBACK_REG , T::pending_bit ); - - } else { - - // We are not currently running, so set the gate bit... - - SBI( CALLBACK_REG , T::running_bit ); - - do { - CBI( CALLBACK_REG , T::pending_bit); - sei(); - T::callback(); - cli(); - } while ( TBI( CALLBACK_REG , T::pending_bit ) ); - - CBI( CALLBACK_REG , T::running_bit); - - } - } -}; - - -// TODO: Check all public functions to make sure they are reentrant. -// IF not, either guard them or document. - -#endif /* CALLBACKS_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/effects.cpp b/cores/blinkcore/effects.cpp deleted file mode 100644 index 4524f72e..00000000 --- a/cores/blinkcore/effects.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* - * effects.c - * - * Created: 8/13/2017 8:21:22 PM - * Author: passp - */ - -/* - - -void rainbowBreathingEffect(void) { - - while (!effectReturnFlag) { - - - for( int b=0; b<255 && !effectReturnFlag; b+=3) { - - setRGB( b , 0 , 0); - - _delay_ms(10); - - } - - for( int b=255; b>0 && !effectReturnFlag ; b-=3) { - - setRGB( b , 0 , 0); - - _delay_ms(10); - - } - - - delayWithReturnFlag(100); - - - - for( int b=0; b<255 && !effectReturnFlag ; b+=3 ) { - - setRGB( 0 , b , 0); - - _delay_ms(10); - - } - - for( int b=255; b>0 && !effectReturnFlag; b-=3) { - - setRGB( 0 , b , 0); - - _delay_ms(10); - - } - - - delayWithReturnFlag(100); - - - for( int b=0; b<255 && !effectReturnFlag; b+=3 ) { - - setRGB( 0 , 0 , b); - - _delay_ms(10); - - } - - for( int b=255; b>0 && !effectReturnFlag; b-=3) { - - setRGB( 0 , 0 , b); - - _delay_ms(10); - - } - - delayWithReturnFlag(100); - - } - -} - - -void rainbowFadeEffect(void) { - - - while (!effectReturnFlag) { - - - for( int h=0; h<255 && !effectReturnFlag; h++ ) { // Fade hue steps - - // uint8_t h = (a + (( 256 * p)/PIXEL_COUNT)) & 255; - - for( uint8_t p=0; p -#include // Must come after F_CPU definition - -#include "ir.h" -#include "utils.h" - -#include "callbacks.h" - -// A bit cycle is one timer tick, currently 512us - -//TODO: Optimize these to be exact minimum for the distance in the real physical object -//TODO: This can likely be reduced or eliminated when we increase sampling rate - -#define IR_CHARGE_TIME_US 2 // How long to charge the LED though the pull-up - -// Currently chosen empirically to work with some tile cases Jon made 7/28/17 - -#define IR_PULSE_TIME_US 10 // Used for sending flashes - - -#if IR_ALL_BITS != IR_BITS - - #error Code assumes IR_ALL_BITS and IR_BITS are equivalant. If not, you need to map them manually. - -#endif - -// This gets called anytime one of the IR LED cathodes has a level change drops. This typically happens because some light -// hit it and discharged the capacitance, so the pin goes from high to low. We initialize each pin at high, and we charge it -// back to high everything it drops low, so we should in practice only ever see high to low transitions here. - -// TOOD: We will use this for waking from nap. - -ISR(IR_ISR) { - - // EMPTY - -} - - -// We use the general interrupt control register to gate interrupts on and off rather than the mask - -void ir_enable(void) { - - // TODO: Call this interrupt enable() - - // This must come before the charge or we could miss a change that happened between the charge and the enable and that would - // loose the LED out of the cycle forever - - // TODO: IR interrupts totally disabled for now. We will need them for wake on data. - - //SBI( PCICR , IR_PCI ); // Enable the pin group to actual generate interrupts - - // There is a race where an IR can get a pulse right here, but that is ok becuase it will just generate an int and be processed normally - // and get recharged naturally before the next line. - - // Initial charge up of cathodes to get things going - //chargeLEDs( IR_BITS ); // Charge all the LEDS - this handles suppressing extra pin change INTs during charging - -} - -void ir_disable(void) { - - CBI( PCICR , IR_PCI ); // Disable the pin group to block interrupts - -} - -void ir_init(void) { - - - IR_ANODE_DDR |= IR_BITS ; // Set all ANODES to drive (and leave forever) - // The PORT will be 0, so these will be driven low - // until we actively send a pulse - - // Leave cathodes DDR in input mode. When we write to PORT, then we will be enabling pull-up which is enough to charge the - // LEDs and saves having to switch DDR every charge. - - - // Pin change interrupt setup - IR_MASK |= IR_PCINT; // Enable pin in Pin Change Mask Register for all 6 cathode pins. Any change after this will set the pending interrupt flag. - // TODO: Single LEDs can get masked here if they get noisy to avoid spurious wakes - -} - - -// Send a pulse on all LEDs that have a 1 in bitmask -// bit 0= D1, bit 1= D2... -// This clobbers whatever charge was on the selected LEDs, so only call after you have checked it. - -// Must be atomic so that... -// 1) the IR ISR doesn't show up and see our weird registers, and -// 2) The flashes don't get interrupted and stretched out long enough to cause 2 triggers - -// TODO: Queue TX so they only happen after a successful RX or idle time. Unnecessary since TX time so short? - -// ASSUMES INTERRUPTS OFF!!!! -// TODO: Make a public facing version that brackets with ATOMIC - -// TODO: Incorporate this into the tick handler so we will be charging anyway. - -static inline void ir_tx_pulse_internal( uint8_t bitmask ) { - - - // TODO: Check for input before sending and abort if found... - // TODO: Maybe as easy as saving the cathode values and then only charging again if they were high? - - // Remember that the normal state for IR LED pins is... - // ANODE always driven. PORT low when we are waiting to RX pulses or charging. PORT driven high when transmitting a pulse. - // CATHODE is input when waiting for RX pulses, so DDR not driven and PORT low. CATHODE is driven high when charging and driven low when sending a pulse. - - // See ir.MD in this repo for more explanations - - uint8_t cathode_ddr_save = IR_CATHODE_DDR; // We don't want to mess with the upper bits not used for IR LEDs - - PCMSK1 &= ~bitmask; // stop Triggering interrupts on these cathode pins because they are going to change when we pulse - - // TODO: Current blinklib does not use IR interrupts, so we could get rid of this but would only save a couple instructions/cycles - - // Now we don't have to worry about... - // (1) a received pulse on this LED interfering with our transmit and - // (2) Our fiddling the LED bits causing an interrupt on this LED - - uint8_t savedCathodeBits = IR_CATHODE_PIN & bitmask; // Read the current IR bit states so we can put them back the way they were and preserve any pending received bits - - IR_CATHODE_DDR |= bitmask ; // Drive Cathode too (now driving low) - - // Right now both cathode and anode are driven and both are low - so LED off - - // if we got interrupted here, then the pulse could get long enough to look like 2 pulses. - - // Anode pins are driven output and low normally, so this will - // make them be driven high output - - // NOTE: We are doing something tricky here. Writing a 1 to a PIN bit actually toggles the PORT bit. - // This saves about 10 instructions to manually load, or, and write back the bits to the PORT. - - /* - 19.2.2. Toggling the Pin - Writing a '1' to PINxn toggles the value of PORTxn, independent on the value of DDRxn. - */ - - IR_ANODE_PIN = bitmask; // Blink! (Remember, a write to PIN actually toggles PORT) - - // Right now anode driven and high, so LED on! - - // TODO: Is this the right TX pulse with? Currently ~6us total width - // Making too long wastes (a little?) battery and time - // Making too short might not be enough light to trigger the RX on the other side - // when TX voltage is low and RX voltage is high? - // Also replace with a #define and _delay_us() so works when clock changes? - - _delay_us( IR_PULSE_TIME_US ); - - IR_ANODE_PIN = bitmask; // Un-Blink! Sets anodes back to low (still output) (Remember, a write to PIN actually toggles PORT) - - // Right now both cathode and anode are driven and both are low - so LED off - - // charge up receiver cathode pins while keeping other pins intact - - // TODO: Only recharge pins that we high when we started - - // TODO: These need to be asm because it sticks a load here. - - // Set cathode high for any bit masked pins that were high when we started - - IR_CATHODE_PIN = savedCathodeBits; - - // Cathodes are now being driven high - - _delay_us( IR_CHARGE_TIME_US ); - - PCMSK1 |= bitmask; // Re-enable pin change on the pins we just charged up - // Note that we must do this while we know the pins are still high - // or there might be a *tiny* race condition if the pin changed in the cycle right after - // we finished charging but before we enabled interrupts. This would latch until the next - // recharge timeout. - - - - IR_CATHODE_DDR = cathode_ddr_save; // Cathode back to input too (pull-ups still on) - - IR_CATHODE_PIN = savedCathodeBits; // Disable pull-ups. Everything back to pure input. - -} - - -static inline void chargeLEDs( uint8_t bitmask ) { - - uint8_t ir_cathode_ddr_cache = IR_CATHODE_DDR; // This will not change unless we change it, so no need to reload it every access - - PCMSK1 &= ~bitmask; // stop Triggering interrupts on these pins because they are going to change when we charge them - - // charge up receiver cathode pins while keeping other pins intact - - // This will enable the pull-ups on the LEDs we want to change without impacting other pins - // The other pins will stay whatever they were. - - // NOTE: We are doing something tricky here. Writing a 1 to a PIN bit actually toggles the PORT bit. - // This saves about 10 instructions to manually load, or, and write back the bits to the PORT. - - - /* - 19.2.2. Toggling the Pin - Writing a '1' to PINxn toggles the value of PORTxn, independent on the value of DDRxn. - */ - - IR_CATHODE_PIN = bitmask; // This enables pull-ups on charge pins - - IR_CATHODE_DDR = ir_cathode_ddr_cache | bitmask; // This will drive the charge pins high - - // Empirically this is how long it takes to charge - // and avoid false positive triggers in 1ms window 12" from halogen desk lamp - - _delay_us( IR_CHARGE_TIME_US ); - - - // Only takes a tiny bit of time to charge up the cathode, even though the pull-up so no extra delay needed here... - - - PCMSK1 |= bitmask; // Re-enable pin change on the pins we just charged up - // Note that we must do this while we know the pins are still high - // or there might be a *tiny* race condition if the pin changed in the cycle right after - // we finished charging but before we enabled interrupts. This would latch - // forever. - - // Stop charging LED cathode pins - - - IR_CATHODE_DDR = ir_cathode_ddr_cache; // Back to the way we started, charge pins now only pulled-up - - IR_CATHODE_PIN = bitmask; // toggle pull-ups off, now cathodes pure inputs - -} - -// Measure the IR LEDs to to see if they have been triggered. -// Must be called when interrupts are off. -// Returns a 1 in each bit for each LED that was fired. -// Fired LEDs are recharged. - -uint8_t ir_test_and_charge_cli( void ) { - - // ===Time critcal section start=== - - // Find out which IR LED(s) went low to trigger this interrupt - - // TODO: Could save lots by redoing in ASM - // TODO: Look into moving _zero_reg_ out of R! to save a push/pop and eor. - - uint8_t ir_LED_triggered_bits; - - ir_LED_triggered_bits = (~IR_CATHODE_PIN) & IR_BITS; // A 1 means that LED triggered - - - // If a pulse comes in after we sample but before we finish charging and enabling pin change, then we will miss it - // so best to keep this short and straight - - // Note that protocol should make sure that real data pulses should have a header pulse that - // gets this receiver in sync so we only are recharging in the idle time after a pulse. - // real data pulses should come less than 1ms after the header pulse, and should always be less than 1ms apart. - // Recharge the ones that have fired - - chargeLEDs( ir_LED_triggered_bits ); - - // TODO: Some LEDs seem to fire right after IR0 is charged when connected to programmer? - - // ===Time critcal section end=== - - return ir_LED_triggered_bits; - -} - - -/* - - We need a way to send pulses with precise spacing between them, otherwise - an interrupt could happen between pulses and spread them out enough - that they are no longer recognized. - - We will use Timer1 dedicated to this task for now. We will run in CTC mode - and send our pulses on each TOP. This way we will never have to modify the - counter while the timer is running- possibly loosing counts. - - We will use ICR1 to define the top. - - Mode 12: CTC TOP=ICR1 WGM13, WGM12 - -*/ - -static volatile uint16_t sendpulse_bitmask; // Which IR LEDs to send on -static volatile uint16_t sendpulse_spaces; // Time to delay until next pulse. 0=pulse sent -static volatile uint16_t sendpulse_spaces_next; // A one entry deep buffer for spaces so we can try to keep it primed. - -// Currently clocks at 23us @ 4Mhz - -ISR(TIMER1_CAPT_vect) { - - if (sendpulse_spaces) { - - sendpulse_spaces--; - - if (sendpulse_spaces==0) { - - ir_tx_pulse_internal( sendpulse_bitmask ); // Flash - - sendpulse_spaces = sendpulse_spaces_next; - - sendpulse_spaces_next = 0; - - } - } - -} - -// Send a series of pulses with spacing_ticks clock ticks between each pulse (or as quickly as possible if spacing too short) -// If count=0 then 256 pulses will be sent. -// If spacing_ticks==0, then the time between pulses will be 65536 ticks - -// Assumes timer1 stopped on entry, leaves timer1 stopped on exit -// Assumes timer1 overflow flag cleared on entry, leaves clear on exit - -// TODO: Add a version that doesn't block and lets you specify a margin at the end before the next TX -// can start? Use overflow bit to see if past. Easy? - -// TODO: Use fast PWM mode so OCR1 is buffered. We can then load the margin value -// on the last pass and it will get automatically swapped in after the final pulse. -// We can then quickly test the compare bit to see if the margin is over. - -// TODO: Check cathode and buffer first and don't send if in progress. Return aborted bits -/* - - uint8_t ir_LED_triggered_bits; - - ir_LED_triggered_bits = (~IR_CATHODE_PIN) & IR_BITS; - - */ - -// Sends starting a pulse train. -// Each pulse will have an integer number of delays between it and the previous pulse. -// Each of those delay windows is spacing_ticks wide. -// This sets things up and sends the initial pulse. -// Then continue to call ir_tx_sendpulse() to send subsequent pulses -// Call ir_tx_end() after last pulse to turn off the ISR (optional but saves CPU and power) - -void ir_tx_start(uint16_t spacing_ticks , uint8_t bitmask , uint16_t initialSpaces ) { - - sendpulse_bitmask = bitmask & IR_ALL_BITS; // Protect the non-IR LED bits from invalid input - - ICR1 = spacing_ticks; // We will fire ISR when we hit this, and also roll back to 0. - - TCNT1 = spacing_ticks-1; // Grease the wheels. This will make the 1st pulse go out as soon as we turn on the timer - - SBI( TIFR1 , ICF1 ); // Clear TOP match flag in case it is set. - // "ICF can be cleared by writing a logic one to its bit location." - - - TIMSK1 |= _BV(ICIE1); // Enable the ISR when we hit ICR1 - // TODO: Do this only once at startup - - sendpulse_spaces_next=initialSpaces; // Get our first space into the buffer so it is ready before we start - sendpulse_spaces=1; // Will send as soon as called (immediately after next line). - - // Start clock in mode 12 with /1 prescaller (one timer tick per clock tick) - TCCR1B = _BV( WGM12) | _BV( WGM13) | _BV( CS10); // clk/1 - - // ISR will trigger immediately and send the 1st pulse - -} - -// leadingSpaces is the number of spaces to wait between the previous pulse and this pulse. -// 0 doesn't really make any sense - -// TODO: single buffer this in case sender has a hiccup or is too slow to keep up? - -void ir_tx_sendpulse( uint8_t leadingSpaces ) { - - while (sendpulse_spaces_next); // Wait for previous entry to buffer to pulse to get sent - sendpulse_spaces_next=leadingSpaces; // ISR trigger will end a pulse after specified number of spaces - -} - -// Turn off the pulse sending ISR -// TODO: This should return any bit that had to be terminated because of collision - -void ir_tx_end(void) { - - while (sendpulse_spaces); // Wait for all previous pulses to get sent (buffered and immedeate complete) - - // stop timer (not more ISR) - TCCR1B = 0; // Sets prescaler to 0 which stops the timer. - -} - diff --git a/cores/blinkcore/ir.h b/cores/blinkcore/ir.h deleted file mode 100644 index 7a4b152d..00000000 --- a/cores/blinkcore/ir.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * ir_comms.h - * - * All the functions for communication and waking on the 6 IR LEDs on the tile edges. - * - */ - -#ifndef IR_H_ -#define IR_H_ - -#include "shared.h" // Get FACECOUNT -#include "bitfun.h" - -#define IRLED_COUNT FACE_COUNT - -#define IR_ALL_BITS (0b00111111) // All six IR LEDs - -// Setup pins, interrupts - -void ir_init(void); - -// Enable IR normal operation (call after init or disable) - -// TODO: Specify specific LEDs? - -void ir_enable(void); - -// Stop IR interrupts (call after enable) - -void ir_disable(void); - -// Sends starting a pulse train. -// Each pulse will have an integer number of delays between it and the previous pulse. -// Each of those delay windows is spacing_ticks wide. -// This sets things up and sends the initial pulse. -// Then continue to call ir_tx_sendpulse() to send subsequent pulses -// Call ir_tx_end() after last pulse to turn off the ISR (optional but saves CPU and power) - -void ir_tx_start(uint16_t spacing_ticks , uint8_t bitmask , uint16_t initialSpaces ); - -// Send next pulse int this pulse train. -// leadingSpaces is the number of spaces to wait between the previous pulse and this pulse. -// 0 doesn't really make any sense -// Note that you must called ir_tx_sendpuse fast enough that the buffer doesn't run dry - -void ir_tx_sendpulse( uint8_t leadingSpaces ); - -// Turn off the pulse sending ISR -// Blocks until final pulse transmitted -// TODO: This should return any bit that had to be terminated because of collision - -void ir_tx_end(void); - -// Measure the IR LEDs to to see if they have been triggered. -// Must be called when interrupts are off. -// Returns a 1 in each bit for each LED that was fired. -// Fired LEDs are recharged. - -uint8_t ir_test_and_charge_cli( void ); - - -// Called anytime on of the IR LEDs triggers, which could -// happen because it received a flash or just because -// enough ambient light accumulated - -void __attribute__((weak)) ir_callback(uint8_t triggered_bits); - - -#define WAKEON_IR_BITMASK_NONE 0 // Don't wake on any IR change -#define WAKEON_IR_BITMASK_ALL IR_BITS // Don't wake on any IR change - -#endif /* IR_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/ir_callback.h b/cores/blinkcore/ir_callback.h deleted file mode 100644 index 727304ba..00000000 --- a/cores/blinkcore/ir_callback.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * ir_callback.h - * - * This head file connects the overflow ISR in Timer.cpp to the periodic IR decoder. - * - * I can't think of a more elegant way to do this without the overhead of a functrion pointer, can you? - * This only needs to be included in timer.cpp and ir.cpp. - * - * THis makes me think that IR decode should be handled at the next higher layer. - */ - -#ifndef IR_CALLBACK_H_ -#define IR_CALLBACK_H_ - -void ir_tick_isr_callback(void); - -#endif /* IR-CALLBACK_H_ */ diff --git a/cores/blinkcore/main.cpp b/cores/blinkcore/main.cpp deleted file mode 100644 index c217217f..00000000 --- a/cores/blinkcore/main.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * main.cpp - * - * This gets called first by the C bootstrap code. - * It initializes the hardware and then called run() - */ - -#include "hardware.h" -#include "shared.h" - -#include -#include - -#include "utils.h" -#include "ir.h" -#include "pixel.h" -#include "timer.h" -#include "button.h" -#include "adc.h" -#include "power.h" -#include "callbacks.h" - -#include "run.h" // Prototype for the run function we will hand off to - -// Change clock prescaler to run at 8Mhz. -// By default the CLKDIV fuse boots us at 8Mhz osc /8 so 1Mhz clock -// Change the prescaler to get some more speed but still run at low battery voltage -// We could go to 8MHz, but then we would not be able to run the battery down lower than 2.4V... -// https://electronics.stackexchange.com/questions/336718/what-is-the-minimum-voltage-required-to-operate-an-avr-mcu-at-8mhz-clock-speed/336719 - -/* - - To avoid unintentional changes of clock frequency, a special write procedure must be followed to change the - CLKPS bits: - 1. Write the Clock Prescaler Change Enable (CLKPCE) bit to one and all other bits in CLKPR to zero. - 2. Within four cycles, write the desired value to CLKPS while writing a zero to CLKPCE. - -*/ - -static void mhz_init(void) { - CLKPR = _BV( CLKPCE ); // Enable changes - CLKPR = _BV( CLKPS0 ); // DIV 2 (4Mhz clock with 8Mhz RC osc) - - #if (F_CPU != 4000000 ) - #error F_CPU must match the clock prescaller bits set in mhz_init() - #endif -} - - -static void init(void) { - - mhz_init(); // switch to 4Mhz. TODO: Some day it would be nice to go back to 1Mhz for FCC, but lets just get things working now. - - power_init(); - button_init(); - - adc_init(); // Init ADC to start measuring battery voltage - pixel_init(); - ir_init(); - - ir_enable(); - - pixel_enable(); - - button_enable(); - - sei(); // Let interrupts happen. For now, this is the timer overflow that updates to next pixel. - -} - - -// Initialize the hardware and pass the flag to run() -// Weak so that a user program can take over immediately on startup and do other stuff. - -int __attribute__ ((weak)) main(void) -{ - - init(); - - while (1) { - run(); - // TODO: Sleep here and only wake on new event - } - - return 0; -} diff --git a/cores/blinkcore/pixel.cpp b/cores/blinkcore/pixel.cpp deleted file mode 100644 index 11a1b2d9..00000000 --- a/cores/blinkcore/pixel.cpp +++ /dev/null @@ -1,768 +0,0 @@ -/* - - Control the 6 RGB LEDs visible on the face of the tile - - - THEORY OF OPERATION - =================== - - The pixels are multiplexed so that only one is lit at any given moment. - The lit pixel is selected by driving its anode high and then driving the common - cathodes of the red, green, and blue LEDs inside each pixel with a PWM signal to control the brightness. - - The PWM signals are generated by hardware timers, so these cathodes can only be connected to - pins that have timer functions on them. - - An ISR driven by a timer interrupt steps though the sequence of anodes. - This is driven by the same timer that generates the PWM signals, and we pick our polarities sothat - the LEDs light up at the end of each PWM cycle so that the ISR has time to step to the next LED - before it actually lights up. - - The PWM timing is slightly complicated by the fact that the compare values that generate the PWM signals are - loaded from a hardware buffer at the end of each PWM cycle, so we need to load the values of the NEXT - pixel while the current pixel is still being driven. - - The blue cathode is slightly different. It has a charge pump to drive the cathode voltage lower than 0V - so it will still work even when the battery is lower than the blue forward voltage (~2.5V). - A second timer drives the charge pump high to charge it up, then low to generate the negative cathode voltage. - The means that the blue diode is out of phase with red and green ones. The blue hardware timer is - lockstep with the one that generates the red and green PWM signals and the ISR interrupt. - -*/ - - -// TODO: Really nail down the gamma mapping and maybe switch everything to 5 bit per channel -// TODO: Really nail down the blue booster - -#include "hardware.h" -#include "bitfun.h" - -#include -#include -#include // Must come after F_CPU definition -#include -#include // memcpy() - -#include "pixel.h" -#include "utils.h" - -#include "callbacks.h" - -#include "timer.h" // We piggyback actual timer callback in pixel since we are using that clock for PWM - -// Here are the raw compare register values for each pixel -// These are precomputed from brightness values because we read them often from inside an ISR -// Note that for red & green, 255 corresponds to OFF and 250 is about maximum prudent brightness -// since we are direct driving them. No danger here since the pins are limited to 20mA, but they do get so -// bright that is gives me a headache. - -typedef struct { - uint8_t rawValueR; - uint8_t rawValueG; - uint8_t rawValueB; -} rawpixel_t; - -// We need these struct gymnastics because C fixed array typedefs do not work -// as you (I?) think they would... -// https://stackoverflow.com/questions/4523497/typedef-fixed-length-array - -typedef struct { - rawpixel_t rawpixels[PIXEL_COUNT]; -} rawpixelset_t; - -// Double buffer the raw pixels so we can switch quickly and atomically - -#define RAW_PIXEL_SET_BUFFER_COUNT 2 - -static rawpixelset_t rawpixelsetbuffer[RAW_PIXEL_SET_BUFFER_COUNT]; - -static rawpixelset_t *displayedRawPixelSet=&rawpixelsetbuffer[0]; // Currently being displayed -static rawpixelset_t *bufferedRawPixelSet =&rawpixelsetbuffer[1]; // Benignly Updateable - -static void setupPixelPins(void) { - - // TODO: Compare power usage for driving LOW with making input. Maybe slight savings because we don't have to drain capacitance each time? Probably not noticable... - // TODO: This could be slightly smaller code by loading DDRD with a full byte rather than bits - - // Setup all the anode driver lines to output. They will be low by default on bootup - SBI( PIXEL0_DDR , PIXEL0_BIT ); - SBI( PIXEL1_DDR , PIXEL1_BIT ); - SBI( PIXEL2_DDR , PIXEL2_BIT ); - SBI( PIXEL3_DDR , PIXEL3_BIT ); - SBI( PIXEL4_DDR , PIXEL4_BIT ); - SBI( PIXEL5_DDR , PIXEL5_BIT ); - - // Set the R,G,B cathode sinks to HIGH so no current flows (this will turn on pull-up until next step sets direction bit).. - - SBI( LED_R_PORT , LED_R_BIT ); // RED - SBI( LED_G_PORT , LED_G_BIT ); // GREEN - - // Note that we do not set the BLUE led pin high here. We leave it low so the pull-up will not be activated when the pin is in input mode. - // We will drive this pin with the timer only since it is connected to the charge pump. - // Whenb not charging or lighting, we leave it in input mode wiht no pull-up so no current can drift though it - // and dimmly light the next LED. - - //SBI( LED_B_PORT , LED_B_BIT ); // BLUE - - // Set the cathode sinks to output (they are HIGH from step above) - // TODO: These will eventually be driven by timers - SBI( LED_R_DDR , LED_R_BIT ); // RED - SBI( LED_G_DDR , LED_G_BIT ); // GREEN - - // Note that we do not set the BLUE led pin to output here. We leave it floating. - // We will drive this pin with the timer only since it is connected to the charge pump - - //SBI( LED_B_DDR , LED_B_BIT ); // BLUE - - // Leave the blue LED sink floating for now. We will enable it as needed in the pixel ISR - // depending on weather the BLUE LED is on or not. - - // We will always leave it low even when in input mode so that the PULLUP is not enabled. - - //SBI( BLUE_SINK_PORT , BLUE_SINK_BIT); // Set the sink output high so blue LED will not come on - //SBI( BLUE_SINK_DDR , BLUE_SINK_BIT); - -} - - -// This will put all timers into sync mode, where they will stop dead -// We can then run the enable() fucntions as we please to get them all set up -// and then release them all at the same exact time -// We do this to get timer0/timer1 and timer2 to be exactly out of phase -// with each other so they can run without stepping on each other -// This assumes that one of the timers will start with its coutner 1/2 way finished -//..which timer2 does. - -static void holdTimers(void) { - SBI(GTCCR,TSM); // Activate sync mode - both timers halted - SBI(GTCCR,PSRASY); // Reset prescaller for timer2 - SBI(GTCCR,PSRSYNC); // Reset prescaller for timer0 and timer1 -} - - -static void releaseTimers(void) { - CBI(GTCCR,TSM); // Release all timers at the same moment -} - - -// Timers are hardwired to colors. No pin portable way to do this. -// RED = OC0A -// GREEN = OC0B -// BLUE = OC2B -// -// Blue is different -// ================= -// Blue is not straight PWM since it is connected to a charge pump that charges on the high and activates LED on the low - - - -// Enable the timer that drives the pixel PWM and radial refresh -// Broken out since we call it both from setupTimers() and enablePixels() - -static void pixelTimersOn(void) { - - // Timer0 to drive R & G PWM. We also use the overflow to jump to the next multiplexed pixel. - - // We are running in FAST PWM mode where we continuously count up to TOP and then overflow. - // Since we are using both outputs, I think we are stuck with Mode 3 = Fast PWM that does not let use use a different TOP - // Mode 3 - Fast PWM TOP=0xFF, Update OCRX at BOTTOM, TOV set at MAX - - // Looking at the diagram in the datasheet, it appears that the OCRs are set at the same time as the TOV INT (at MAX) - - // The outputs are HIGH at the beginning and LOW at the end. HIGH turns OFF the LED and LEDs should be low duty cycle, - // so this gives us time to advance to the next pixel while LED is off to avoid visual glitching. - - // First turn everything off so no glitch while we get ready - - // Writing OCR0A=MAX will result in a constantly high or low output (depending on the - // polarity of the output set by the COM0A[1:0] bits.) - // So setting OCR to MAX will turn off the LED because the output pin will be constantly HIGH - - // Timer0 (R,G) - OCR0A = 255; // Initial value for RED (off) - OCR0B = 255; // Initial value for GREEN (off) - TCNT0 = 0; // This will match BOTTOM so SET the output pins (set is LED off) - - SBI( TCCR0B , FOC0A ); // Force output compare 0A - should set the output - SBI( TCCR0B , FOC0B ); // Force output compare 0B - should set the output - - - // When we get here, timer 0 is not running, timer pins are driving red and green LEDs and they are off. - - // We are using Timer0 mode 3 here for FastPWM which defines the TOP (the value when the overflow interrupt happens) as 255 - - TCCR0A = - _BV( WGM00 ) | _BV( WGM01 ) | // Set mode=3 (0b11) - _BV( COM0A1) | // Clear OC0A on Compare Match, set OC0A at BOTTOM, (non-inverting mode) (clearing turns LED on) - _BV( COM0B1) // Clear OC0B on Compare Match, set OC0B at BOTTOM, (non-inverting mode) - ; - - #if TIMER_TOP != 256 - #Timer TOP is hardcoded as 256 in this mode, must match value uesed in calculations - #endif - - // IMPORTANT:If you change the mode, you must update PIXEL_STEPS_PER_OVR above!!!! - - // Next setup Timer2 for blue PWM. - // When the output pin goes low, it pulls down on the charge pump cap - // which pulls down the blue RGB cathode to negative voltage, lighting the blue led - - TCCR2A = - _BV( COM2B1) | // 1 0 = Clear OC0B on Compare Match (blue on), set OC0B at BOTTOM (blue off), (non-inverting mode) - _BV( WGM01) | _BV( WGM00) // Mode 3 - Fast PWM TOP=0xFF - ; - - #if TIMER_TOP != 256 - #Timer TOP is hardcoded as 256 in this mode, must match value used in calculations - #endif - - - // Timer2 (B) // Charge pump is attached to OC2B - - OCR2A = 128; // Fire a match interrupt half way though the cycle. - // This lets us sample the IR LEDs at double the overflow rate. - // It is also nice because in the match ISR we *only* do IR stuff. - - OCR2B = 255; // Initial value for BLUE (off) - TCNT2= 0; // This is BOTTOM, so when we force a compare the output should be SET (set is LED off, charge pump charging) - - SBI( TCCR2B , FOC2B ); // This should force compare between OCR2B and TCNT2, which should SET the output in our mode (LED off) - - // Ok, everything is ready so turn on the timers! - - - holdTimers(); // Hold the timers so when we start them they will be exactly synced - - // If you change this prescaller, you must update the the TIMER_PRESCALLER - - TCCR0B = // Turn on clk as soon as possible after setting COM bits to get the outputs into the right state - _BV( CS01 ); // clkIO/8 (From prescaler)- ~ This line also turns on the Timer0 - - #if TIMER_PRESCALER != 8 - # Actual hardware prescaller for Timer1 must match value used in calculations - #endif - - // IMPORTANT! - // If you change this prescaler, you must update the the TIMER_PRESCALLER - - // The two timers might be slightly unsynchronized by a cycle, but that should not matter since all the action happens at the end of the cycle anyway. - - TCCR2B = // Turn on clk as soon as possible after setting COM bits to get the outputs into the right state - _BV( CS21 ); // clkI/O/8 (From prescaler)- This line also turns on the Timer0 - - - #if TIMER_PRESCALER != 8 - # Actual hardware prescaller for Timer2 must match value used in calculations - #endif - - // NOTE: There is a datasheet error that calls this bit CA21 - it is actually defined as CS21 - releaseTimers(); // Timer0 and timer1 now in lockstep - -} - - -static void setupTimers(void) { - - TIMSK0 = _BV( TOIE0 ) ; // The corresponding interrupt is executed if an overflow in Timer/Counter0 occurs - - TIMSK2 = _BV( OCIE2A ); // Generate an interrupt when OCR2A matches, which happens excatly out of phase with the overflow. - -} - -void pixel_init(void) { - - // First initialize the buffers - for( uint8_t i = 0 ; i < RAW_PIXEL_SET_BUFFER_COUNT ; i++ ) { - rawpixelset_t *rawpixelset = &rawpixelsetbuffer[ i ]; - for( uint8_t j =0; j < PIXEL_COUNT ; j++ ) { - rawpixelset->rawpixels[j].rawValueR = 255; - rawpixelset->rawpixels[j].rawValueG = 255; - rawpixelset->rawpixels[j].rawValueB = 255; - } - } - - setupPixelPins(); - setupTimers(); -} - -// Note that LINE is 0-5 whereas the pixels are labeled p1-p6 on the board. - -static void activateAnode( uint8_t line ) { - - // TODO: These could probably be compressed with some bit hacking - - switch (line) { - - case 0: - SBI( PIXEL0_PORT , PIXEL0_BIT ); - break; - - case 1: - SBI( PIXEL1_PORT , PIXEL1_BIT ); - break; - - case 2: - SBI( PIXEL2_PORT , PIXEL2_BIT ); - break; - - case 3: - SBI( PIXEL3_PORT , PIXEL3_BIT ); - break; - - case 4: - SBI( PIXEL4_PORT , PIXEL4_BIT ); - break; - - case 5: - SBI( PIXEL5_PORT , PIXEL5_BIT ); - break; - - } - -} - -// Deactivate all anodes. Faster to blindly do all of them than to figure out which is -// is currently on and just do that one. - -static void deactivateAnodes(void) { - - // Each of these compiles to a single instruction - CBI( PIXEL0_PORT , PIXEL0_BIT ); - CBI( PIXEL1_PORT , PIXEL1_BIT ); - CBI( PIXEL2_PORT , PIXEL2_BIT ); - CBI( PIXEL3_PORT , PIXEL3_BIT ); - CBI( PIXEL4_PORT , PIXEL4_BIT ); - CBI( PIXEL5_PORT , PIXEL5_BIT ); - -} - -/* - -volatile uint8_t vccAboveBlueFlag=0; // Is the battery voltage higher than the blue LED forward voltage? - // If so, then we need a different strategy to dim since the LED - // will always be on Even when the pump is not pushing. - // Instead we will do straight PWM on the SINK. - // For now, there are only two modes to keep it simple. - // TODO: Take into account the brightness level and the Vcc and pick which is the most efficient dimming - // strategy cycle-by-cycle. - -#define BLUE_LED_THRESHOLD_V 2.6 - -void updateVccFlag(void) { // Set the flag based on ADC check of the battery voltage. Don't have to do more than once per minute. - vccAboveBlueFlag = (adc_lastVccX10() > BLUE_LED_THRESHOLD_V); - vccAboveBlueFlag = 1; -} - -*/ - - -static uint8_t currentPixelIndex; // Which pixel are we on now? - -// Each pixel has 5 phases - -// 0=Charging blue pump. All anodes are low. -// 1=Resting after pump charge. Get ready to show blue. -// 2=Displaying blue -// 3=Displaying green -// 4=Displaying red - - -// We need a rest because the pump sink is not connected to an OCR pin -// so we need a 3 phase commit to turn off led, turn on pump, turn off pump, turn on led - -// TODO: Use 2 transistors to tie the pump sink and source to the same OCR pin. - -static uint8_t phase=0; - -// To swap the display buffer, you set this and then wait until it is unset by the -// background display ISR -// This makes display updates atomic, and swaps always happen between frames to avoid tearing and aliasing - -static volatile uint8_t pendingRawPixelBufferSwap =0; - -// Need to compute timekeeping based off the pixel interrupt - -// This is hard coded into the Timer setup code in pixels.cpp - -// Number of timer cycles per overflow interrupt -// Hard coded into the timer setup code - - -// Some interesting time calculations: -// Clock 4mhz -// Prescaller is 8 -// ... so Timer clock is 4Mhz/8 = 500KHz -// ... so one timer step is 2us -// 256 steps per phase -// ... so a phase is 2us * 256 = 512us -// 4 phase per pixel -// ... so one pixel takes 512us * 5 = ~2.5ms -// 6 pixels per frame -// ... so one frame takes 6 * 2.5ms = ~15ms -// ... so refresh rate is 1/15ms = ~66Hz - -// Called every time pixel timer0 overflows -// Since OCR PWM values only get loaded from buffers at overflow by the AVR, -// this gives us plenty of time to get the new values into the buffers for next -// pass, so none of this is timing critical as long as we finish in time for next -// pass - -static void pixel_isr(void) { - - // THIS IS COMPLICATED - // Because of the buffering of the OCR registers, we are always setting values that will be loaded - // the next time the timer overflows. - - rawpixel_t *currentPixel = &(displayedRawPixelSet->rawpixels[currentPixelIndex]); // TODO: cache this and eliminate currentPixel since buffer only changes at end of frame - - switch (phase) { - - - case 0: // In this phase, we step to the next pixel and start charging the pump. All PWMs are currently off. - - deactivateAnodes(); - - // Connect the timer to the output pin. - // It might have been disconnected on the the pixel if that pixel did not have any blue in it. - - if ( currentPixel->rawValueB != 255 ) { // Is blue on for this pixel? - - // Connect the timer to the PWM pin - // Otherwise it floats to prevent current from leaking though the cap - // and dimly lighting the blue LED - - - TCCR2A = - _BV( COM2B1) | // 1 0 = Clear OC0B on Compare Match (blue on), set OC0B at BOTTOM (blue off), (non-inverting mode) - _BV( WGM01) | _BV( WGM00); // Mode 3 - Fast PWM TOP=0xFF - - // Right now the timer is at 255, so putting out a steady high which is currently - // just enabling the pull-up since the pin is still input until next step... - - // It is safe to turn on the blue sink because all anodes are off (low) - - SBI( LED_B_DDR , LED_B_BIT ); // Drive BLUE LED output pin - which is high when the LED is not being PWMed. - - - SBI( BLUE_SINK_DDR , BLUE_SINK_BIT ); // Enable output on sink pin. Since this pin port is always 0, this will drive it low. - // Allows capacitor charge though the diode - - // Ok, now the pump capacitor is charging. No LEDs are on. - - // TODO: Handle the case where battery is high enough to drive blue directly and skip the pump - - } - - phase++; - break; - - case 1: - - // OK, if blue is on then CAP has been charging. - // Nothing is on yet, no anodes are activated. - - // Here we rest after charging the pump. - // This is necessary since there is no way to ensure timing between - // turning off the sink and turning on the PWM - - // If the blue led is on for this pixel, then in the previous phase we - // enabled the sink and connected the PWM pin. - - // Now we blinkly disable the sink since we don't want it anymore no matter what - - CBI( BLUE_SINK_DDR , BLUE_SINK_BIT); // Turn off blue sink (make it input) if we were charging - // Might already be off, but faster to blindly turn off again rather than test - - // we leave the PWM pin alone - we want it connected if there is blue here - // so it can pull the cap down. - - // Now the sink is off, we are safe to activate the anode. - // Remember that the PWM pin is still high and connected if there is blue in this pixel. - // A little current will flow now though the capactor, but that ok. - // when the PWM goes low, then the boost will kick in and make the BLUE really light - - activateAnode( currentPixelIndex ); - - // Ok, now we are ready for all the PWMing to happen on this pixel - - // Load up the blue PWM to go low and show blue (if the pump and PWM were activeated in phase #0).... - - OCR2B=currentPixel->rawValueB; // Load OCR to turn on blue at next overflow - - phase++; - - break; - - - case 2: - - // Right now, the blue led is on. Lets get ready for the red one next. - // TODO: Leave blue on until last phase for more brightness? - - - OCR2B = 255; // Load OCR to turn off blue at next overflow - OCR0A = currentPixel->rawValueR; // Load OCR to turn on red at next overflow - - phase++; - break; - - case 3: // Right now, the red LED is on. Get ready for green - - // We are now done with BLUE, so we need to disconnect it to avoid - // dimly lighting the next LED. - - // TODO: Pushg this forward a phase for more brightness? - - // Float the BLUE LED drive pin. - // This cuts off a path for current though the pump cap - // in cases where the blue LED is completely off. Other wise it would - // glow dimly as it discharges though the cap. - - CBI( LED_B_DDR , LED_B_BIT ); - - // Note that this actually still leaves the pull-up connected to the pin - // since the timer is setting the output to 1, so a tiny tiny little bit does - // leak though. - - // To turn off the pull-up, we must completely disconnect the timer to stop - // it from pushing a 1. - // Note that we always leave the bit in PORT at 0, so this will completely - // float the pin. - - TCCR2A = - _BV( WGM01) | _BV( WGM00); // Mode 3 - Fast PWM TOP=0xFF - - // BLue LED is no completely disconnected form everything so should be off. - - - OCR0A = 255; // Load OCR to turn off red at next overflow - OCR0B = currentPixel->rawValueG; // Load OCR to turn on green at next overflow - - phase++; - break; - - case 4: // Right now the green LED is on. - - #if TIMER_PHASE_COUNT!= 5 - #error If this switch does not have 5 cases, then need to update the TIMER_PHASE_COUNT in timer.h to make calculations colrrect - #endif - - OCR0B = 255; // Load OCR to turn off green at next overflow - - phase=0; // Step to next pixel and start over - - currentPixelIndex++; - - if (currentPixelIndex==PIXEL_COUNT) { - currentPixelIndex=0; - - if (pendingRawPixelBufferSwap) { - - rawpixelset_t *temp; - - // Quickly swap the display and buffer sets - temp = displayedRawPixelSet; - displayedRawPixelSet = bufferedRawPixelSet; - bufferedRawPixelSet = temp; - - pendingRawPixelBufferSwap=0; - - } - - } - - break; - - } - -} - -// Stop the timer that drives pixel PWM and refresh -// Used before powering down to make sure all pixels are off - -static void pixelTimerOff(void) { - - TCCR0B = 0; // Timer0 stopped, so no ISR can change anything out from under us - // Right now one LED has its anode activated so we need to turn that off - // before driving all cathodes low - - - deactivateAnodes( ); - - TCCR2B = 0; // Timer/counter2 stopped. - - - // PWM outputs will be stuck where ever they were, at this point. - // Lets set them all low so no place for current to leak. - // If diode was reverse biases, we will have a tiny leakage current. - - TCCR0A = 0; // Disable both timer0 outputs - TCCR2A = 0; // Disable timer2 output - - - // Turn off the BLUE SINK, just in case the BLUE drive pin is high - // this will prevent even the tiny tiny leakage though the boost cap. - // The ISR will turn this back on when needed. - - CBI( BLUE_SINK_DDR , BLUE_SINK_BIT); - - // Now all three timer pins should be inputs - -} - - -// Called when Timer0 overflows, which happens at the end of the PWM cycle for each pixel. We advance to the next pixel. - -// This fires every 500us (2Khz) -// You must finish work in this ISR in 1ms or else might miss an overflow. - - -ISR(TIMER0_OVF_vect) -{ - timer_256us_callback_cli(); // Do any timing critical double-time stuff with interrupts off - // Currently used to sample & charge (but not decode) the IR LEDs - - // We want to turn interrupts back on as quickly as possible here - // to limit the amount of jitter we add to the space gaps between outgoing - // IR pulses - - sei(); // Exhale. We want interrupt on as quickly as possible so we - // don't mess up IR transmit timing. - - // Deal with the PWM stuff. There is a deadline here since we must get the new values - // loaded into the double-buffered registers before the next overflow. - - pixel_isr(); - - timer_256us_callback_sei(); // Do the doubletime callback - timer_512us_callback_sei(); // Do everything else non-timing sensitive. - return; -} - - -ISR(TIMER2_COMPA_vect) // Called when OCR2a matches, which we have set up to happen - // exactly out of phase with the TIMER0_OVR to effectively double - // Double the rate we call some callbacks (currently used for IR) -{ - timer_256us_callback_cli(); // Do any timing critical stuff with interrupts off - - // Currently used to sample & charge (but not decode) the IR LEDs - - // We want to turn interrupts back on as quickly as possible here - // to limit the amount of jitter we add to the space gaps between outgoing - // IR pulses - - sei(); // Exhale. We want interrupt on as quickly as possible so we - // don't mess up IR transmit timing. - - // Deal with the PWM stuff. There is a deadline here since we must get the new values - // loaded into the double-buffered registers before the next overflow. - - timer_256us_callback_sei(); // Do everything else non-timing sensitive. - return; -} - - - -// Turn of all pixels and the timer that drives them. -// You'd want to do this before going to sleep. - -void pixel_disable(void) { - - // First we must disable the timer or else the ISR could wake up - // and turn on the next pixel while we are trying to turn them off. - - pixelTimerOff(); - - - - // Ok, now all the anodes should be low so all LEDs off - // and no timer running to turn any anodes back on - -} - -// Re-enable pixels after a call to disablePixels. -// Pixels will return to the color they had before being disabled. - -void pixel_enable(void) { - - pixelTimersOn(); - - // Technically the correct thing to do here would be to turn the previous pixel back on, - // but it will get hit on the next refresh which happens muchg faster than visible. - - // Next time timer expires, ISR will benignly deactivate the already inactive last pixel, - // then turn on the next pixel and everything will pick up where it left off. - -} - - -// Update the pixel buffer with raw PWM register values. -// Larger pwm values map to shorter PWM cycles (255=off) so for red and green -// there is an inverse but very non linear relation between raw value and brightness. -// For blue is is more complicated because of the charge pump. The peak brightness is somewhere -// in the middle. - -// Values set here are buffered into next call to pixel_displayBufferedPixels() - -// This is mostly useful for utilities to find the pwm -> brightness mapping to be used -// in the gamma lookup table below. - -void pixel_bufferedSetPixelRaw( uint8_t pixel, uint8_t r_pwm , uint8_t g_pwm , uint8_t b_pwm ) { - - rawpixel_t *rawpixel = &(bufferedRawPixelSet->rawpixels[pixel]); - - rawpixel->rawValueR= r_pwm; - rawpixel->rawValueG= g_pwm; - rawpixel->rawValueB= b_pwm; - -} - - -// Gamma table courtesy of adafruit... -// https://learn.adafruit.com/led-tricks-gamma-correction/the-quick-fix -// Compressed down to 32 entries, normalized for our raw values that start at 255 off. - -// TODO: Possible that green and red are similar enough that we can combine them into one table and save some flash space - -static const uint8_t PROGMEM gamma8R[32] = { - 255,254,253,251,250,248,245,242,238,234,230,224,218,211,204,195,186,176,165,153,140,126,111,95,78,59,40,19,13,9,3,1 -}; - -static const uint8_t PROGMEM gamma8G[32] = { - 255,254,253,251,250,248,245,242,238,234,230,224,218,211,204,195,186,176,165,153,140,126,111,95,78,59,40,19,13,9,3,1 -}; - -static const uint8_t PROGMEM gamma8B[32] = { - 255,254,253,251,250,248,245,242,238,234,230,224,218,211,204,195,186,176,165,153,140,126,111,95,78,59,40,19,13,9,3,1 -}; - - -// Update the pixel buffer. - -void pixel_bufferedSetPixel( uint8_t pixel, pixelColor_t newColor) { - - // TODO: OMG, this could be so much more efficient by reducing the size of the gamma table - // to 32 entries per color and having direct mapping to raw values. - // We will do that when we normalize the colors. - - rawpixel_t *rawpixel = &(bufferedRawPixelSet->rawpixels[pixel]); - - rawpixel->rawValueR= pgm_read_byte(&gamma8R[newColor.r]); - rawpixel->rawValueG= pgm_read_byte(&gamma8G[newColor.g]); - rawpixel->rawValueB= pgm_read_byte(&gamma8B[newColor.b]); - -} - -// Display the buffered pixels by swapping the buffer. Blocks until next frame starts. - -void pixel_displayBufferedPixels(void) { - - pendingRawPixelBufferSwap = 1; // Signal to background that we want to swap buffers - - while (pendingRawPixelBufferSwap); // wait for that to actually happen - - // Insure continuity by making sure that after the swap the (now) buffer starts - // off with the same values that the old buffer ended with - memcpy( bufferedRawPixelSet , displayedRawPixelSet , sizeof( rawpixelset_t ) ); - -} diff --git a/cores/blinkcore/pixel.h b/cores/blinkcore/pixel.h deleted file mode 100644 index 50d709e6..00000000 --- a/cores/blinkcore/pixel.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * pixel.h - * - * All the functions for showing colors on the 6 RGB LEDs on the tile face. - * Also timekeeping functions since we use the same timer as for the LEDs. - * - */ - -#ifndef RGB_PIXELS_H_ -#define RGB_PIXELS_H_ - -#include "shared.h" - -#include - -// True today, but could imagine a blinks with 1 pixel or one with with 18 pixels... - -#define PIXEL_COUNT FACE_COUNT - -// Setup pins, interrupts. Call once at power up. - -void pixel_init(void); - -// Enable pixels after a call to pixel_init or pixel_disable -// Pixels will return to the color they had before being disabled. -// Call pixel_setPixels() first to set initial values of pixels before 1st call to pixel_enable() -void pixel_enable(void); - -// Turn of all pixels and the timer that drives them. -// You'd want to do this before going to sleep. - -void pixel_disable(void); - -/** Display interface ***/ - -// Each pixel has 32 brightness levels for each of the three colors (red,green,blue) -// These brightness levels are normalized to be visually linear with 0=off and 31=max brightness - -typedef struct { - uint8_t r:5; - uint8_t g:5; - uint8_t b:5; -} pixelColor_t; - -// Update the pixel buffer. - -void pixel_bufferedSetPixel( uint8_t pixel, pixelColor_t newColor ); - -// Display the buffered pixels. Blocks until next frame starts. - -void pixel_displayBufferedPixels(void); - -/** Callback interface **/ - -// This is the number of cycles between calls of the pixel_callback -// It is determined by the programming of the timer that drives -// the RGB LEDs and the way the callback is called form within the -// interrupts generated by that timer combined with the number of -// phases in the pixel ISR handler - -#define PIXEL_CYCLES_PER_FRAME (8 * 256 * 5) - -// Called at the end of each display frame, which is currently -// about 66Hz - -void pixel_callback_onFrame(void) __attribute__((weak)); - -// Update the pixel buffer with raw PWM register values. -// Larger pwm values map to shorter PWM cycles (255=off) so for red and green -// there is an inverse but very non linear relation between raw value and brightness. -// For blue is is more complicated because of the charge pump. The peak brightness is somewhere -// in the middle. - -// Values set here will be shown on the next hardware reload in the ISR - no buffering -// except for the hardware buffering. - -// This is mostly useful for utilities to find the pwm -> brightness mapping to be used -// in the gamma lookup table below. - -void pixel_bufferedSetPixelRaw( uint8_t pixel, uint8_t r_pwm , uint8_t g_pwm , uint8_t b_pwm ); - -#endif /* RGB_PIXELS_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/power.cpp b/cores/blinkcore/power.cpp deleted file mode 100644 index 92e71218..00000000 --- a/cores/blinkcore/power.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - - Power control. - - THEORY OF OPERATION - =================== - - We put the MCU into "powerdown" mode to save batteries when the unit is not in use. - - The sleep with timeout works by enabling the watchdog timer to generate an interrupt after the specified delay. - - -*/ - - -#include "hardware.h" - -#include -#include -#include -#include - -#include "utils.h" -#include "power.h" - -// Don't do anything, just the interrupt itself will wake us up -// TODO: If we ever need more code space, this could be replaced by an IRET in the VECTOR table. - -static volatile uint8_t wdt_flag=0; // The the WDT get to fire? - -ISR( WDT_vect ) { - - wdt_flag=1; // Remember if we fired - -} - -// Goto low power sleep - get woken up by button or IR LED -// Be sure to turn off all pixels before sleeping since -// the PWM timers will not be running so pixels will not look right. -// If wakeOnButton is true, then we will wake if the button changes (up or down) -// Each bit in wakeOnIR_bitmask represents the IR sensor on one face. If the bit is 1 -// then we will wake when there is a change on that face - -// TODO: We should probably ALWAYS sleep with timers on between frames to save CPU power. - -void power_sleep(void) { - - sleep_cpu(); // Good night - -} - -// Sleep with a predefined timeout. -// This is very power efficient since chip is stopped except for WDT -// If wakeOnButton is true, then we will wake if the button changes (up or down) -// Each bit in wakeOnIR_bitmask represents the IR sensor on one face. If the bit is 1 -// then we will wake when there is a change on that face - -bool power_sleepWithTimeout( power_sleepTimeoutType timeout ) { - - wdt_flag=0; // Reset timeout flag - // Don't worry about a race since WDT is off now - - - - cli(); - wdt_reset(); - WDTCSR |= _BV(WDCE) | _BV(WDE); // Does not actually set WDE, just enables changes to the prescaler in next instruction. NOT DOCUMENTED THAT YOU NEED THE WDE SET ON THIS WRITE - WDTCSR = timeout; // Enable WDT Interrupt (WDIE and timeout bits all included in the timeout values) - sei(); - - sleep_cpu(); // Good night - compiles to 1 instruction - - // Don't worry about edge where we get interrupted here and another WDT fires - it will benignly just set the already set flag. - - WDTCSR = 0; // Turn off the WDT interrupt (no special sequence needed here) - // (assigning all bits to zero is 1 instruction and we don't care about the other bits getting clobbered - - return wdt_flag; - -} - -void power_init(void) { - - wdt_reset(); // reset the countdown! - MCUSR &= ~_BV(WDRF);// You can not turn off the WDT unless this bit is cleared! (NOTE THIS IS MISNAMED IN THE DATASHEET AS WDRT!) - wdt_disable(); // In case we just rebooted becuase of a power_reset(), this makes sure we will not timeout and - // and reboot again. This is necessary because on WDT reset the WDT flag is set, which automatically re-enables - // the watchdog timer. - - // Could save a byte here by combining these two to a single assign - - // I know data sheet repeatedly states you should only set Sleep Enable just before sleeping to avoid - // "accidentally" sleeping, but the only way to sleep is to execute the "sleep" instruction - // so if you are somehow executing that instruction at a time when you don't mean to sleep, then you - // have bigger problems to worry about. - - // SIZE: These could be combined to save a few bytes - set_sleep_mode( SLEEP_MODE_PWR_DOWN ); - sleep_enable(); - - -} - -// Execute a soft reset - almost like power up -// Based on http://www.atmel.com/webdoc/avrlibcreferencemanual/FAQ_1faq_softreset.html -// Assumes that WDRF is cleared in the startup code, which apparently it is. - -void power_soft_reset(void) { - cli(); - wdt_enable(WDTO_1S); // This *must* be long enough that after the reset power_init() will be called before we reset again. - while (1); -} diff --git a/cores/blinkcore/power.h b/cores/blinkcore/power.h deleted file mode 100644 index fad988ea..00000000 --- a/cores/blinkcore/power.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * power.h - * - * All the functions for controlling power and sleep. - * - */ - -#ifndef POWER_H_ -#define POWER_H_ - -#include -#include - -// Sorry, I can't figure out a better way to get Arduino IDE to compile this type... - -#ifndef SLEEP_TIMEOUT_ -#define SLEEP_TIMEOUT_ - -// Possible sleep timeout values -// I know that it is idiomatic Arduino to use straight defines here -// but that leaves open questions. This makes is clear that you pass -// a single timeout value from the list of permitted values. - -typedef enum { - - TIMEOUT_16MS = (_BV(WDIE) ), - TIMEOUT_32MS = (_BV(WDIE) | _BV( WDP0) ), - TIMEOUT_64MS = (_BV(WDIE) | _BV( WDP1) ), - TIMEOUT_125MS= (_BV(WDIE) | _BV( WDP1) | _BV( WDP0) ), - TIMEOUT_250MS= (_BV(WDIE) | _BV( WDP2) ), - TIMEOUT_500MS= (_BV(WDIE) | _BV( WDP2) | _BV( WDP0) ), - TIMEOUT_1S = (_BV(WDIE) | _BV( WDP2) | _BV( WDP1) ), - TIMEOUT_2S = (_BV(WDIE) | _BV( WDP2) | _BV( WDP1) | _BV( WDP0) ), - TIMEOUT_4S = (_BV(WDIE) | _BV( WDP3) ), - TIMEOUT_8S = (_BV(WDIE) | _BV( WDP3) | _BV( WDP0) ) - -} power_sleepTimeoutType; - - -#endif - -// Goto low power sleep - get woken up by any active interrupt source -// which could include button change or IR change - -void power_sleep(void); - -// Sleep with a predefined timeout. -// This is very power efficient since chip is stopped except for WDT - -// Returns 1 if the timeout expired. - -bool power_sleepWithTimeout( power_sleepTimeoutType timeout ); - - -// Get everything set up for proper sleeping -void power_init(void); - -// Execute a soft reset - almost like power up - -void power_soft_reset(void); - -#endif /* POWER_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/run.h b/cores/blinkcore/run.h deleted file mode 100644 index 36be495e..00000000 --- a/cores/blinkcore/run.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * run.h - * - * Prototypes the run() function that is called after all hardware - * has been initialized and the blinkcore functions are operational. - */ - - -#ifndef RUN_H_ -#define RUN_H_ - -// Called repeatedly after hardware has been initialized. - -void run(void); - -#endif \ No newline at end of file diff --git a/cores/blinkcore/shared.h b/cores/blinkcore/shared.h deleted file mode 100644 index 00e20790..00000000 --- a/cores/blinkcore/shared.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * shared.h - * - * Some core values that are shared with higher levels. - * - * These could be exposed cleanly via function calls, but then they would only be known at runtime. - * Putting them here lets higher levels see them without having to pull in morew of the core namespace. - * - * Created: 7/14/2017 1:50:04 PM - * Author: josh - */ - - -#ifndef SHARED_H_ -#define SHARED_H_ - -/*** Utils serial number ***/ - -// Legth of the globally unique serial number returned by utils_serialno() - -#define SERIAL_NUMBER_LEN 9 - -/*** Global processor speed ***/ - -// This mess is to avoid "warning: "F_CPU" redefined" under Arduino IDE. -// If you know a better way, please tell me! - - -#ifdef F_CPU -#undef F_CPU -#endif - -#define F_CPU 4000000UL // Default fuses are DIV8 so we boot at 1Mhz, -// but then we change the prescaler to get to 4Mhz by the time user code runs - - -/*** Number of faces ***/ - -#define FACE_COUNT 6 // Total number of IRLEDs - - -#endif \ No newline at end of file diff --git a/cores/blinkcore/sp.cpp b/cores/blinkcore/sp.cpp deleted file mode 100644 index a30f2a63..00000000 --- a/cores/blinkcore/sp.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Access the service port - * - * The service port has a high speed (500Kbps) bi-directional serial connection plus an Aux pin that can be used - * as either digitalIO or an analog in. - * - * Mostly useful for debugging, but maybe other stuff to? :) - * - */ - -#include "hardware.h" - -#include "utils.h" - -#include "sp.h" - - -// Read the analog voltage on service port pin A -// Returns 0-255 for voltage between 0 and Vcc -// Handy to connect a potentiometer here and use to tune params -// like rightness or speed - -uint8_t sp_aux_analogRead(void) { - - ADMUX = - _BV(REFS0) | // Reference AVcc voltage - _BV( ADLAR ) | // Left adjust result so only one 8 bit read of the high register needed - _BV( MUX2 ) | _BV( MUX1 ) // Select ADC6 - ; - - ADCSRA = - _BV( ADEN ) | // Enable ADC - _BV( ADSC ) // Start a conversion - ; - - - while (TBI(ADCSRA,ADSC)) ; // Wait for conversion to complete - - return( ADCH ); - -} - - -// Initialize the serial on the service port. -// Overrides digital mode for service port pins T and R respectively. - -void sp_serial_init(void) { - sp_serial_init(500000); -} - -void sp_serial_init(unsigned long _baudrate=500000) { - - //Initialize the AUX pin as digitalOut - //SBI( SP_AUX_DDR , SP_AUX_PIN ); - - // Initialize SP serial port for 500K baud, n-8-1 - // This feels like it belongs in hardware.c, maybe in an inline function? - - SBI( SP_SERIAL_CTRL_REG , U2X0); // 2X speed - - SBI( UCSR0B , TXEN0 ); // Enable transmitter (disables digital mode on T pin) - - SP_PIN_R_SET_1(); // Enable pull-up on RX pin so we can use an open-collector to drive it - SBI( UCSR0B , RXEN0); // Enable receiver (disables digital mode on R pin) - - #if F_CPU!=4000000 - #error Serial port calculation in debug.cpp must be updated if not 4Mhz CPU clock. - #endif - - /* - * kenj: - * UBRR calculation for async double speed is: - * (from http://masteringarduino.blogspot.com/2013/11/USART.html) - * UBRR=(F_CPU/(8 * baudrate)-1) - */ - switch(_baudrate) { - case 250000: - UBRR0=1; - break; - case 500000: - default: - UBRR0 = 0; // 500Kbd. This is as fast as we can go at 4Mhz, and happens to be 0% error and supported by the Arduino serial monitor. - // See datasheet table 25-7. - break; - break; - } -} - -// Free up service port pin R for digital IO again - -void sp_serial_disable_rx(void) { - CBI( UCSR0B , RXEN0 ); // Enable transmitter (disables digital mode on T pin) -} - -// Free up service port pin T for digital IO again - -void sp_serial_disable_tx(void) { - CBI( UCSR0B , TXEN0 ); // Enable transmitter (disables digital mode on T pin) -} - - -// Send a byte out the serial port. DebugSerialInit() must be called first. Blocks unitl buffer free if TX already in progress. - -void sp_serial_tx(uint8_t b) { - - while (!TBI(SP_SERIAL_CTRL_REG,UDRE0)); // Wait for buffer to be clear so we don't overwrite in progress - - SP_SERIAL_DATA_REG=b; // Send new byte - -} - -// Wait for most recently transmitted byte to complete - -void sp_serial_flush(void) { - - while (!TBI(SP_SERIAL_CTRL_REG,TXC0)); // Wait until the entire frame in the Transmit Shift Register has been shifted out and there are - // no new data currently present in the transmit buffer - -} - - -// Is there a char ready to read? - -uint8_t sp_serial_rx_ready(void) { - - return TBI( SP_SERIAL_CTRL_REG , SP_SERIAL_READY_BIT ); - -} - -// Read byte from service port serial. Blocks if nothing received yet. - -uint8_t sp_serial_rx(void) { - - while ( !TBI( SP_SERIAL_CTRL_REG , SP_SERIAL_READY_BIT ) ); - - return( SP_SERIAL_DATA_REG ); - -} - diff --git a/cores/blinkcore/sp.h b/cores/blinkcore/sp.h deleted file mode 100644 index 837782f1..00000000 --- a/cores/blinkcore/sp.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Access the service port - * - * The service port has a high speed (500Kbps) bi-directional serial connection plus an Aux pin that can be used - * as either digitalIO or an analog in. - * - * Mostly useful for debugging, but maybe other stuff to? :) - * - */ - - -#ifndef SP_H_ - - #define SP_H_ - - #include "hardware.h" - - #include "bitfun.h" - - - // Set the pin direction for the service port pins - // Note that you can not use the R pin in output mode with the serial adapter board - // because the level conversion transistor will pull the R pin down. - - #define SP_PIN_A_MODE_OUT() SBI( SP_A_DDR , SP_A_BIT ) - #define SP_PIN_A_MODE_IN() CBI( SP_A_DDR , SP_A_BIT ) - #define SP_PIN_R_MODE_OUT() SBI( SP_R_DDR , SP_R_BIT ) - #define SP_PIN_R_MODE_IN() CBI( SP_R_DDR , SP_R_BIT ) - #define SP_PIN_T_MODE_OUT() SBI( SP_T_DDR , SP_T_BIT ) - #define SP_PIN_T_MODE_IN() CBI( SP_T_DDR , SP_T_BIT ) - - // Set the level on the service port pins. - // These execute in a single instruction - // You must set the aux pin to output mode first or else - // driving 1 will just enable the pullup if pin is in input mode (which is the default at reset) - - #define SP_PIN_A_SET_1() SBI( SP_A_PORT, SP_A_BIT) - #define SP_PIN_A_SET_0() CBI( SP_A_PORT, SP_A_BIT) - #define SP_PIN_R_SET_1() SBI( SP_R_PORT, SP_R_BIT) - #define SP_PIN_R_SET_0() CBI( SP_R_PORT, SP_R_BIT) - #define SP_PIN_T_SET_1() SBI( SP_T_PORT, SP_T_BIT) - #define SP_PIN_T_SET_0() CBI( SP_T_PORT, SP_T_BIT) - - - // Initialize the serial on the service port. - // Overrides digital mode for service port pins T and R respectively. - // Also enables the pull-up on the RX pin so it can be connected to an open-collector output - - void sp_serial_init(void); - void sp_serial_init(unsigned long); - - // Send a byte out the serial port. Blocks if a transmit already in progress. - // Must call sp_serial_init() first - - void sp_serial_tx(uint8_t b); - - #define SP_SERIAL_TX_NOW(b) (SP_SERIAL_DATA_REG=b) // Send blindly, but instantly (1 instruction). New byte ignored if there is already a pending one in the buffer (avoid this by leaving 12 clocks between consecutive writes) - - // Wait for all pending transmits to complete - - void sp_serial_flush(void); - - // Is there a char ready to read? - - uint8_t sp_serial_rx_ready(void); - - // Read byte from service port serial. Blocks if nothing received yet. - // Input buffer is only 1 byte so if an interrupts happens while data is streaming, you will loose any incoming data. - // Best to limit yourself interactions with plenty of time (or an ACK) between each incoming byte. - - uint8_t sp_serial_rx(void); - - - // Free up service port pin R for digital IO again after sp_serial_init() called - void sp_serial_disable_rx(void); - - // Free up service port pin T for digital IO again after sp_serial_init() called - void sp_serial_disable_tx(void); - - - // Read the analog voltage on service port pin A - // Returns 0-255 for voltage between 0 and Vcc - // Handy to connect a potentiometer here and use to tune params - // like brightness or speed - // Make sure SP_PIN_A is in input mode (default on power up) or this will be very boring - - uint8_t sp_aux_analogRead(void); - -#endif /* DEBUG_H_ */ diff --git a/cores/blinkcore/timer.cpp b/cores/blinkcore/timer.cpp deleted file mode 100644 index 4c090b2a..00000000 --- a/cores/blinkcore/timer.cpp +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Timer related functions - * - */ - - -// NOTE: Everythin is in the header for Timer \ No newline at end of file diff --git a/cores/blinkcore/timer.h b/cores/blinkcore/timer.h deleted file mode 100644 index 27ff0d20..00000000 --- a/cores/blinkcore/timer.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * timer.h - * - * Timer related functions - * - * Note that the RGB pixel PWM uses timer0 and timer2, so we piggy back on timer0 overflow to - * provide the timer callback. That means all this code lives in pixel.h - * - */ - -#ifndef TIMER_H_ -#define TIMER_H_ - -#include "shared.h" - -// These values are based on how we actually program the timer registers in timer_enable() -// There are checked with assertion there, so don't change these without changing the actual registers first - - -// There are two timer callback speeds - every 256us and every 512us. -// The 256us timer callback is broken into two parts. One is called with interrupts off -// and should complete any atomic operations very quickly and return. Then the interrupts -// on callback is called and can take longer but must comeplete before the next firing. - -// User supplied callback. Called every 256us with interrupts off. Should complete work as -// quickly as possible!!! -// Actually called from pixel.cpp since we also use the pixel timer for time keeping - -void timer_256us_callback_cli(void); - - -// User supplied callback. Called every 256us with interrupts on. Should complete work in <<256us. -// Actually called form pixel.cpp since we also use the pixel timer for time keeping - -void timer_256us_callback_sei(void); - - - -// User supplied callback. Called every 512us with interrupts on. Should complete work in <<256us. -// Actually called form pixel.cpp since we also use the pixel timer for time keeping - -void timer_512us_callback_sei(void); - -#define TIMER_PRESCALER 8 // How much we divide the F_CPU by to get the timer0 frequency - -#define TIMER_TOP 256 // How many timer ticks per overflow? - -#define TIMER_PHASE_COUNT 5 // How many phases between timer callback? - -#define TIMER_CYCLES_PER_TICK (TIMER_PRESCALER*TIMER_TOP) - -#define F_TIMER ( F_CPU / TIMER_CYCLES_PER_TICK ) // Timer overflow frequency. Units of TICKS/SEC. Comes out to 1953.125 = 1.953125 kilohertz, 512us per tick - -#define CYCLES_PER_MS (F_CPU/MILLIS_PER_SECOND) - -#define TIMER_MS_TO_TICKS(ms) ( ( CYCLES_PER_MS * ms) / TIMER_CYCLES_PER_TICK ) // Slightly contorted to preserve precision - -#define CYCLES_PER_US (F_CPU/US_PER_SECOND) - -#define US_TO_CYCLES(us) (us * CYCLES_PER_US ) - -#define US_PER_SECOND 1000000 - -#define MILLIS_PER_SECOND 1000 - -#define SECONDS_PER_MINUTE 60 - -#endif /* TIMER_H_ */ \ No newline at end of file diff --git a/cores/blinkcore/utils.cpp b/cores/blinkcore/utils.cpp deleted file mode 100644 index 4d020732..00000000 --- a/cores/blinkcore/utils.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Time related functions - * - */ - - -#include "hardware.h" - -#include "utils.h" - - -// Returns the device's unique 8-byte serial number - -utils_serialno_t const *utils_serialno(void) { - - // 0xF0 points to the 1st of 8 bytes of serial number data - // As per "13.6.8.1. SNOBRx - Serial Number Byte 8 to 0" - - return ( utils_serialno_t *) 0xF0; -} \ No newline at end of file diff --git a/cores/blinkcore/utils.h b/cores/blinkcore/utils.h deleted file mode 100644 index f04394fd..00000000 --- a/cores/blinkcore/utils.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * utils.h - * - * Access to the unique serial number inside the ATMEGA chip. - * - * Created: 7/14/2017 1:50:04 PM - * Author: josh - */ - - -#ifndef UTILS_H_ -#define UTILS_H_ - -#include "shared.h" - -typedef struct { - uint8_t bytes[SERIAL_NUMBER_LEN] ; -} utils_serialno_t; - -// Returns the device's unique 9-byte serial number - -utils_serialno_t const *utils_serialno(void); - -/* - Compiles to: - - 280: 24 81 ldd r18, Z+4 ; 0x04 - 282: 2f 5f subi r18, 0xFF ; 255 - 284: 09 f0 breq .+2 ; 0x288 <__vector_4+0x50> - 286: 24 83 std Z+4, r18 ; 0x04 - -*/ - -#endif /* UTILS_H_ */ \ No newline at end of file diff --git a/libraries/blinklib/src/Arduino.h b/cores/blinklib/Arduino.h similarity index 95% rename from libraries/blinklib/src/Arduino.h rename to cores/blinklib/Arduino.h index dc19767c..631e3d28 100644 --- a/libraries/blinklib/src/Arduino.h +++ b/cores/blinklib/Arduino.h @@ -24,11 +24,11 @@ #ifndef Arduino_h #define Arduino_h - + #include #include #include - + #include "ArduinoTypes.h" #define HIGH 0x1 @@ -67,16 +67,13 @@ #define bitClear(value, bit) ((value) &= ~(1UL << (bit))) #define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) + #define bit(b) (1UL << (b)) - #define bit(b) (1UL << (b)) - // Don't judge me // I know this is so ugly, but only other way to make it so people do not need to do a - // manual #include in their sketches would be to throw all the library code into one giant + // manual #include in their sketches would be to throw all the library code into one giant // directory... and that is even uglier, right? - - #include "shared.h" + #include "blinklib.h" - #include "blinkstate.h" - + #endif diff --git a/libraries/blinklib/src/ArduinoTypes.h b/cores/blinklib/ArduinoTypes.h similarity index 82% rename from libraries/blinklib/src/ArduinoTypes.h rename to cores/blinklib/ArduinoTypes.h index f09017c8..590239dc 100644 --- a/libraries/blinklib/src/ArduinoTypes.h +++ b/cores/blinklib/ArduinoTypes.h @@ -1,9 +1,9 @@ /* Some types used in idiomatic Arduino code and Arduino compatible function signatures. - We break them out into thier own header so we can implement against them without having to - include the toxic Arduino.h itself. - + We break them out into thier own header so we can implement against them without having to + include the toxic Arduino.h itself. + */ @@ -16,7 +16,7 @@ typedef bool boolean; typedef uint8_t byte; typedef unsigned int word; - + typedef uint32_t ulong; #endif \ No newline at end of file diff --git a/cores/blinklib/DummySerial.h b/cores/blinklib/DummySerial.h new file mode 100644 index 00000000..9509a4c1 --- /dev/null +++ b/cores/blinklib/DummySerial.h @@ -0,0 +1,93 @@ +/* + + A very simplified serial port built to look like Arduino's Serial class. + + This serial port lives on the service port on new boards. It is really intended for debugging. + + The port always runs at a fixed 1M baud. Dev Candy Adapter boards and cables are available to + connect to a USB port and then use the Arduino IDE's serial monitor to interact with your tile. + +*/ + +#include + +#include "ArduinoTypes.h" + +#ifndef Serial_h + +#define Serial_h + +#include "Print.h" + +class ServicePortSerial : public Print +{ + + public: + + //inline ServicePortSerialSerial(void); + void begin(void); + void end(); + + virtual int available(void); + + // Input buffer is only 1 byte so if an interrupts happens while data is streaming, you will loose any incoming data. + // Best to limit yourself interactions with plenty of time (or an ACK) between each incoming byte. + + virtual int read(void); // Read a byte - returns byte read or -1 if no byte ready. + virtual uint8_t readWait(void); // Blocking read (faster) + + virtual size_t write(uint8_t); + void flush(void); // Block until all pending transmits complete + + using Print::write; // pull in write(str) and write(buf, size) from Print + + +}; + +#endif + + + +// Public Methods ////////////////////////////////////////////////////////////// + +void ServicePortSerial::begin(void) +{ +} + +void ServicePortSerial::end() +{ +} + +// We only use the 1 byte hardware buffer + +inline int ServicePortSerial::available(void) { + return(0); +} + +inline int ServicePortSerial::read(void) +{ + return -1; +} + +inline byte ServicePortSerial::readWait(void) +{ + return 0; +} + +// We don't implement flush because it would require adding a flag to remember if we ever sent. +// This is because the hardware only gives us a bit that tells us when a tx completes, not if +// no TX was ever started. Ardunio does this with the `_writen` flag. +// If you can convince me that we really need flush, LMK and it can be added. + +inline size_t ServicePortSerial::write(uint8_t c) +{ + + return(1); + +} + +// Block until all pending transmits complete + +void ServicePortSerial::flush(void) +{ +} \ No newline at end of file diff --git a/libraries/blinklib/src/Print.cpp b/cores/blinklib/Print.cpp similarity index 97% rename from libraries/blinklib/src/Print.cpp rename to cores/blinklib/Print.cpp index 2bc7003f..6c58436a 100644 --- a/libraries/blinklib/src/Print.cpp +++ b/cores/blinklib/Print.cpp @@ -8,21 +8,21 @@ Print.cpp - Base class that provides print() and println() Copyright (c) 2008 David A. Mellis. All right reserved. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - + Modified 23 November 2006 by David A. Mellis Modified 03 August 2015 by Chuck Todd */ @@ -204,15 +204,15 @@ size_t Print::printNumber(unsigned long n, uint8_t base) return write(str); } -size_t Print::printFloat(double number, uint8_t digits) -{ +size_t Print::printFloat(double number, uint8_t digits) +{ size_t n = 0; - + if (isnan(number)) return print("nan"); if (isinf(number)) return print("inf"); if (number > 4294967040.0) return print ("ovf"); // constant determined empirically if (number <-4294967040.0) return print ("ovf"); // constant determined empirically - + // Handle negative numbers if (number < 0.0) { @@ -224,7 +224,7 @@ size_t Print::printFloat(double number, uint8_t digits) double rounding = 0.5; for (uint8_t i=0; i 0) { - n += print('.'); + n += print('.'); } // Extract digits from the remainder one at a time @@ -243,8 +243,8 @@ size_t Print::printFloat(double number, uint8_t digits) remainder *= 10.0; unsigned int toPrint = (unsigned int)(remainder); n += print(toPrint); - remainder -= toPrint; - } - + remainder -= toPrint; + } + return n; } \ No newline at end of file diff --git a/libraries/blinklib/src/Print.h b/cores/blinklib/Print.h similarity index 87% rename from libraries/blinklib/src/Print.h rename to cores/blinklib/Print.h index 3d9b73b6..f71d4146 100644 --- a/libraries/blinklib/src/Print.h +++ b/cores/blinklib/Print.h @@ -1,11 +1,11 @@ /* - This is a slimed down version of the Arduino Print class for the tiles platform. + This is a slimed down version of the Arduino Print class for the tiles platform. Implements most cases except WStrings and Printables Original header: - + Print.h - Base class that provides print() and println() Copyright (c) 2008 David A. Mellis. All right reserved. @@ -93,4 +93,18 @@ class Print virtual void flush() { /* Empty implementation for backward compatibility */ } }; + +/** The Printable class provides a way for new classes to allow themselves to be printed. + By deriving from Printable and implementing the printTo method, it will then be possible + for users to print out instances of this class by passing them into the usual + Print::print and Print::println methods. +*/ + +class Printable +{ + public: + virtual size_t printTo(Print& p) const = 0; +}; + + #endif \ No newline at end of file diff --git a/libraries/blinklib/src/Serial.cpp b/cores/blinklib/Serial.cpp similarity index 58% rename from libraries/blinklib/src/Serial.cpp rename to cores/blinklib/Serial.cpp index d74e5b46..febc1fde 100644 --- a/libraries/blinklib/src/Serial.cpp +++ b/cores/blinklib/Serial.cpp @@ -1,12 +1,12 @@ /* - A very simplified serial port built to look like Arduino's Serial class. - - This serial port lives on the service port on new boards. It is really intended for debugging. - - The port always runs at a fixed 500K baud. Adapter cables and boards will be available to connect to a USB port - and then use the Arduino IDE's serial monitor to interact with your tile. - + A very simplified serial port built to look like Arduino's Serial class. + + This serial port lives on the service port on new boards. It is really intended for debugging. + + The port always runs at a fixed 1M baud. Dev Candy Adapter boards and cables are available to + connect to a USB port and then use the Arduino IDE's serial monitor to interact with your tile. + */ //#include @@ -14,53 +14,42 @@ #include #include +#include "sp.h" + #include "Serial.h" #include "ArduinoTypes.h" -#include "sp.h" // Public Methods ////////////////////////////////////////////////////////////// void ServicePortSerial::begin(void) { - - sp_serial_init(DEF_SERVICE_PORT_BAUDRATE); -} + sp_serial_init(); -void ServicePortSerial::begin(unsigned long _baudrate=DEF_SERVICE_PORT_BAUDRATE) { - switch(_baudrate) { - case 250000: - sp_serial_init(250000); - break; - case 500000: - default: - sp_serial_init(500000); - break; - } } void ServicePortSerial::end() -{ +{ // TODO: Is there any reason to turn off the serial port? Power? - + } // We only use the 1 byte hardware buffer int ServicePortSerial::available(void) { - + if (sp_serial_rx_ready()) { return(1); } else { return(0); - } - -} + } + +} int ServicePortSerial::read(void) -{ +{ if (!sp_serial_rx_ready()) { return -1; } else { @@ -68,30 +57,23 @@ int ServicePortSerial::read(void) } } -byte ServicePortSerial::readWait(void) -{ - while (!sp_serial_rx_ready()); - return sp_serial_rx(); -} - -// We don't implement flush because it would require adding a flag to remember if we ever sent. -// This is because the hardware only gives us a bit that tells us when a tx completes, not if -// no TX was ever started. Ardunio does this with the `_writen` flag. -// If you can convince me that we really need flush, LMK and it can be added. +// We don't implement flush because it would require adding a flag to remember if we ever sent. +// This is because the hardware only gives us a bit that tells us when a tx completes, not if +// no TX was ever started. Ardunio does this with the `_writen` flag. +// If you can convince me that we really need flush, LMK and it can be added. size_t ServicePortSerial::write(uint8_t c) { - + sp_serial_tx(c); - + return(1); - + } // Block until all pending transmits complete void ServicePortSerial::flush(void) { - sp_serial_flush(); -} - + sp_serial_flush(); +} \ No newline at end of file diff --git a/libraries/blinklib/src/Serial.h b/cores/blinklib/Serial.h similarity index 64% rename from libraries/blinklib/src/Serial.h rename to cores/blinklib/Serial.h index 4d259180..db876f63 100644 --- a/libraries/blinklib/src/Serial.h +++ b/cores/blinklib/Serial.h @@ -1,12 +1,12 @@ /* - A very simplified serial port built to look like Arduino's Serial class. - - This serial port lives on the service port on new boards. It is really intended for debugging. - - The port always runs at a fixed 500K baud. Use the blink Dev Candy board to connect to a USB port - and then use the Arduino IDE's serial monitor to interact with your tile. - + A very simplified serial port built to look like Arduino's Serial class. + + This serial port lives on the service port on new boards. It is really intended for debugging. + + The port always runs at a fixed 1M baud. Use the blink Dev Candy board to connect to a USB port + and then use the Arduino IDE's serial monitor to interact with your tile. + */ @@ -16,33 +16,30 @@ #include "Print.h" - #define DEF_SERVICE_PORT_BAUDRATE 500000 + #define DEF_SERVICE_PORT_BAUDRATE 1000000 class ServicePortSerial : public Print { public: - + //inline ServicePortSerialSerial(void); - - void begin(void); - void begin(unsigned long); + void begin(void); void end(); - + virtual int available(void); - + // Input buffer is only 1 byte so if an interrupts happens while data is streaming, you will loose any incoming data. - // Best to limit yourself interactions with plenty of time (or an ACK) between each incoming byte. - - virtual int read(void); // Read a byte - returns byte read or -1 if no byte ready. - virtual uint8_t readWait(void); // Blocking read (faster) - + // Best to limit yourself interactions with plenty of time (or an ACK) between each incoming byte. + + virtual int read(void); // Read a byte - returns byte read or -1 if no byte ready. + virtual size_t write(uint8_t); void flush(void); // Block until all pending transmits complete - + using Print::write; // pull in write(str) and write(buf, size) from Print - - + + }; #endif diff --git a/cores/blinklib/Timer.cpp b/cores/blinklib/Timer.cpp new file mode 100644 index 00000000..8ae33a5e --- /dev/null +++ b/cores/blinklib/Timer.cpp @@ -0,0 +1,60 @@ +#include // Get ULONG_MAX for NEVER + +#include "blinklib.h" +// Note we directly access millis() here, which is really bad style. +// The timer should capture millis() in a closure, but no good way to +// do that in C++ that is not verbose and inefficient, so here we are. + +#define NEVER (ULONG_MAX) + +// All Timers come into this world pre-expired, so their expireTime is 0 +// Here we leave the constructor empty and depend in the BBS section clearing +// to set it to 0 (the constructor mechanism uses lots of flash). + +bool Timer::isExpired() { + return millis() > m_expireTime; +} + +void Timer::set( uint32_t ms ) { + m_expireTime= millis()+ms; +} + +uint32_t Timer::getRemaining() { + + uint32_t timeRemaining; + + if( millis() >= m_expireTime) { + + timeRemaining = 0; + + } else { + + timeRemaining = m_expireTime - millis(); + + } + + return timeRemaining; + +} + +void Timer::add( uint16_t ms ) { + + // Check to avoid overflow + + uint32_t timeLeft = NEVER - m_expireTime; + + if (ms > timeLeft ) { + + m_expireTime = NEVER; + + } else { + + m_expireTime+= ms; + + } +} + +void Timer::never(void) { + m_expireTime=NEVER; +} + diff --git a/cores/blinklib/blinklib.cpp b/cores/blinklib/blinklib.cpp new file mode 100644 index 00000000..c36b7d6e --- /dev/null +++ b/cores/blinklib/blinklib.cpp @@ -0,0 +1,1454 @@ +/* + * + * This library lives in userland and acts as a shim to th blinkos layer + * + * This view tailored to be idiomatic Arduino-y. There are probably better views of the interface if you are not an Arduinohead. + * + * In this view, each tile has a "state" that is represented by a number between 1 and 127. + * This state value is continuously broadcast on all of its faces. + * Each tile also remembers the most recently received state value from he neighbor on each of its faces. + * + * You supply setup() and loop(). + * + * While in loop(), the world is frozen. All changes you make to the pixels and to data on the faces + * is buffered until loop returns. + * + */ + +#include +#include + +#include // PROGMEM for parity lookup table +#include // cli() and sei() so we can get snapshots of multibyte variables + +#include // sleep_cpu() so we can rest between interrupts. + +#include // Used in randomize() to get some entropy from the skew between the WDT osicilator and the system clock. + +#include + +#include + +#include "ArduinoTypes.h" + +#include "blinklib.h" + +// Here are our magic shared memory links to the BlinkBIOS running up in the bootloader area. +// These special sections are defined in a special linker script to make sure that the addresses +// are the same on both the foreground (this blinklib program) and the background (the BlinkBIOS project compiled to a HEX file) + +// The actual memory for these blocks is allocated in main.cpp. Remember, it overlaps with the same blocks in BlinkBIOS code running in the bootloader! + +#include "shared/blinkbios_shared_button.h" +#include "shared/blinkbios_shared_millis.h" +#include "shared/blinkbios_shared_pixel.h" +#include "shared/blinkbios_shared_irdata.h" + +#include "shared/blinkbios_shared_functions.h" // Gets us ir_send_packet() + + +#define TX_PROBE_TIME_MS 150 // How often to do a blind send when no RX has happened recently to trigger ping pong + // Nice to have probe time shorter than expire time so you have to miss 2 messages + // before the face will expire + +#define RX_EXPIRE_TIME_MS 200 // If we do not see a message in this long, then show that face as expired + +#define VIRAL_BUTTON_PRESS_LOCKOUT_MS 2000 // Any viral button presses received from IR within this time period are ignored + // since insures that a single press can not circulate around indefinitely. + +#define WARM_SLEEP_TIMEOUT_MS ( 10 * 60 * 1000UL ) // 10 mins + // We will warm sleep if we do not see a button press or remote button press + // in this long + +// This is a special byte that signals that this is a long data packet +// Note that this is also a value value, but we can tell that it is a data by looking at the IR packet len. Datagrams are always >2 bytes. +// It must appear in the first byte of the data, and the final byte is an inverted checksum of all bytes including this header byte + +#define DATAGRAM_SPECIAL_VALUE 0b00101010 + +// This is a special byte that triggers a warm sleep cycle when received +// It must appear in the first & second byte of data +// When we get it, we virally send out more warm sleep packets on all the faces +// and then we go to warm sleep. + +#define TRIGGER_WARM_SLEEP_SPECIAL_VALUE 0b00010101 + +// This is a special byte that does nothing. +// It must appear in the first & second byte of data. +// We send it when we warm wake to warm wake our neighbors. + +#define NOP_SPECIAL_VALUE 0b00110011 + + +// We use bit 6 in the IR data to indicate that a button has been pressed so we should +// postpone sleeping. This spreads a button press to all connected tiles so +// they will stay awake if any tile in the group gets pressed. + +// We use bit 7 in the IR data as ODD parity check. We do ODD to make sure at least 1 bit is always +// set (otherwise 0x00 would be 0x00 with parity). + +// Assumes ( d < IR_DATA_VALUE_MAX ) + +#if IR_DATA_VALUE_MAX > 63 + #warning The following code assumes that the top two bits of the header byte are available +#endif + +// Returns true if odd number of bits set +// TODO: make asm + +uint8_t oddParity( uint8_t d ) { + + uint8_t bits=0; + + while (d) { + + if (d & 0b00000001 ) { + bits++; + } + + d >>=1; + + } + + return bits & 0xb00000001; +} + +static uint8_t irValueEncode( uint8_t d , uint8_t postponeSleepFlag ) { + + if (postponeSleepFlag) { + d |= 0b01000000; // 6th bit button pressed flag + } + + if ( !oddParity( d )) { + + d |= 0b10000000; // Top bit ODD parity (including postpone sleep flag) + + } + + return d; + +} + + +static uint8_t irValueCheckValid( uint8_t d ) { + + return oddParity( d ); // Odd parity + +} + + +// The actual data is hidden in the middle + +static uint8_t irValueDecodeData( uint8_t d ) { + + return (d & 0b00111111) ; + +} + + +static uint8_t irValueDecodePostponeSleepFlag( uint8_t d ) { + + return d & 0b01000000 ; + +} + + +// TODO: These structs even better if they are padded to a power of 2 like https://stackoverflow.com/questions/1239855/pad-a-c-structure-to-a-power-of-two + +#if IR_DATAGRAM_LEN > IR_RX_PACKET_SIZE + #error IR_DATAGRAM_LEN must not be bigger than IR_RX_PACKET_SIZE +#endif + +// All semantics chosen to have sane startup 0 so we can +// keep this in bss section and have it zeroed out at startup. + +struct face_t { + + uint8_t inValue; // Last received value on this face, or 0 if no neighbor ever seen since startup + uint8_t outValue; // Value we send out on this face + millis_t expireTime; // When this face will be considered to be expired (no neighbor there) + millis_t sendTime; // Next time we will transmit on this face (set to 0 every time we get a good message so we ping-pong across the link) + + uint8_t inDatagramLen; // 0= No datagram waiting to be read + uint8_t inDatagramData[IR_DATAGRAM_LEN]; + + uint8_t outDatagramLen; // 0= No datagram waiting to be sent + uint8_t outDatagramData[IR_DATAGRAM_LEN]; +}; + +static face_t faces[FACE_COUNT]; + +uint8_t viralButtonPressSendOnFaceBitflags; // A 1 here means send the viral button press bit on the next IR packet on this face. Cleared when it gets sent. + +Timer viralButtonPressLockoutTimer; // Set each time we send a viral button press to avoid sending getting into a circular loop + +// Millis snapshot for this pass though loop +millis_t now; + +// Capture time snapshot +// It is 4 bytes long so we cli() so it can not get updated in the middle of us grabbing it + +void updateNow() { + cli(); + now = blinkbios_millis_block.millis; + sei(); +} + +unsigned long millis() { + return now; +} + +// Returns the inverted checksum of all bytes + +uint8_t computePacketChecksum( volatile const uint8_t *buffer , uint8_t len ) { + + uint8_t computedChecksum = 0; + + for( uint8_t l=0; l < len ; l++ ) { + + computedChecksum += *buffer++; + + } + + return computedChecksum ^ 0xff ; + +} + + +#if ( ( IR_LONG_PACKET_MAX_LEN + 3 ) > IR_RX_PACKET_SIZE ) + + #error There has to be enough room in the blinkos packet buffer to hold the user packet plus 2 header bytes and one checksum byte + +#endif + +byte getDatagramLengthOnFace( uint8_t face ) { + return faces[face].inDatagramLen; +} + +boolean isDatagramReadyOnFace( uint8_t face ) { + return getDatagramLengthOnFace(face) != 0; +} + +const byte *getDatagramOnFace( uint8_t face ) { + return faces[face].inDatagramData; +} + +void markDatagramReadOnFace( uint8_t face ) { + faces[face].inDatagramLen = 0; +} + +// Jump to the send packet function all way up in the bootloader + +uint8_t blinkbios_irdata_send_packet( uint8_t face, const uint8_t *data , uint8_t len ) { + + // Call directly into the function in the bootloader. This symbol is resolved by the linker to a + // direct call to the target address. + return BLINKBIOS_IRDATA_SEND_PACKET_VECTOR(face,data,len); + +} + +#define SBI(x,b) (x|= (1< IR_DATAGRAM_LEN ) { + + // Ignore request to send oversized packet + + return; + + } + + face_t *f = &faces[face]; + + f->outDatagramLen = len; + memcpy( f->outDatagramData , data , len ); + +} + + +static void clear_packet_buffers() { + + FOREACH_FACE(f) { + + blinkbios_irdata_block.ir_rx_states[f].packetBufferReady = 0; + + } +} + +// Set the color and display it immediately +// for internal use where we do not want the loop buffering + +static void setColorNow( Color newColor ) { + + setColor( newColor ); + BLINKBIOS_DISPLAY_PIXEL_BUFFER_VECTOR(); + +} + + +Color dim( Color color, byte brightness) { + return MAKECOLOR_5BIT_RGB( + (GET_5BIT_R(color)*brightness)/255, + (GET_5BIT_G(color)*brightness)/255, + (GET_5BIT_B(color)*brightness)/255 + ); +} + + +// When will we warm sleep due to inactivity +// reset by a button press or seeing a button press bit on +// an incoming packet + +Timer warm_sleep_time; + +void reset_warm_sleep_timer() { + + warm_sleep_time.set( WARM_SLEEP_TIMEOUT_MS ); + +} + +// Remembers if we have woken from either a BIOS sleep or +// a blinklib forced sleep. + +uint8_t hasWarmWokenFlag =0; + +// We need to save the pixel buffer when we warm sleep so we display our +// sleep and wake animations and then restore the original game pixels before restarting the game + +pixelColor_t savedPixelBuffer[PIXEL_COUNT]; + +void savePixels() { + // Save game pixels + + FOREACH_FACE(f) { + + savedPixelBuffer[f] = blinkbios_pixel_block.pixelBuffer[f]; + + } +} + +void restorePixels() { + // Save game pixels + + FOREACH_FACE(f) { + + blinkbios_pixel_block.pixelBuffer[f] = savedPixelBuffer[f]; + + } +} + + +#define SLEEP_ANIMATION_DURATION_MS 300 +#define SLEEP_ANIMATION_MAX_BRIGHTNESS 200 + +// A special warm sleep trigger packet has len 2 and the two bytes are both the special cookie value +// Because it must be 2 long, this means that the cookie can still be a data value since that value would only have a 1 byte packet + +static uint8_t force_sleep_packet[2] = { TRIGGER_WARM_SLEEP_SPECIAL_VALUE , TRIGGER_WARM_SLEEP_SPECIAL_VALUE}; + +// This packet does nothing except wake up our neighbors + +static uint8_t nop_wake_packet[2] = { NOP_SPECIAL_VALUE , NOP_SPECIAL_VALUE}; + + +#define SLEEP_PACKET_REPEAT_COUNT 5 // How many times do we send the sleep and wake packets for redunancy? + +static void warm_sleep_cycle() { + + BLINKBIOS_POSTPONE_SLEEP_VECTOR(); // Postpone cold sleep so we can warm sleep for a while + // The cold sleep will eventually kick in if we + // do not wake from warm sleep in time. + + // Save the games pixels so we can restore them on waking + // we need to do this because the sleep and wake animations + // will overwrite whatever is there. + + savePixels(); + + + // Ok, now we are virally sending FORCE_SLEEP out on all faces to spread the word + // and the pixels are off so the user is happy and we are saving power. + + // First send the force sleep packet out to all our neighbors + // We are indiscriminate, just splat it 5 times everywhere. + // This is a brute force approach to make sure we get though even with collisions + // and long packets in flight. + + // We also show a little animation while transmitting the packets + + // Figure out how much brightness to animate on each packet + + const int animation_fade_step = SLEEP_ANIMATION_MAX_BRIGHTNESS / ( SLEEP_PACKET_REPEAT_COUNT * FACE_COUNT ); + + uint8_t fade_brightness; + + // For the sleep animation we start bright and dim to 0 by the end + + // This code picks a start near to SLEEP_ANIMATION_MAX_BRIGHTNESS that makes sure we end up at 0 + fade_brightness = SLEEP_ANIMATION_MAX_BRIGHTNESS; + + for( uint8_t n=0; npacketBufferReady) { + + if (ir_rx_state->packetBuffer[1] != TRIGGER_WARM_SLEEP_SPECIAL_VALUE ) { + + saw_packet_flag =1; + + } + + ir_rx_state->packetBufferReady=0; + + } + + ir_rx_state++; + } + + } + + cli(); + blinkbios_millis_block.millis = save_time; + BLINKBIOS_POSTPONE_SLEEP_VECTOR(); // It is ok top call like this to reset the inactivity timer + sei(); + + hasWarmWokenFlag = 1; // Remember that we warm slept + reset_warm_sleep_timer(); + + // Forced sleep mode + // Really need button down detection in bios so we only wake on lift... + // BLINKBIOS_SLEEP_NOW_VECTOR(); + + // Clear out old packets (including any old FORCE_SLEEP packets so we don't go right back to bed) + + clear_packet_buffers(); + + // Show smooth wake animation + + // This loop empirically works out to be about the right delay. + // I know this hardcode is hackyish, but we need to save flash space + + // For the wake animation we start off and dim to MAX by the end + + // This code picks a start near to SLEEP_ANIMATION_MAX_BRIGHTNESS that makes sure we end up at 0 + fade_brightness = 0; + + for( uint8_t n=0; npacketBufferReady ) { + + // Got something, so we know there is someone out there + // TODO: Should we require the received packet to pass error checks? + face->expireTime = now + RX_EXPIRE_TIME_MS; + + // This is slightly ugly. To save a buffer, we get the full packet with the BlinkBIOS IR packet type byte. + + volatile const uint8_t *packetData = (ir_rx_state->packetBuffer); + + if ( *packetData++ == IR_USER_DATA_HEADER_BYTE ) { // We only process user data and ignore (and consume) anything else. This is ugly. Sorry. + + uint8_t packetDataLen = (ir_rx_state->packetBufferLen)-1; // deduct the BlinkBIOS packet type byte + + // blinkBIOS will only pass use packets with len >0 + + uint8_t irDataFirstByte = *packetData; + + if (irValueCheckValid( irDataFirstByte )) { + + // If we get here, then we know this is a valid packet + + // Clear to send on this face immediately to ping-pong messages at max speed without collisions + face->sendTime = 0; + + if (irValueDecodePostponeSleepFlag(irDataFirstByte )) { + + // The blink on on the other side of this connection is telling us that a button was pressed recently + // Send the viral message to all neighbors. + + viralPostponeWarmSleep(); + + // We also need to extend hardware sleep + // since we did not get a physical button press + BLINKBIOS_POSTPONE_SLEEP_VECTOR(); + + } + + + uint8_t decodedByte = irValueDecodeData( irDataFirstByte ); + + if ( packetDataLen == 1 ) { // normal user face value, One header byte + One data byte + + // We got a face value! Save it! + + face->inValue =decodedByte; + + + } else { // (packetDataLen>1) + + + if ( decodedByte == DATAGRAM_SPECIAL_VALUE) { + + uint8_t datagramPayloadLen = packetDataLen-2; // We deduct 2 from he length to account for the header byte and the trailing checksum byte + const uint8_t *datagramPayloadData = packetData+1; // Skip the packet header byte + + // Long packets are kind of a special case since we do not mark them read immediately + if ( computePacketChecksum( datagramPayloadData , datagramPayloadLen ) == datagramPayloadData[ datagramPayloadLen ] ) { // Run checksum on payload bytes after the header, compare that to the checksum at the end + + // Ok this packet checks out folks! + + if ( face->inDatagramLen == 0 && !(datagramPayloadLen > IR_DATAGRAM_LEN) ) { // Check if buffer free and datagram not too long + + face->inDatagramLen = datagramPayloadLen; + + memcpy( face->inDatagramData , datagramPayloadData , datagramPayloadLen); // Skip the header bytes + + } + + } + + } else { // packetLen > 1 && decodedByte != LONG_DATA_SPECIAL_VALUE + + // Here is look for a magic packet that has 2 bytes of data and both are the special sleep trigger cookie + + if ( packetDataLen == 2 && decodedByte == TRIGGER_WARM_SLEEP_SPECIAL_VALUE && packetData[1] == TRIGGER_WARM_SLEEP_SPECIAL_VALUE ) { + + warm_sleep_cycle(); + + } + + } // ( decodedByte == LONG_DATA_SPECIAL_VALUE) + + } // (packetDataLen>1) + + } else { + + // Invalid packet received. No good way to show or log this. :/ + + //#warning + //setColorNow( RED ); + + } + + } + + // No matter what, mark buffer as read so we can get next packet + ir_rx_state->packetBufferReady=0; + + } // if ( ir_data_buffer->ready_flag ) + + face++; + ir_rx_state++; + + } // for( uint8_t f=0; f < FACE_COUNT ; f++ ) + +} + + +// Buffer to build each outgoing IR packet +// This is the easy way to do this, but uses RAM unnecessarily. +// TODO: Make a scatter version of this to save RAM & time + +static uint8_t ir_send_packet_buffer[ IR_DATAGRAM_LEN + 2 ]; // header byte + Datagram payload + checksum byte + +static void TX_IRFaces() { + + // Use these pointers to step though the arrays + face_t *face = faces; + + for( uint8_t f=0; f < FACE_COUNT ; f++ ) { + + // Send one out too if it is time.... + + if ( face->sendTime <= now ) { // Time to send on this face? + // Note that we do not use the rx_fresh flag here because we want the timeout + // to do automatic retries to kickstart things when a new neighbor shows up or + // when an IR message gets missed + + uint8_t outgoingPacketLen; // Total length of the outgoing packet in ir_send_packet_buffer + uint8_t outgoiungPacketHeaderValue; // Value to encode into first byte of outgoing IR packet before transmitting + + // Ok, it is time to send something on this face + // Do we have a pending datagram? If so, datagrams get priority over face values + + if (face->outDatagramLen) { + + outgoiungPacketHeaderValue = DATAGRAM_SPECIAL_VALUE; + + // Build a datagram into the outgoing buffer including checksum + + uint8_t *d = ir_send_packet_buffer+1; // Data goes after the 1st byte header + const uint8_t *s = face->outDatagramData ; // Just to convert from void to uint8_t + + uint8_t datagramPayloadLen = face->outDatagramLen; + + memcpy( d, s , datagramPayloadLen ); + + // First header, then payload, when checksum + ir_send_packet_buffer[1+datagramPayloadLen] = computePacketChecksum( s , datagramPayloadLen ); + + outgoingPacketLen = 1 + datagramPayloadLen +1; // include header byte + payload + checksum (header added below) + + // Note that the outgoing datagram buffer will be cleared below if the IR send succeeds + + } else { + + // Just send a normal face value + outgoiungPacketHeaderValue = face->outValue; + outgoingPacketLen=1; + + } + + // Encode the header byte with the parity and viral button flag + + uint8_t encodedIrValue; + + if ( TBI( viralButtonPressSendOnFaceBitflags , f )) { + + // We need to send the viral button press on this face right now + + encodedIrValue= irValueEncode( outgoiungPacketHeaderValue , 1 ); + + CBI( viralButtonPressSendOnFaceBitflags , f ); + + + } else { + + encodedIrValue= irValueEncode( outgoiungPacketHeaderValue , 0 ); + + } + + ir_send_packet_buffer[0] = encodedIrValue; // store the encoded header into the outgoing buffer + + if (blinkbios_irdata_send_packet( f , ir_send_packet_buffer , outgoingPacketLen ) ) { + + // Here we set a timeout to keep periodically probing on this face, but + // if there is a neighbor, they will send back to us as soon as they get what we + // just transmitted, which will make us immediately send again. So the only case + // when this probe timeout will happen is if there is no neighbor there. + + // If ir_send_userdata() returns 0, then we could not send becuase there was an RX in progress on this face. + // Because we do not reset the sentTime in that case, we will automatically try again next pass. + + // We add the face index here to try to spread the sends out in time + // otherwise the degenerate case is that they can all happen repeatedly in the same + // pass thugh loop() every time when there are no neighbors. + + + face->sendTime = now + TX_PROBE_TIME_MS + f; + + + // Mark any pending datagram as sent + // safe to do this blindly because datagram always gets priority so it would have been + // what was just sent if there was one pending + face->outDatagramLen = 0; + + } + + } // if ( face->sendTime <= now ) + + face++; + + } // for( uint8_t f=0; f < FACE_COUNT ; f++ ) + +} + + +// Returns the last received state on the indicated face +// Remember that getNeighborState() starts at 0 on powerup. +// so returns 0 if no neighbor ever seen on this face since power-up +// so best to only use after checking if face is not expired first. +// Note the a face expiring has no effect on the getNeighborState() + +byte getLastValueReceivedOnFace( byte face ) { + + return faces[face].inValue; + +} + +// Did the neighborState value on this face change since the +// last time we checked? +// Remember that getNeighborState starts at 0 on powerup. +// Note the a face expiring has no effect on the getNeighborState() + +byte didValueOnFaceChange( byte face ) { + static byte prevState[FACE_COUNT]; + + byte curState = getLastValueReceivedOnFace(face); + + if ( curState == prevState[face] ) { + return false; + } + prevState[face] = curState; + + return true; + +} + + + +byte isValueReceivedOnFaceExpired( byte face ) { + + return faces[face].expireTime < now; + +} + +// Returns false if their has been a neighbor seen recently on any face, true otherwise. + +bool isAlone() { + + FOREACH_FACE(f) { + + if( !isValueReceivedOnFaceExpired(f) ) { + return false; + } + + } + return true; + +} + + +// Set our broadcasted state on all faces to newState. +// This state is repeatedly broadcast to any neighboring tiles. + +// By default we power up in state 0. + +void setValueSentOnAllFaces( byte value ) { + + if (value > IR_DATA_VALUE_MAX ) { + + value = IR_DATA_VALUE_MAX; + + } + + FOREACH_FACE(f) { + + faces[f].outValue = value; + + } + +} + +// Set our broadcasted state on indicated face to newState. +// This state is repeatedly broadcast to the partner tile on the indicated face. + +// By default we power up in state 0. + +void setValueSentOnFace( byte value , byte face ) { + + if (value > IR_DATA_VALUE_MAX ) { + + value = IR_DATA_VALUE_MAX; + + } + + faces[face].outValue = value; + +} + + + +// --------------Button code + + +// Here we keep a local snapshot of the button block stuff + +static uint8_t buttonSnapshotDown; // 1 if button is currently down (debounced) + +static uint8_t buttonSnapshotBitflags; + +static uint8_t buttonSnapshotClickcount; // Number of clicks on most recent multiclick + + +bool buttonDown(void) { + return buttonSnapshotDown != 0; +} + +static bool grabandclearbuttonflag( uint8_t flagbit ) { + bool r = buttonSnapshotBitflags & flagbit; + buttonSnapshotBitflags &= ~ flagbit; + return r; +} + +bool buttonPressed(void) { + return grabandclearbuttonflag( BUTTON_BITFLAG_PRESSED ); +} + +bool buttonReleased(void) { + return grabandclearbuttonflag( BUTTON_BITFLAG_RELEASED ); +} + +bool buttonSingleClicked() { + return grabandclearbuttonflag( BUTTON_BITFLAG_SINGLECLICKED ); +} + +bool buttonDoubleClicked() { + return grabandclearbuttonflag( BUTTON_BITFLAG_DOUBLECLICKED ); +} + +bool buttonMultiClicked() { + return grabandclearbuttonflag( BUTTON_BITFLAG_MULITCLICKED ); +} + + +// The number of clicks in the longest consecutive valid click cycle since the last time called. +byte buttonClickCount(void) { + return buttonSnapshotClickcount; +} + +// Remember that a long press fires while the button is still down +bool buttonLongPressed(void) { + return grabandclearbuttonflag( BUTTON_BITFLAG_LONGPRESSED ); +} + +// 6 second press. Note that this will trigger seed mode if the blink is alone so +// you will only ever see this if blink has neighbors when the button hits the 6 second mark. +// Remember that a long press fires while the button is still down +bool buttonLongLongPressed(void) { + return grabandclearbuttonflag( BUTTON_BITFLAG_3SECPRESSED ); +} + + +// --- Utility functions + +Color makeColorRGB( byte red, byte green, byte blue ) { + + // Internal color representation is only 5 bits, so we have to divide down from 8 bits + return Color( red >> 3 , green >> 3 , blue >> 3 ); + +} + +Color makeColorHSB( uint8_t hue, uint8_t saturation, uint8_t brightness ) { + + uint8_t r; + uint8_t g; + uint8_t b; + + if (saturation == 0) + { + // achromatic (grey) + r =g = b= brightness; + } + else + { + unsigned int scaledHue = (hue * 6); + unsigned int sector = scaledHue >> 8; // sector 0 to 5 around the color wheel + unsigned int offsetInSector = scaledHue - (sector << 8); // position within the sector + unsigned int p = (brightness * ( 255 - saturation )) >> 8; + unsigned int q = (brightness * ( 255 - ((saturation * offsetInSector) >> 8) )) >> 8; + unsigned int t = (brightness * ( 255 - ((saturation * ( 255 - offsetInSector )) >> 8) )) >> 8; + + switch( sector ) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + default: // case 5: + r = brightness; + g = p; + b = q; + break; + } + } + + return( makeColorRGB( r , g , b ) ); +} + +// OMG, the Ardiuno rand() function is just a mod! We at least want a uniform distibution. + +// We base our generator on a 32-bit Marsaglia XOR shifter +// https://en.wikipedia.org/wiki/Xorshift + +/* The state word must be initialized to non-zero */ + +// Here we use Marsaglia's seed (page 4) +// https://www.jstatsoft.org/article/view/v008i14 +static uint32_t rand_state=2463534242UL; + +// Generate a new seed using entropy from the watchdog timer +// This takes about 16ms * 32 bits = 0.5s + +void randomize() { + + WDTCSR = _BV(WDIE); // Enable WDT interrupt, leave timeout at 16ms (this is the shortest timeout) + + // The WDT timer is now generating an interrupt about every 16ms + // https://electronics.stackexchange.com/a/322817 + + for( uint8_t bit=32; bit; bit-- ) { + + blinkbios_pixel_block.capturedEntropy=0; // Clear this so we can check to see when it gets set in the background + while (blinkbios_pixel_block.capturedEntropy==0 || blinkbios_pixel_block.capturedEntropy==1 ); // Wait for this to get set in the background when the WDT ISR fires + // We also ignore 1 to stay balanced since 0 is a valid possible TCNT value that we will ignore + rand_state <<=1; + rand_state |= blinkbios_pixel_block.capturedEntropy & 0x01; // Grab just the bottom bit each time to try and maximum entropy + + } + + wdt_disable(); + +} + +// Note that rand executes the shift feedback register before returning the next result +// so hopefully we will be spreading out the entropy we get from randomize() on the first invokaton. + +static uint32_t nextrand32() +{ + // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" + uint32_t x = rand_state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + rand_state = x; + return x; +} + + +#define GETNEXTRANDUINT_MAX ( (word) -1 ) + +word randomWord(void) { + + // Grab bottom 16 bits + + return ( (uint16_t) nextrand32() ); + +} + +// return a random number between 0 and limit inclusive. +// https://stackoverflow.com/a/2999130/3152071 + +word random( uint16_t limit ) { + + word divisor = GETNEXTRANDUINT_MAX/(limit+1); + word retval; + + do { + retval = randomWord() / divisor; + } while (retval > limit); + + return retval; +} + +/* + + The original Arduino map function which is wrong in at least 3 ways. + + We replace it with a map function that has proper types, does not overflow, has even distribution, and clamps the output range. + + Our code is based on this... + + https://github.com/arduino/Arduino/issues/2466 + + ...downscaled to `word` width and with up casts added to avoid overflows (yep, even the corrected code + in the `map() function equation wrong` discoussion would still overflow :/ ). + + In the casts, we try to keep everything at the smallest possible width as long as possible to hold the result, but we have to bump up + to do the multiply. We then cast back down to (word) once we divide the (uint32_t) by a (word) since we know that will fit. + + We could trade code for performance here by special casing out each possible overflow condition and reordering the operations to avoid the overflow, + but for now space more important than speed. User programs can alwasy implement thier own map() if they need it since this will not link in if it is + not called. + + Here is some example code on how you might efficiently handle those multiplys... + + http://ww1.microchip.com/downloads/en/AppNotes/Atmel-1631-Using-the-AVR-Hardware-Multiplier_ApplicationNote_AVR201.pdf + +*/ + + + +word map(word x, word in_min, word in_max, word out_min, word out_max) +{ + // if input is smaller/bigger than expected return the min/max out ranges value + if (x < in_min) { + + return out_min; + + } else if (x > in_max) { + + return out_max; + + } else { + + // map the input to the output range. + if ((in_max - in_min) > (out_max - out_min)) { + + // round up if mapping bigger ranges to smaller ranges + // the only time we need full width to avoid overflow is after the multiply but before the divide, + // and the single (uint32_t) of the first operand should promote the entire expression - hopefully optimally. + return (word) ( ((uint32_t) (x - in_min)) * (out_max - out_min + 1) / (in_max - in_min + 1) ) + out_min; + + } else { + + // round down if mapping smaller ranges to bigger ranges + // the only time we need full width to avoid overflow is after the multiply but before the divide, + // and the single (uint32_t) of the first operand should promote the entire expression - hopefully optimally. + return (word) ( ((uint32_t) (x - in_min)) * (out_max - out_min) / (in_max - in_min) ) + out_min; + + } + } +} + +// Returns the device's unique 8-byte serial number +// TODO: This should this be in the core for portability with an extra "AVR" byte at the front. + +// 0xF0 points to the 1st of 8 bytes of serial number data +// As per "13.6.8.1. SNOBRx - Serial Number Byte 8 to 0" + + +const byte * const serialno_addr = ( const byte *) 0xF0; + + +// Read the unique serial number for this blink tile +// There are 9 bytes in all, so n can be 0-8 + + +byte getSerialNumberByte( byte n ) { + + if (n>8) return(0); + + return serialno_addr[n]; + +} + +// Returns the currently blinkbios version number. +// Useful to check is a newer feature is available on this blink. + +byte getBlinkbiosVersion() { + return BLINKBIOS_VERSION_VECTOR(); +} + +// Returns 1 if we have slept and woken since last time we checked +// Best to check as last test at the end of loop() so you can +// avoid intermediate display upon waking. + +uint8_t hasWoken(void) { + + uint8_t ret = 0; + + if (hasWarmWokenFlag) { + ret =1; + hasWarmWokenFlag = 0; + } + + if (blinkbios_button_block.wokeFlag==0) { // This flag is set to 0 when waking! + ret=1; + blinkbios_button_block.wokeFlag=1; + } + + return ret; + +} + +// --- Pixel functions + +// Change the tile to the specified color +// NOTE: all color changes are double buffered +// and the display is updated when loop() returns + + +// Set the pixel on the specified face (0-5) to the specified color +// NOTE: all color changes are double buffered +// and the display is updated when loop() returns + +// A buffer for the colors. +// We use a buffer so we can update all faces at once during a vertical +// retrace to avoid visual tearing from partially applied updates + +void setColorOnFace( Color newColor , byte face ) { + + // This is so ugly, but we need to match the volatile in the shared block to the newColor + // There must be a better way, but I don't know it other than a memcpy which is even uglier! + + // This at least gets the semantics right of coping a snapshot of the actual value. + + blinkbios_pixel_block.pixelBuffer[face].as_uint16 = newColor.as_uint16; // Size = 1940 bytes + + + // This BTW compiles much worse + + // *( const_cast (&blinkbios_pixel_block.pixelBuffer[face])) = newColor; // Size = 1948 bytes + +} + + +void setColor( Color newColor) { + + FOREACH_FACE(f) { + setColorOnFace( newColor , f ); + } + +} + + +void setFaceColor( byte face, Color newColor ) { + + setColorOnFace( newColor , face ); + +} + + +// This truly lovely code from the FastLED library +// https://github.com/FastLED/FastLED/blob/master/lib8tion/trig8.h +// ...adapted to save RAM by stroing the table in PROGMEM + +/// Fast 8-bit approximation of sin(x). This approximation never varies more than +/// 2% from the floating point value you'd get by doing +/// +/// float s = (sin(x) * 128.0) + 128; +/// +/// @param theta input angle from 0-255 +/// @returns sin of theta, value between 0 and 255 + +PROGMEM const uint8_t b_m16_interleave[] = { 0, 49, 49, 41, 90, 27, 117, 10 }; + +byte sin8_C( byte theta) +{ + uint8_t offset = theta; + if( theta & 0x40 ) { + offset = (uint8_t)255 - offset; + } + offset &= 0x3F; // 0..63 + + uint8_t secoffset = offset & 0x0F; // 0..15 + if( theta & 0x40) secoffset++; + + uint8_t section = offset >> 4; // 0..3 + uint8_t s2 = section * 2; + const uint8_t* p = b_m16_interleave; + p += s2; + + uint8_t b = pgm_read_byte(p); + p++; + uint8_t m16 = pgm_read_byte(p); + + uint8_t mx = (m16 * secoffset) >> 4; + + int8_t y = mx + b; + if( theta & 0x80 ) y = -y; + + y += 128; + + return y; +} + +// #define NO_STACK_WATCHER to disable the stack overflow detection. +// saves a few bytes of flash and 2 bytes RAM + +#ifdef NO_STACK_WATCHER + + + void statckwatcher_init() { + } + + uint8_t stackwatcher_intact() { + return 1; + } + + +#else + + // We use this sentinel to see if we blew the stack + // Note that we can not statically initialize this because it is not + // in the initialized part of the data section. + // We check it periodically from the ISR + + uint16_t __attribute__(( section(".stackwatcher") )) stackwatcher; + + #define STACKWATCHER_MAGIC_VALUE 0xBABE + + void statckwatcher_init() { + stackwatcher = STACKWATCHER_MAGIC_VALUE; + } + + uint8_t stackwatcher_intact() { + return stackwatcher == STACKWATCHER_MAGIC_VALUE; + } + + + +#endif + +// This is the main event loop that calls into the arduino program +// (Compiler is smart enough to jmp here from main rather than call! +// It even omits the trailing ret! +// Thanks for the extra 4 bytes of flash gcc!) + +void __attribute__((noreturn)) run(void) { + + // TODO: Is this right? Should hasWoke() return true or false on the first check after start up? + + blinkbios_button_block.wokeFlag = 1; // Clear any old wakes (wokeFlag is cleared to 0 on wake) + + updateNow(); // Initialize out internal millis so that when we reset the warm sleep counter it is right, and so setup sees the right millis time + reset_warm_sleep_timer(); + + statckwatcher_init(); // Set up the sentinel byte at the top of RAM used by variables so we can tell if stack clobbered it + + setup(); + + while (1) { + + // Did we blow the stack? + + if (!stackwatcher_intact()) { + // If so, show error code to user + BLINKBIOS_ABEND_VECTOR(4); + } + + // Here we check to enter seed mode. The button must be held down for 6 seconds and we must not have any neighbors + // Note that we directly read the shared block rather than our snapshot. This lets the 6 second flag latch and + // so to the user program if we do not enter seed mode because we have neighbors. See? + + if (( blinkbios_button_block.bitflags & BUTTON_BITFLAG_3SECPRESSED) && isAlone() ) { + + // Button has been down for 6 seconds and we are alone... + // Signal that we are about to go into seed mode with full blue... + + // First save the game pixels because our blue seed spin is going to mess them up + // and we will need to get them back if the user continues to hold past the seed phase + // and into the warm sleep phase. + + savePixels(); + + // Now wait until either the button is lifted or is held down past 7 second mark + // so we know what to do + + uint8_t face = 0; + + while ( blinkbios_button_block.down && ! ( blinkbios_button_block.bitflags & BUTTON_BITFLAG_6SECPRESSED) ) { + + // Show a very fast blue spin that it would be hard for a user program to make + // during the 1 second they have to let for to enter seed mode + + setColor(OFF); + setColorOnFace( BLUE , face++ ); + if (face==FACE_COUNT) face=0; + BLINKBIOS_DISPLAY_PIXEL_BUFFER_VECTOR(); + + } + + restorePixels(); + + if ( blinkbios_button_block.bitflags & BUTTON_BITFLAG_6SECPRESSED ) { + + // Held down past the 7 second mark, so this is a force sleep request + + warm_sleep_cycle(); + + // Clear out the press that put us to sleep so we do not see it again + // Also clear out everything else so we start with a clean slate on waking + + blinkbios_button_block.bitflags = 0; + + } else { + + // They let go before we got to 7 seconds, so enter SEED mode! (and never return!) + + // Give instant visual feedback that we know they let go of the button + // Costs a few bytes, but the checksum in the bootloader takes a a sec to complete before we start sending) + setColorNow( OFF ); + + BLINKBIOS_BOOTLOADER_SEED_VECTOR(); + + __builtin_unreachable(); + + } + } + + if ( ( blinkbios_button_block.bitflags & BUTTON_BITFLAG_6SECPRESSED) ) { + + warm_sleep_cycle(); + + // Clear out the press that put us to sleep so we do not see it again + // Also clear out everything else so we start with a clean slate on waking + blinkbios_button_block.bitflags = 0; + + } + + // Capture time snapshot + // Used by millis() and Timer thus functions + // This comes after the possible button holding to enter seed mode + + updateNow(); + + if ( blinkbios_button_block.bitflags & BUTTON_BITFLAG_PRESSED ) { // Any button press resets the warm sleep timeout + viralPostponeWarmSleep(); + } + + cli(); + buttonSnapshotDown = blinkbios_button_block.down; + buttonSnapshotBitflags |= blinkbios_button_block.bitflags; // Or any new flags into the ones we got + blinkbios_button_block.bitflags=0; // Clear out the flags now that we have them + buttonSnapshotClickcount = blinkbios_button_block.clickcount; + sei(); + + + // Update the IR RX state + // Receive any pending packets + RX_IRFaces(); + + loop(); + + // Update the pixels to match our buffer + + BLINKBIOS_DISPLAY_PIXEL_BUFFER_VECTOR(); + + // Transmit any IR packets waiting to go out + // Note that we do this after loop had a chance to update them. + TX_IRFaces(); + + if (warm_sleep_time.isExpired()) { + + warm_sleep_cycle(); + + } + + } + + +} diff --git a/cores/blinklib/blinklib.h b/cores/blinklib/blinklib.h new file mode 100644 index 00000000..93e0bcdc --- /dev/null +++ b/cores/blinklib/blinklib.h @@ -0,0 +1,382 @@ +/* + * blkinklib.h + * + * This defines a statefull view of the blinks tile interactions with neighbors. + * + * In this view, each tile has a "state" that is represented by a number between 1 and 127. + * This state value is continuously broadcast on all of its faces. + * Each tile also remembers the most recently received state value from he neighbor on each of its faces. + * + * Note that this library depends on the blinklib library for communications with neighbors. The blinklib + * IR read functions are not available when using the blinkstate library. + * + * Note that the beacon transmissions only occur when the loop() function returns, so it is important + * that sketches using this model return from loop() frequently. + * + */ + +#ifndef BLINKLIB_H_ +#define BLINKLIB_H_ + +#include // UINTLONG_MAX for NEVER +#include "ArduinoTypes.h" + +// Number of faces on a blink. Looks nicer than hardcoding '6' everywhere. + +#define FACE_COUNT 6 + +/* + + IR communications functions + +*/ + +// The value of the data sent and received on faces via IR can be between 0 and IR_DATA_VALUE_MAX +// If you try to send higher than this, the max value will be sent. + +#define IR_DATA_VALUE_MAX 63 + +// Returns the last received value on the indicated face +// Between 0 and IR_DATA_VALUE_MAX inclusive +// returns 0 if no neighbor ever seen on this face since power-up +// so best to only use after checking if face is not expired first. + +byte getLastValueReceivedOnFace( byte face ); + +// Did the neighborState value on this face change since the +// last time we checked? + +// Note the a face expiring has no effect on the last value + +byte didValueOnFaceChange( byte face ); + +// false if messages have been recently received on the indicated face +// (currently configured to 100ms timeout in `expireDurration_ms` ) + +byte isValueReceivedOnFaceExpired( byte face ); + +// Returns false if their has been a neighbor seen recently on any face, returns true otherwise. +bool isAlone(); + +// Set value that will be continuously broadcast on specified face. +// Value should be between 0 and IR_DATA_VALUE_MAX inclusive. +// If a value greater than IR_DATA_VALUE_MAX is specified, IR_DATA_VALUE_MAX will be sent. +// By default we power up with all faces sending the value 0. + +void setValueSentOnFace( byte value , byte face ); + +// Same as setValueSentOnFace(), but sets all faces in one call. + +void setValueSentOnAllFaces( byte value ); + +/* --- Datagram processing */ + +// A datagram is a set of 1-IR_DATAGRAM_MAX_LEN bytes that are atomically sent over the IR link +// The datagram is sent immediately on a best efforts basis. If it is not received by the other side then +// it is lost forever. Each datagram sent is received at most 1 time. Once you have processed a received datagram +// then you must mark it as read before you can receive the next one on that face. + +// Must be smaller than IR_RX_PACKET_SIZE + +#define IR_DATAGRAM_LEN 16 + +// Returns the number of bytes waiting in the data buffer, or 0 if no packet ready. +byte getDatagramLengthOnFace( uint8_t face ); + +// Returns true if a packet is available in the buffer +boolean isDatagramReadyOnFace( uint8_t face ); + + // Returns a pointer to the actual received datagram data + // This should really be a (void *) so it can be assigned to any pointer type, + // but in C++ you can not cast a (void *) into something else so it doesn't really work there + // and I think too ugly to have these functions that are inverses of each other to take/return different types. + // Thanks, Stroustrup. +const byte *getDatagramOnFace( uint8_t face ); + +// Frees up the buffer holding the datagram data. Do this as soon as possible after you have +// processed the datagram to free up the slot for the next incoming datagram on this face. +// If a new datagram is recieved on a face before markDatagramReadOnFace() is called then +// the new datagram is siliently discarded. + +void markDatagramReadOnFace( uint8_t face ); + +// Send a datagram. +// Datagram is sent as soon as possible and takes priority over sending a value on face. +// If you call sendDatagramOnFace() and there is already a pending datagram, the older pending +// one will be replaced with the new one. + +// Note that if the len>IR_DATAGRAM_LEN then packet will never be sent or recieved + +void sendDatagramOnFace( const void *data, byte len , byte face ); + + +/* + + This set of functions let you test for changes in the environment. + +*/ + +/* + + Button functions + +*/ + +// Debounced view of button state. true if the button currently pressed. + +bool buttonDown(void); + +// Was the button pressed or lifted since the last time we checked? +// Note that these register the change the instant the button state changes +// without any delay, so good for latency sensitive cases. +// It is debounced, so the button must have been in the previous state a minimum +// debounce time before a new detection will occur. + +bool buttonPressed(void); +bool buttonReleased(void); + +// Was the button single, double , or multi clicked since we last checked? +// Note that there is a delay after the button is first pressed +// before a click is registered because we have to wait to +// see if another button press is coming. +// A multiclick is 3 or more clicks + +// Remember that these click events fire a short time after the button is lifted on the final click +// If the button is held down too long on the last click, then click interaction is aborted. + +bool buttonSingleClicked(); + +bool buttonDoubleClicked(); + +bool buttonMultiClicked(); + + +// The number of clicks in the longest consecutive valid click cycle since the last time called. +byte buttonClickCount(void); + +// Remember that a long press fires while the button is still down +bool buttonLongPressed(void); + +// 6 second press. Note that this will trigger seed mode if the blink is alone so +// you will only ever see this if blink has neighbors when the button hits the 6 second mark. +// Remember that a long press fires while the button is still down +bool buttonLongLongPressed(void); + +/* + + This set of functions lets you control the colors on the face RGB LEDs + +*/ + + +// Ok, it kills me to include this #include here because it pulls all of these symbols into +// the Arduino namespace... but, dude, Arduino pulls in everything anyway!!!! +// To do it right we'd break out the only thing we actually want (pixelColor_t) into +// its own header file and only include that. Maybe someday. +// TODO: Break pixelColor_t into blinkbios_shared_pixelcolor_t.h just for neatness counts + +#include "shared/blinkbios_shared_pixel.h" + +typedef pixelColor_t Color; + +// Number of visible brightness levels in each channel of a color +#define BRIGHTNESS_LEVELS_5BIT 32 +#define MAX_BRIGHTNESS_5BIT (BRIGHTNESS_LEVELS_5BIT-1) + +// R,G,B are all in the domain 0-31 +// Here we expose the internal color representation, but it is worth it +// to get the performance and size benefits of static compilation +// Shame no way to do this right in C/C++ + +#define MAKECOLOR_5BIT_RGB(r,g,b) (pixelColor_t(r,g,b,1)) + +#define RED MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT, 0 ,0) +#define ORANGE MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT/2 ,0) +#define YELLOW MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT ,0) +#define GREEN MAKECOLOR_5BIT_RGB( 0 ,MAX_BRIGHTNESS_5BIT ,0) +#define CYAN MAKECOLOR_5BIT_RGB( 0 ,MAX_BRIGHTNESS_5BIT ,MAX_BRIGHTNESS_5BIT) +#define BLUE MAKECOLOR_5BIT_RGB( 0 , 0 ,MAX_BRIGHTNESS_5BIT) +#define MAGENTA MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT, 0 ,MAX_BRIGHTNESS_5BIT) + +#define WHITE MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT ,MAX_BRIGHTNESS_5BIT) + +#define OFF MAKECOLOR_5BIT_RGB( 0 , 0 , 0) + +#define GET_5BIT_R(color) (color.r) +#define GET_5BIT_G(color) (color.g) +#define GET_5BIT_B(color) (color.b) + +// Dim the specified color. Brightness is 0-255 (0=off, 255=don't dim at all-keep original color) +// Inlined to allow static simplification at compile time + +#define MAX_BRIGHTNESS (255) + +Color dim( Color color, byte brightness); + +// This maps 0-255 values to 0-31 values with the special case that 0 (in 0-255) is the only value that maps to 0 (in 0-31) +// This leads to some slight non-linearity since there are not a uniform integral number of 1-255 values +// to map to each of the 1-31 values. + +// Make a new color from RGB values. Each value can be 0-255. + +Color makeColorRGB( byte red, byte green, byte blue ); + +// Make a new color in the HSB colorspace. All values are 0-255. + +Color makeColorHSB( byte hue, byte saturation, byte brightness ); + +// Change the tile to the specified color +// NOTE: all color changes are double buffered +// and the display is updated when loop() returns + +void setColor( Color newColor); + +// Set the pixel on the specified face (0-5) to the specified color +// NOTE: all color changes are double buffered +// and the display is updated when loop() returns + +void setColorOnFace( Color newColor , byte face ); + +// DEPREICATED: Use setColorOnFace() +//void setFaceColor( byte face , Color newColor ) __attribute__ ((deprecated)); +void setFaceColor( byte face, Color newColor ); + +/* + + Timing functions + +*/ + +// Number of running milliseconds since power up. +// +// Important notes: +// 1) does not increment while sleeping +// 2) is only updated between loop() interations +// 3) is not monotonic, so always use greater than +// and less than rather than equals for comparisons +// 4) overflows after about 50 days +// 5) is only accurate to about +/-10% + +unsigned long millis(void); + +class Timer { + + private: + + uint32_t m_expireTime; // When this timer will expire + + public: + + Timer() {}; // Timers come into this world pre-expired. + + bool isExpired(); + + uint32_t getRemaining(); + + void set( uint32_t ms ); // This time will expire ms milliseconds from now + + void add( uint16_t ms ); + + void never(void); // Make this timer never expire (unless set()) + +}; + + +/* + + Utility functions + +*/ + + +// Return a random number between 0 and limit inclusive. +// By default uses a hardcoded seed. If you need different blinks +// to generate different streams of random numbers, then call +// randomize() once (probably in setup()) to generate a truely random seed. + +word random( word limit ); + +// Generate a random 16 bit word. Slightly faster than random(), +// but be careful because you will not get a uniform distribution +// unless your desired range is a power of 2. + +word randomWord(void); + +// Generate a new random seed using entropy from the watchdog timer +// This takes about 16ms * 32 bits = 0.5s + +void randomize(); + +// Read the unique serial number for this blink tile +// There are 9 bytes in all, so n can be 0-8 + +#define SERIAL_NUMBER_LEN 9 + +byte getSerialNumberByte( byte n ); + +// Returns the current blinkbios version number. +// Useful to check is a newer feature is available on this blink. + +byte getBlinkbiosVersion(); + +// Map one set to another +// Note that this explodes to big code, so do the explicit calculations +// by hand if you are running out of flash space. + +word map(word x, word in_min, word in_max, word out_min, word out_max); + +// Maps theta 0-255 to values 0-255 in a sine wave +// Based on fabulous FastLED library code here... +// https://github.com/FastLED/FastLED/blob/master/lib8tion/trig8.h#L159 + +byte sin8_C( byte theta); + +/* Power functions */ + +// The blink will automatically sleep if the button has not been pressed in +// more than 10 minutes. The sleep is preemptive - the blink stops in the middle of whatever it +// happens to be doing. + +// The blink wakes from sleep when the button is pressed. Upon waking, it picks up from exactly +// where it left off. It is up to the programmer to check to see if the blink has slept and then +// woken and react accordingly if desired. + +// Returns 1 if we have woken from sleep since last time we checked + +uint8_t hasWoken(void); + +/* + + These hook functions are filled in by the sketch + +*/ + + +// Called when this sketch is first loaded and then +// every time the tile wakes from sleep + +void setup(void); + +// Called repeatedly just after the display pixels +// on the tile face are updated + +void loop(); + + +/* + + Some syntactic sugar to make our programs look not so ugly. + +*/ + + + +// 'Cause C ain't got no iterators and all those FOR loops are too ugly. +// TODO: Yuck, gcc expands this loop index to a word, which costs a load, a compare, and a multiply. :/ +#define FOREACH_FACE(x) for(uint8_t x = 0; x < FACE_COUNT ; ++ x) // Pretend this is a real language with iterators + +// Get the number of elements in an array. +#define COUNT_OF(x) ((sizeof(x)/sizeof(x[0]))) + + +#endif /* BLINKLIB_H_ */ diff --git a/cores/blinklib/main.cpp b/cores/blinklib/main.cpp new file mode 100644 index 00000000..be992a7a --- /dev/null +++ b/cores/blinklib/main.cpp @@ -0,0 +1,45 @@ + +// Here are our magic shared memory links to the BlinkBIOS running up in the bootloader area. +// These special sections are defined in a special linker script to make sure that the addresses +// are the same on both the foreground (this blinklib program) and the background (the BlinkBIOS project compiled to a HEX file) + +#include "shared/blinkbios_shared_button.h" +#include "shared/blinkbios_shared_millis.h" +#include "shared/blinkbios_shared_pixel.h" +#include "shared/blinkbios_shared_irdata.h" + +#include "blinklib.h" + +#include "run.h" + +// Here are the actual allocations for the shared memory blocks + +// We put each in its own section so that the separately compiled blinkos will be able to find them. + +// Note that without the `used` attribute, these blocks get tossed even though they are marked as `KEEP` in the linker script + +blinkbios_pixelblock_t __attribute__ ((section (".ipcram1") , used )) blinkbios_pixel_block; +blinkbios_millis_block_t __attribute__ ((section (".ipcram2") , used )) blinkbios_millis_block; +blinkbios_button_block_t __attribute__ ((section (".ipcram3") , used )) blinkbios_button_block; +blinkbios_irdata_block_t __attribute__ ((section (".ipcram4") , used )) blinkbios_irdata_block; + +// Here is our entry point. We are called by the BlinkBIOS after everything is set up and ready +// Note that this is not a normal startup, we are staring right from flash address 0x000 with no +// vector table at all. We don't need one because all vectors are pointing to the BlinkBIOS +// running up in the bootloader area. + +// We will never return, so don't need any of the extra formality, just give me the straight up code + +#include + +extern "C" void mainx(void) __attribute__ ((used)) __attribute__ ((noreturn)); + +extern "C" void mainx(void) { + + run(); + + __builtin_unreachable(); + + // Don't fall off the edge of the earth here! + // Make sure we stop above! +} diff --git a/cores/blinklib/run.h b/cores/blinklib/run.h new file mode 100644 index 00000000..32800b4c --- /dev/null +++ b/cores/blinklib/run.h @@ -0,0 +1,5 @@ + +// Link from mainx() into blinklib.cpp +// Called to start the sketch. + +void __attribute__((noreturn)) run(void); \ No newline at end of file diff --git a/cores/blinklib/shared/BlinkBIOS.hex b/cores/blinklib/shared/BlinkBIOS.hex new file mode 100644 index 00000000..ae7fbb2f --- /dev/null +++ b/cores/blinklib/shared/BlinkBIOS.hex @@ -0,0 +1,278 @@ +:102E00000AB400FCFDCF90938900809388008AB5B6 +:102E100081608ABD08959B01F894409131015091E1 +:102E2000320160913301709134017894420F531F45 +:102E3000611D711DFC01408351836283738308957A +:102E4000CF93C62F7AD5882349F08DE55DD58C2F99 +:102E50005BD585E78C1B58D5CF9146C5CF9108959A +:102E6000CF92DF92FF920F931F93CF93DF93062FA2 +:102E7000F42E152F62D58823F1F08BED45D5CF2D9B +:102E8000D12F6E0180E2C80ED11C1BEDF9908F2D61 +:102E90003BD51F0DCC16DD06C9F7802F35D582ED49 +:102EA000801B811B31D5DF91CF911F910F91FF9036 +:102EB000DF90CF9019C5DF91CF911F910F91FF90B7 +:102EC000DF90CF900895CF93DF93C0E0D1E0DE0193 +:102ED000EC912E2F229526952695237011968C9194 +:102EE0001197982F9770990F990F922B8695869529 +:102EF0008695E695EF71F0E0EC5BF74CE491ED8799 +:102F0000E92FF0E0EC5BF74CE491EE87E82FF0E07E +:102F1000EC5BF74CE491EF871296249681E0AC309D +:102F2000B807B1F6DF91CF910895E0E0F1E011929A +:102F300082E0EE37F807D8F38091310190913201A9 +:102F4000A0913301B0913401805E9D4EA54FBF4FDB +:102F50008093360190933701A0933801B0933901E3 +:102F6000B2CFEDE4F1E0108212AA1092B1011092FA +:102F7000E30110921502109247020895F894EEE3CF +:102F8000F1E01182148216821582128611867894DD +:102F9000089582E00895FF920F931F93CF93DF93DC +:102FA000F62E072F142FC9D4882361F087E3ACD401 +:102FB000CF2DD02F112321F08991A6D41150FACF13 +:102FC00093D481E0DF91CF911F910F91FF900895ED +:102FD000182FC12FDE63DF738D2F90E09ED52FEF6A +:102FE00080E792E0215080409040E1F700C000006F +:102FF000B8DF80E090E091D52FEF80E792E021509C +:1030000080409040E1F700C00000ABDFC15021F0EC +:1030100080913F0180FFDECFC6E0C15021F080915A +:103020003F0180FF0CC080913F0180FFD2CFF89418 +:1030300080E18093600088E080936000FFCF80E0B3 +:1030400090E06BD52FEF80E792E0215080409040D8 +:10305000E1F700C0000085DF80E090E05ED52FEF53 +:1030600080E792E0215080409040E1F700C00000EE +:1030700078DFD3CF14BE0FB6F894A8958091600086 +:10308000886180936000109260000FBEC1E6D0E0BE +:1030900010E8188301E008838EE680937C0045DF0A +:1030A00085B7982F916095BF826085BF10BF84E877 +:1030B00080936400E8E6F0E08081846080835F9A1A +:1030C000729A589A599A269A689A279A699A549A9B +:1030D000529A5E9A5D9A569A559A00936E0082E0D3 +:1030E00080937000D6D380E0E82E81E0F82EF701BF +:1030F00000A784B18F6384B987B1806C87B9789455 +:1031000086EC80937A0012D5F8941883188278940C +:10311000F70110A6BED45CD4CF93DF93CDB7DEB752 +:10312000CB5BD1090FB6F894DEBF0FBECDBFC4553F +:10313000DF4F8883CC5AD04008D581E08093790254 +:1031400080E090E0EAD4C455DF4F2881CC5AD040CB +:103150002223F9F0412C512CE0E0F0E08491480E5C +:10316000511C3196E11537E1F307C1F768E873E1C7 +:10317000CE018F559F4F4FDE4EE2C655DF4F48833D +:10318000CA5AD0401FE300E088EBF82E96E0B92E33 +:1031900043C01DE4812E11E0912EE12CF12C12E3AD +:1031A000BE2C1E9D60011F9DD00C1124F601E35B17 +:1031B000FE4F8081882309F4AAC083818B3009F0F7 +:1031C000A6C08281843009F0A2C0C4014DD38823F7 +:1031D00009F49DC0F601E05BFE4F4180528001E0A2 +:1031E0000E0D063019F400E0B5E0BB2E81E090E052 +:1031F0000B2C01C0880F0A94EAF7182F10951F7343 +:1032000060E070E0CE018F559F4F05DEC655DF4F61 +:103210001882CA5AD040F12CCB55DF4F1882198240 +:103220001A821B82C55AD040A9DECB54DF4F488298 +:10323000C55BD040CA54DF4F5882C65BD04087EC94 +:10324000282E24182518C954DF4F2882C75BD04088 +:1032500053E0C755DF4F5883C95AD040C555DF4F9B +:103260001882CB5AD0407AEBE72E8B2D90E0E2E328 +:10327000E89F9001E99F300D1124A901435B5E4F47 +:103280001A01FA013396C155DF4FF983E883CF5A0B +:10329000D040A901405B5E4FCF54DF4F5983488334 +:1032A000C15BD040FC01EE0FFF1FCD54DF4FF9830F +:1032B000E883C35BD0404F5F5F4FC854DF4F5983F3 +:1032C0004883C85BD040E050FF4FC355DF4FF983C0 +:1032D000E883CD5AD0403E01F1E86F0E711C930196 +:1032E000205E3F4FC654DF4F39832883CA5BD040EE +:1032F000C455DF4F3881CC5AD040332319F0112305 +:1033000009F406C280913F0180FF0DC001C23FEF6A +:10331000E31AF30A42E3840E911C56E0E516F10429 +:1033200009F03ECF63CFC455DF4F8881CC5AD040DF +:10333000811198C0F1018081882309F493C0C1559F +:10334000DF4FE881F981CF5AD04080818B3D09F071 +:1033500060C0F1018281833209F05BC0C10184D277 +:10336000882309F456C0CF54DF4FE881F981C15B4F +:10337000D04081A18F1160C0CD54DF4FE881F98129 +:10338000C35BD040E050FF4F818187709F2D9295A5 +:10339000990F907E892B81838F2D8370A1E0B0E0FF +:1033A000AC0FBD1FF0E28F9FA00DB11D112490E264 +:1033B000C854DF4FE881F981C85BD04001900D927D +:1033C0009A95E1F78330A1F4BE016F5F7F4FC65538 +:1033D000DF4F8881CA5AD04085D3C655DF4F288138 +:1033E000CA5AD0402F5FC655DF4F2883CA5AD040F3 +:1033F000F39438EBF3122CC0C355DF4FE881F98109 +:10340000CD5AD0408081806C808383E08183A12C61 +:1034100017C0C155DF4FE881F981CF5AD040808174 +:10342000883E51F4F1018281823031F4C1011CD215 +:10343000AA24A394811101C0A12CF8EBFF120CC0A7 +:10344000111108C069EB8B2DFBDC68EBF62E07C071 +:10345000A12C02C058EBF52E6F2D8B2DF1DCF10164 +:10346000108201C0A12CC02ED12CF2E3FC9D4001A2 +:10347000FD9D900C1124F401E35BFE4F80818823B5 +:1034800009F45CC083818D3509F04DC082818330A1 +:1034900009F049C0CF01E8D1882309F444C021E0F4 +:1034A00030E0002E01C0220F0A94EAF7C555DF4F25 +:1034B0004881CB5AD040422BC555DF4F4883CB5A69 +:1034C000D040F401E05BFE4FE180C601880F991FF8 +:1034D00057EB5E15D8F4E9EBEE120BC020951223E2 +:1034E000FC01E050FF4F2081206C20832181276068 +:1034F00009C0FC01E050FF4F2081206C2083218116 +:10350000287F236021839924939401C0912CFC018E +:10351000E050FF4F818187709E2D9295990F907E8C +:10352000892B818301C0912C22E32C9DF0012D9DDC +:10353000F00D1124E35BFE4F108201C0912CEF14BB +:1035400008F03DC08E2D86958695C655DF4F388193 +:10355000CA5AD0408317B8F440E2E49EC001112457 +:10356000D30186199709FC01EA0FFB1FE491ED9343 +:10357000C654DF4FE881F981CA5BD040AE17BF0760 +:1035800091F7A3010BC08E2D837041E050E04C0FEA +:103590005D1FF0E28F9F400D511D11246E2D802F75 +:1035A0005FDC68E770E0CE018B559F4F34DC23E091 +:1035B000C755DF4F2883C95AD0403AEBE32EF89421 +:1035C0004091310150913201609133017091340189 +:1035D0007894CB55DF4F88819981AA81BB81C55AE8 +:1035E000D04084179507A607B70718F09110C1C0FF +:1035F0008BC0C755DF4FE881C95AD040E111AEC03A +:10360000F8944091310150913201609133017091F1 +:1036100034017894CF55DF4F88819981AA81BB818D +:10362000C15AD04084179507A607B70748F481E030 +:1036300090E00C2C01C0880F0A94EAF780951823BB +:10364000F601EE0FFF1FE050FF4F818187708183ED +:10365000112309F459C0212F30E00F5F063009F41F +:1036600000E0C901002E02C0959587950A94E2F703 +:1036700080FFF3CF33E0C755DF4F3883C95AD040BE +:10368000802F5BD18823A9F08BE03ED1CB54DF4F54 +:103690008881C55BD04038D1CA54DF4F8881C65B72 +:1036A000D04032D1C954DF4F8881C75BD0402CD184 +:1036B0001BD1E02FF0E0EE0FFF1FE050FF4F8181A4 +:1036C000886F8183C555DF4F4881CB5AD040842F06 +:1036D00090E0002E02C0959587950A94E2F780FD50 +:1036E00040C062E370E0CE018B559F4F94DBC7551D +:1036F000DF4F5881C95AD0405150C755DF4F5883CA +:10370000C95AD0408AEBE82EDEDBAA2009F4F0CDBE +:103710001092790233DC98EBF91226C080E090E039 +:10372000E0E0F0E02491820F911D3196E11527E150 +:10373000F207C1F748165906B9F403E010E0812FEB +:10374000FCD0882329F088EEDFD08AEEDDD0CCD003 +:103750001F5F1630A1F7015089F73AD1992031F057 +:103760006FCF68E770E0BFCF83E032DC111188CF04 +:10377000CBCFC755DF4FF881C95AD040FF2309F49A +:103780003FCF5ECFF89404E00EBF0FEF0DBF7894EB +:0E379000C3DC80E0F7DF7DD181E0F4DF19DCDF +:1038000031C00000FDCF0000FBCF0000F9CF000069 +:10381000C2CB000011C1000010C10000E9C10000CE +:1038200074C10000B8CF000090C10000DAC00000F1 +:103830009DC1000011C10000ACCB0000AFCF000063 +:10384000D7C10000FFFEFDFBFAF8F5F2EEEAE6E074 +:10385000DAD3CCC3BAB0A5998C7E6F5F4E3B2813E8 +:103860000D090301112406DCFC012281339690E04E +:10387000215018F08191980FFBCF81E0923D09F023 +:1038800080E008952E9870982F9871985C985A98B7 +:1038900008958FEF87BD88BD16BC95B5906895BD1E +:1038A00095B5906495BD93EA94BD93E29093B00072 +:1038B00090E89093B3008093B4001092B200E1EBD3 +:1038C000F0E080818064808383B5806883BD83B5A8 +:1038D000826083BD83B5816083BD82E085BD8083C6 +:1038E00083B58F7783BD089580EB97E189DAEDE4A6 +:1038F000F1E0118213AA1092B2011092E401109229 +:1039000016021092480208950F931F93CF93DF93EE +:10391000082FC8E0D0E011E0812F802319F08CE05F +:1039200096E002C08EED9EE06BDA110F2197A1F7B1 +:10393000DF91CF911F910F910895F89442E3849FF6 +:10394000F0011124E35BFE4F95A5911103C091E0B6 +:10395000918301C090E078949923C1F121E030E097 +:10396000A90102C0440F551F8A95E2F7CA012091B0 +:1039700081002111FCCF98E190938100109280008A +:10398000109285001092840021E030E0309389008D +:103990002093880093E0909380002CE036E03093F1 +:1039A000890020938800B19A90916F009260909363 +:1039B0006F0080938A008AB581608ABD89E1809317 +:1039C000810080EB97E11CDA81E0089580E00895A2 +:1039D000F894ABDA04E00EBF0FEF0DBF78940C94AF +:1039E00000001F920F920FB60F9211242F938F9306 +:1039F0009F9390918A0086B1892397B993B92AE160 +:103A00002A95F1F700C093B986B995E09A95F1F738 +:103A1000000017B886B90AB400FC03C010928100F8 +:103A200003C08AB58E7F8ABD9F918F912F910F9091 +:103A30000FBE0F901F90189518951F920F920FB6FA +:103A40000F9211248F9386B5809327018F910F9049 +:103A50000FBE0F901F90189590E8899FF0011124D8 +:103A6000F89483E080935700E895789407B600FCBB +:103A7000FDCFF89485E080935700E895789407B6D9 +:103A800000FCFDCFF89481E180935700E89578948D +:103A900008950F931F93CF93DF9310E020E037E159 +:103AA00001E080E090E0E901C80FD91F803891055E +:103AB00069F0FE0145915491F894FC010A010093CC +:103AC0005700E895112478940296EDCF812FC4DF3A +:103AD0001F5F1E3211F09E01E4CFDF91CF911F9145 +:103AE0000F910895E0E0F0E091E0F894DB012D9172 +:103AF0003D91BD01090190935700E89511247894F8 +:103B00003296E038F10589F7A7CF81E080930C0168 +:103B100080910C018111FCCFD6C9E0E0F1E08193E6 +:103B2000919321E0EC30F207D1F7EFCF00008091C4 +:103B30007A0086FDFCCF8091790096EC90937A0014 +:103B400087FF02C082E044CA0895F894E1E3F1E0FF +:103B500080819181A281B381805E9D4EA54FBF4F30 +:103B600085839683A783B08778940895F89480E836 +:103B70008093610081E080936100789415BC82DEBF +:103B80001092B10014BC1092B0006B982FEF80E738 +:103B900092E0215080409040E1F700C000004F9B30 +:103BA000FECF2FEF80E792E0215080409040E1F778 +:103BB00000C0000080916D00806880936D0085E0FA +:103BC00083BF889580916D008F7780936D0061DE53 +:103BD00086EC80937A00AADFF89480E880936100F5 +:103BE000109261007894CAD9B0DF10924101089513 +:103BF0001F920F920FB60F921124EF92FF920F9324 +:103C00001F932F933F934F935F936F937F938F9364 +:103C10009F93AF93BF93CF93DF93EF93FF9316B12F +:103C2000109516B917B985E18A95F1F7000095E06E +:103C30009A95F1F7000017B816B978948091B20000 +:103C400087FFBDC0C091350180E28C0F8D3708F42D +:103C5000B3C08091310190913201A0913301B091B4 +:103C600034010196A11DB11D809331019093320161 +:103C7000A0933301B0933401409136015091370144 +:103C80006091380170913901481759076A077B071D +:103C900008F46CDF80913F0169B1562F5095551F94 +:103CA0005527551F90913E014091420120914301BB +:103CB00030914401591349C04111415067FD2FC053 +:103CC0002115310529F02150310911F410924501D7 +:103CD00060914701709148016F5F7F4F7093480179 +:103CE00060934701613797E1790730F0806810925F +:103CF00048011092470149C0693B9BE0790710F0E9 +:103D0000806443C0613B744008F43FC09091460119 +:103D100091113BC0826091E09093460136C021151D +:103D2000310599F12150310981F59091450191308A +:103D300011F4886007C0923011F4806103C0806282 +:103D4000909340011092450120C041111DC09091F7 +:103D5000450167FD0BC081609F3F09F09F5F109296 +:103D60004801109247012AE431E007C08460211520 +:103D7000310509F490E0109246019093450150936B +:103D80003E0144E191E002C044E190E080933F01B4 +:103D90004093420130934401209343019111D5DEB9 +:103DA00080912801811106C081E080932801BEDE48 +:103DB0001092280183EA8C0F8093350173C08091A3 +:103DC000260190912501C92FD0E0823009F441C02D +:103DD00028F4882351F08130C9F062C0833009F49F +:103DE00045C0843009F451C05BC04CDDCC0FDD1FF1 +:103DF000CC0FDD1FC050DF4F8F858F3F29F083E24E +:103E00008093B000539A6B9A81E04AC06B989230CD +:103E100081F030F4992359F0913081F4709A0EC0FA +:103E2000943059F040F0953049F45A9A07C02E9AD0 +:103E300005C02F9A03C0719A01C05C9ACC0FDD1F98 +:103E4000CC0FDD1FC050DF4F8F858093B40082E020 +:103E500027C08FEF8093B400CC0FDD1FCC0FDD1F88 +:103E6000C050DF4F8D8587BD83E01AC05398809383 +:103E7000B0008FEF87BDCC0FDD1FCC0FDD1FC05012 +:103E8000DF4F8E8588BD84E00BC08FEF88BD9F5FBC +:103E9000963019F410920C0190E09093250180E087 +:103EA00080932601C7E4D2E000E2F12CEE24E394F3 +:103EB0009CA5802F812309F43EC08DA59430D0F4B9 +:103EC0008823B1F1923010F490E801C090E0282FDF +:103ED0002695922B80FF2BC08A81883248F48A81F4 +:103EE000FE01E80FF11D93838A818F5F8A8338C0BA +:103EF0001A8214C09730E0F4803891F49A819923A3 +:103F0000B9F0E8828B818B3049F48A81843031F4B6 +:103F1000CE01AADC882311F0FF24F39480E008C0CE +:103F20009881911105C099819923C9F001C0892F09 +:103F30008DA71CA606C0983018F49F5F9CA701C0EF +:103F40001DA6E297069581E0CB31D80709F0B0CFE6 +:103F5000FF2041F080917902811104C01ADC1A829D +:103F600080E8E6CFFF91EF91DF91CF91BF91AF91C4 +:103F70009F918F917F916F915F914F913F912F9181 +:103F80001F910F91FF90EF900F900FBE0F901F9019 +:023F9000189582 +:0400000300003800C1 +:00000001FF diff --git a/cores/blinklib/shared/blinkbios_shared_button.h b/cores/blinklib/shared/blinkbios_shared_button.h new file mode 100644 index 00000000..b73d6987 --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_button.h @@ -0,0 +1,69 @@ +/* + * blinkbios_pixel_block.h + * + * Defines the shared memory block used to check for button presses + * + */ + +#ifndef BLINKBIOS_BUTTON_BLOCK_H_ +#define BLINKBIOS_BUTTON_BLOCK_H_ + +// #define USER_VOLATILE or BIOS_VOLATILE based on the presence of #define BIOS_VOLATILE_FLAG +#include "blinkbios_shared_volatile.h" + +#include + +// I know this is ugly, but keeping them in a single byte lets us pass them by value +// and also lets us OR them together. Efficiency in the updater trumps since it +// runs every millisecond. + +// TODO: Maybe more RAM but less flash to have these as individual bytes? + +#define BUTTON_BITFLAG_PRESSED 0b00000001 +#define BUTTON_BITFLAG_LONGPRESSED 0b00000010 +#define BUTTON_BITFLAG_RELEASED 0b00000100 +#define BUTTON_BITFLAG_SINGLECLICKED 0b00001000 +#define BUTTON_BITFLAG_DOUBLECLICKED 0b00010000 +#define BUTTON_BITFLAG_MULITCLICKED 0b00100000 + +#define BUTTON_BITFLAG_3SECPRESSED 0b01000000 +#define BUTTON_BITFLAG_6SECPRESSED 0b10000000 + +struct blinkbios_button_block_t { + + USER_VOLATILE uint8_t down; // 1 if button is currently down (debounced) + + BOTH_VOLATILE uint8_t bitflags; + + USER_VOLATILE uint8_t clickcount; // Number of clicks on most recent multiclick + + BOTH_VOLATILE uint8_t wokeFlag; // Set to 0 upon initial start up or waking from sleep + + // The variables below are used to track the intermediate button state + // and probably not interesting to user programs + + uint8_t buttonDebounceCountdown; // How long until we are done bouncing. Only touched in the callback + // Set to BUTTON_DEBOUNCE_MS every time we see a change, then we ignore everything + // until it gets to 0 again + + uint16_t clickWindowCountdown; // How long until we close the current click window. 0=done + + uint8_t clickPendingcount; // How many clicks so far int he current click window + + uint8_t longpressRegisteredFlag; // Did we register a long press since the button went down? + // Avoids multiple long press events from the same down event + // We could keep an independent longpressCountup, but that would be a 16 bit so heavy to increment and compare. + + uint16_t pressCountup; // Start counting up when the button goes down to detect long presses. Has to be 16 bit because long presses are >255 centiseconds. + + // For future use? + uint8_t slack[4]; + + +}; + +// Everything in here is volatile to the user code + +extern blinkbios_button_block_t blinkbios_button_block; + +#endif /* BLINKBIOS_BUTTON_BLOCK_H_ */ \ No newline at end of file diff --git a/cores/blinklib/shared/blinkbios_shared_functions.h b/cores/blinklib/shared/blinkbios_shared_functions.h new file mode 100644 index 00000000..208a9c71 --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_functions.h @@ -0,0 +1,97 @@ +/* + * blinkbios_shared_functions.h + * + * Defines the entry points into the BLINKBIOS called functions + * + */ + +// Note: Aesthetically it might seem better to put these function declarations with their functional groups, but ultimately I decided to +// keep them all here in one place to avoid vector collisions. It is not a decision without downside though since a client who, say, +// only wants the IR send funtion gets polluted with the others as well. :/ + +// The VECTOR definitions are used on the BIOS side to hook the correct incoming vector. +// The function definitions are used on the caller side to make the actual call to the vector. +// The vector number and the jmp address must be manually matched. + +// The actual vector number is not important as long as it is not used for an actual ISR. We really just use the vector pattern so the +// caller has a well known address to jmp to and vectors are always at a well known address. + +// The `jmp`s just work because the caller will set up the args in the right registers and then make the jmp to the +// interrupt vector. Once there, the vector will directly call the target function with all the args still in place, +// then the target will return back to the original caller. Cool right? The optimizer will even put the target function at +// the vector jump address if it has no other callers, saving a jmp. + +#ifndef BLINKBIOS_SHARED_FUCNTIONS_H_ +#define BLINKBIOS_SHARED_FUCNTIONS_H_ + +// Send a user data packet +// See what we did here - we do a naked jump into vector_4, which is a jump to the `uint8_t ir_send_userdata( uint8_t face, const uint8_t *data , uint8_t len )` function +// it all works out because the params happened to be in the same registers because of the AVR C calling convention. +// When compiling the BIOS with LTO, it even puts the send packet function right at the target of the vector. + +// We use unused inetrrupt vectors to link between the user and BIOS code since they are at a known place. +// The links are defined as symbols like `boot_vectorX` where X is the number of the unused vector we are taking. +// In the BIOS project, these appear in the vector table in `startup.S`. +// IN the user mode projects, these appear in the linkscript and are hard coded to the correct addressed (based at the bootloader vtable at 0x3800). + +// boot_vector4 is defined in the linkerscript and points to the boot loader's interrupt vector 4 at address 0x3810 +// This is just a prototype so gcc knows what args to pass. The linker will resolve it to a jump to the address. + +#define BLINKBIOS_IRDATA_SEND_PACKET_VECTOR boot_vector4 // This lands at base + 4 bytes per vector * 4th vector (init is at 0) = 0x10 + +extern "C" uint8_t BLINKBIOS_IRDATA_SEND_PACKET_VECTOR( uint8_t face, const uint8_t *data , uint8_t len ) __attribute__((used)) __attribute__((noinline)); + +// Translate and copy the RGB values in the pixel buffer to raw PWM values in the +// raw pixel buffer. Waits for next vertical blanking interval to avoid +// display of partial update. + +#define BLINKBIOS_DISPLAY_PIXEL_BUFFER_VECTOR boot_vector8 + +extern "C" void BLINKBIOS_DISPLAY_PIXEL_BUFFER_VECTOR() __attribute__((used)) __attribute__((noinline)); + + + +#define BLINKBIOS_BOOTLOADER_SEED_VECTOR boot_vector9 + +extern "C" void BLINKBIOS_BOOTLOADER_SEED_VECTOR() __attribute__((used)) __attribute__((noinline)); + + +// Push back the inactivity sleep timer +// Can be called with interrupts off, so you can adjust the +// blinkbios_millis_block.millis and then call BLINKBIOS_POSTPONE_SLEEP_VECTOR +// to reset the sleep_time to match the new timebase +// Leaves with interrupts ON + +#define BLINKBIOS_POSTPONE_SLEEP_VECTOR boot_vector10 + +extern "C" void BLINKBIOS_POSTPONE_SLEEP_VECTOR() __attribute__((used)) __attribute__((noinline)); + + +#define BLINKBIOS_SLEEP_NOW_VECTOR boot_vector12 + +extern "C" void BLINKBIOS_SLEEP_NOW_VECTOR() __attribute__((used)) __attribute__((noinline)); + +// Fill the flash page buffer using the boot_page_fill() function, +// then call here. Will write the page to flash, wait for it to complete, +// re-enable the rww part of flash, then return - so you can call this from +// the non-bootloader part of memory + +#define BLINKBIOS_WRITE_FLASH_PAGE_VECTOR boot_vector13 + +extern "C" void BLINKBIOS_WRITE_FLASH_PAGE_VECTOR(uint8_t page) __attribute__((used)) __attribute__((noinline)); + + +// Returns the version of the blinksbios present + +#define BLINKBIOS_VERSION_VECTOR boot_vector14 + +extern "C" uint8_t BLINKBIOS_VERSION_VECTOR() __attribute__((used)) __attribute__((noinline)); + + +// Error mode. Blinks red until button pressed, then soft reset + +#define BLINKBIOS_ABEND_VECTOR boot_vector15 + +extern "C" void BLINKBIOS_ABEND_VECTOR( uint8_t blinkCount ) __attribute__((used)) __attribute__((noinline)) __attribute__((noreturn)); + +#endif /* BLINKBIOS_SHARED_FUCNTIONS_H_ */ \ No newline at end of file diff --git a/cores/blinklib/shared/blinkbios_shared_irdata.h b/cores/blinklib/shared/blinkbios_shared_irdata.h new file mode 100644 index 00000000..ff1181f8 --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_irdata.h @@ -0,0 +1,95 @@ +/* + * blinkbios_pixel_block.h + * + * Defines the shared memory block used to check for button presses + * + */ + +#ifndef BLINKBIOS_IRDATA_BLOCK_H_ +#define BLINKBIOS_IRDATA_BLOCK_H_ + +// #define USER_VOLATILE or BIOS_VOLATILE based on the presence of #define BIOS_VOLATILE_FLAG +#include "blinkbios_shared_volatile.h" + +#include + +#define IR_FACE_COUNT 6 +#define IR_FACE_BITMASK 0b00111111 // Mask for the 6 faces. Fails hard when we start making nonagon tiles. + +// Must be big enough to hold the biggest packet, which internally is PUSH packet. +#define IR_RX_PACKET_SIZE 40 + +// State for each receiving IR LED + + +// User programs should only pay attention to messages with the header byte +// they should silently consume any messages with other header bytes. + +#define IR_USER_DATA_HEADER_BYTE 0b00110111 + +struct ir_rx_state_t { + + BOTH_VOLATILE uint8_t packetBufferReady; // 1 if we got the trailing sync byte. Foreground reader will set this back to 0 to enable next read. + + BOTH_VOLATILE uint8_t txInProgressFlag; // Are we currently transmitting? If so, then ignore any incoming SYNC. + + USER_VOLATILE uint8_t packetBufferLen; // How many bytes currently in the packet buffer? Does not include checksum when bufferReady is set + + + USER_VOLATILE uint8_t packetBuffer[ IR_RX_PACKET_SIZE+1 ]; // Assemble incoming packet here. +1 to hold the type byte. Type byte comes first, but shoul;d be ignored since the BIOS consumes anything with a type besides USERDATA + // First byte is header, which user code must check for USERDATA + + // These internal variables not interesting to user code. + // They are only updated in ISR, so don't need to be volatile. + + uint8_t windowsSinceLastTrigger; // How long since we last saw a trigger on this IR LED? + + // We add new samples to the bottom and shift up. + // The we look at the pattern to detect data bits + // This is better than using a counter because with a counter we would need + // to check for overflow before incrementing. With a shift register like this, + // 1's just fall off the top automatically and We can keep shifting 0's forever. + + uint8_t byteBuffer; // Buffer for RX byte in progress. Data bits shift up until full byte received. + // We prime with a '1' in the bottom bit when we get a valid start. + // This way we can test for 0 to see if currently receiving a byte, and + // we can also test for '1' in top position to see if full byte received. + + + // This struct should be even power of 2 in length for more efficient array indexing. + + // For future use? + uint8_t slack[4]; + + +}; + +struct blinkbios_irdata_block_t { + + ir_rx_state_t ir_rx_states[ IR_FACE_COUNT ]; + + uint8_t download_in_progress_flag; // Set if a download is in progress so subsequent SEED packets will be ignored + + // For future use? + uint8_t slack[4]; + + +}; + +extern blinkbios_irdata_block_t blinkbios_irdata_block; + +// You can use this function to see if there is an RX in progress on a face +// Don't transmit if there is an RX in progress or you'll make a collision + +inline uint8_t blinkbios_is_rx_in_progress( uint8_t face ) { + + // This uses the fact that anytime we are actively receiving a byte, the byte buffer will + // be nonzero. Even if we are getting an all-0 byte, the top bit will be marching up. + // This is handy, but it does allow a collision during the leading sync. See below. + + return blinkbios_irdata_block.ir_rx_states[face].byteBuffer; + +} + + +#endif /* BLINKBIOS_IRDATA_BLOCK_H_ */ \ No newline at end of file diff --git a/cores/blinklib/shared/blinkbios_shared_millis.h b/cores/blinklib/shared/blinkbios_shared_millis.h new file mode 100644 index 00000000..604d2862 --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_millis.h @@ -0,0 +1,46 @@ +/* + * blinkbios_millis_block.h + * + * Defines the shared memory block used to keep the millisecond counter + * + */ + +#ifndef BLINKBIOS_MILLIS_BLOCK_H_ +#define BLINKBIOS_MILLIS_BLOCK_H_ + +// C++ implementations should define these macros only when __STDC_LIMIT_MACROS is defined before is included + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +//C++ implementations should define these macros only when __STDC_LIMIT_MACROS is defined before is included. +// Gets us the UINT*_MAX macros +#define __STDC_LIMIT_MACROS + +#include + +// #define USER_VOLATILE or BIOS_VOLATILE based on the presence of #define BIOS_VOLATILE_FLAG +#include "blinkbios_shared_volatile.h" + +typedef uint32_t millis_t; + +#define MILLIS_MAX ( UINT32_MAX ) // This is a hack because it seems our compiled does not have UINT32_MAX correctly defined. :/ + +struct blinkbios_millis_block_t { + + USER_VOLATILE millis_t millis; + USER_VOLATILE uint8_t step_8us; // Carry over of sub-millis since our clock does not go evenly into 1ms. This is the number of 8us steps past the value in millis. Never gets above 125. + + USER_VOLATILE millis_t sleep_time; // When millis>sleep_time, then we go to sleep + + // For future use? + uint8_t slack[4]; +}; + +extern blinkbios_millis_block_t blinkbios_millis_block; + + +#define MILLIS_NEVER ( MILLIS_MAX ) // A time that will forever be in the future + + +#endif /* BLINKBIOS_MILLIS_BLOCK_H_ */ \ No newline at end of file diff --git a/cores/blinklib/shared/blinkbios_shared_pixel.h b/cores/blinklib/shared/blinkbios_shared_pixel.h new file mode 100644 index 00000000..5775896d --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_pixel.h @@ -0,0 +1,175 @@ +/* + * blinkbios_pixel_block.h + * + * Defines the shared memory block used to set the color of the pixels + * and to determine best time to update the values + * + */ + +#ifndef BLINKBIOS_PIXEL_BLOCK_H_ +#define BLINKBIOS_PIXEL_BLOCK_H_ + +// #define USER_VOLATILE or BIOS_VOLATILE based on the presence of #define BIOS_VOLATILE_FLAG +#include "blinkbios_shared_volatile.h" + +/** Display interface ***/ + +#define PIXEL_COUNT 6 + +// Here are the raw compare register values for each pixel +// These are precomputed from brightness values because we read them often from inside an ISR +// Note that for red & green, 255 corresponds to OFF and 0 is full on + +struct rawpixel_t { + uint8_t rawValueR; + uint8_t rawValueG; + uint8_t rawValueB; + uint8_t paddng; // Adding this padding save about 10 code bytes since x4 (double-doubling) is faster than adding 3. + + rawpixel_t( uint8_t in_raw_r , uint8_t in_raw_g , uint8_t in_raw_b ) { + rawValueR = in_raw_r; + rawValueG = in_raw_g; + rawValueB = in_raw_b; + + } + + rawpixel_t() {}; + +}; + + +const rawpixel_t RAW_PIXEL_OFF( 0xff , 0xff, 0xff ); + +// pixel_color_t is a higher level view RGB encoded of a pixel + +// Each pixel has 32 brightness levels for each of the three colors (red,green,blue) +// These brightness levels are normalized to be visually linear with 0=off and 31=max brightness + +// Note use of anonymous union members to let us switch between bitfield and int +// https://stackoverflow.com/questions/2468708/converting-bit-field-to-int + +union pixelColor_t { + + struct { + uint8_t reserved:1; + uint8_t r:5; + uint8_t g:5; + uint8_t b:5; + }; + + uint16_t as_uint16; + + pixelColor_t(); + pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in ); + pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in , uint8_t reserverd_in ); + +}; + +inline pixelColor_t::pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in ) { + + r=r_in; + g=g_in; + b=b_in; + +} + +inline pixelColor_t::pixelColor_t(uint8_t r_in , uint8_t g_in, uint8_t b_in , uint8_t reserverd_in ) { + + r=r_in; + g=g_in; + b=b_in; + reserved = reserverd_in; + +} + +/* + +template pixelColor_t preset_pixelcolor_t { +{ + pixelColor_t p = pixelColor_t( r , g , b ); + + +}; + +*/ + +// Maximum value you can assign to one of the primaries in a pixelColor_t +#define PIXELCOLOR_PRIMARY_FULL 31 +#define PIXELCOLOR_PRIMARY_HALF 15 + +// So this mess below is my way of emulating constexpr in C++ <11 +// These defines will expand and eval at compile time down to a single uint16_t load +// if instead we tried using a `const pixelColor_r`, then it would blow up into +// a constructor call at runtime. Yuck. + +#define PIXEL_COLOR_FULL_GREEN pixelColor_t( 0 , PIXELCOLOR_PRIMARY_FULL , 0 ) +#define PIXEL_COLOR_HALF_GREEN pixelColor_t( 0 , PIXELCOLOR_PRIMARY_HALF , 0 ) + +#define PIXEL_COLOR_FULL_RED pixelColor_t( PIXELCOLOR_PRIMARY_FULL , 0 , 0 ) +#define PIXEL_COLOR_HALF_RED pixelColor_t( PIXELCOLOR_PRIMARY_HALF , 0 , 0 ) + +#define PIXEL_COLOR_FULL_BLUE pixelColor_t( 0 , 0 , PIXELCOLOR_PRIMARY_FULL ) + +#define PIXEL_COLOR_OFF pixelColor_t() + +inline pixelColor_t::pixelColor_t() { + + // Faster than setting the individual elements? + // We don't need to do this because in bss this will get cleared to 0 anyway. + // as_uint16 = 0; + +} + +// We need these struct gymnastics because C fixed array typedefs do not work +// as you (I?) think they would... +// https://stackoverflow.com/questions/4523497/typedef-fixed-length-array + +struct blinkbios_pixelblock_t { + + // RGB pixel buffer + // Call displayPixelBuffer() to decode and copy this into the raw pixel buffer + // so it will appear on the LEDs + + BIOS_VOLATILE pixelColor_t pixelBuffer[PIXEL_COUNT]; + + // This is cleared when we are done displaying the current buffer values and about to reset and start again + // Once this is cleared, you have one pixel interrupt period to get your new data into the pixel buffer + // before the next refresh cycle starts. + + // We picked to clear this rather than set to 1 because in the ISR is is faster to set to 0 since R1 is always loaded with 0 + + BOTH_VOLATILE uint8_t vertical_blanking_interval; + + // Below is some global state that is probably not interesting to user code + + rawpixel_t rawpixels[PIXEL_COUNT]; + + uint8_t currentPixelIndex; // Which pixel are we currently lighting? Pixels are multiplexed and only refreshed one at a time in sequence. + uint8_t phase; // Phase up updating the current pixel. There are 5 phases that include lighting each color, charging the charge pump, and resting the charge pump. + + + // Here we keep the value of TCNT0 that was captured at the last watchdog timer interrupt + // We put this here because the pixel code enables and sets up Timer0, and really not other good place for it. + // User code is responsible for enabling the WDT interrupt and then checking this value to see + // when it gets set asynchronously by the bios WDT ISR. + + volatile uint8_t capturedEntropy; + + // Setting this to 1 will skip the low battery check and let the blink run down until the chip + // dies. This will happen about 2.4V at the standard 8Mhz speed, but you can switch the prescaller to 4Mhz + // and run all the way down to 1.8V, but know that IR timing (inter alia) will be hosed. + + // (We really only have this so that the low battery display can re-use the pixel code and not be + // re-entrant since otherwise the pixel ISR code ends up calling the low battery check code) + + volatile uint8_t skip_low_voltage_check; + + // For future use? + uint8_t slack[8]; + +}; + +extern blinkbios_pixelblock_t blinkbios_pixel_block; // Currently being displayed. You can have direct access to this to save memory, + // but use the vertical retrace to avoid visual tearing on updates + +#endif /* BLINKBIOS_PIXEL_BLOCK_H_ */ \ No newline at end of file diff --git a/cores/blinklib/shared/blinkbios_shared_volatile.h b/cores/blinklib/shared/blinkbios_shared_volatile.h new file mode 100644 index 00000000..187b1150 --- /dev/null +++ b/cores/blinklib/shared/blinkbios_shared_volatile.h @@ -0,0 +1,39 @@ +/* + * blinkbios_shared_voltaile.h + * + * This is a workaround to letting the BIOS code and user code have different `volatile` views of the + * shared data structures. While we need some of these shared variables to be seen as volatile to the user code + * since they can be changed by the BIOS code in the background, the BIOS code can never be interrupted by the user code + * so it is wasteful to have it see them as volatile also. All the unneeded reloads are not only slow, but we really need + * to save flash. + * + * To make this work, the BIOS code always `#define`s `BIOS_VOLATILE_FLAG` before including any of the shared files. + * + */ + + +#ifndef BLINKBIOS_SHAED_VOLATILE__H_ + + #define BLINKBIOS_SHAED_VOLATILE__H_ + + #ifdef BIOS_VOLATILE_FLAG + + // BIOS code sees this + + #define BIOS_VOLATILE volatile + #define USER_VOLATILE + #define BOTH_VOLATILE volatile + + #else + + // User code sees this + + #define BIOS_VOLATILE + #define USER_VOLATILE volatile + #define BOTH_VOLATILE volatile + + #endif + + + +#endif diff --git a/cores/blinklib/shared/readme.MD b/cores/blinklib/shared/readme.MD new file mode 100644 index 00000000..57b28276 --- /dev/null +++ b/cores/blinklib/shared/readme.MD @@ -0,0 +1,14 @@ +Do not manually change these files! + +They come from the `blinkbios` project... + +https://github.com/Move38/Move38-BlinkBIOS + +...and must travel as a coherent set. + +`BlinkBIOS.hex` contains the compiled image of the BlinkBIOS code. It is loaded in upper flash and takes care of lots of common hardware tasks so that individual games do not need duplicated code. + +We program this file every time download a sketch. We also set the `BOOTRST` fuse so that the chip will jump to the bootload section rather than address 0x000 on RESET. Both of these happen in the `AVRDUDE` the recipes in `platform.txt`. + +To be able to successfully talk to the BIOS code, the blink user code must be compiled against the matching header files. + \ No newline at end of file diff --git a/cores/blinklib/sp.cpp b/cores/blinklib/sp.cpp new file mode 100644 index 00000000..30aa1593 --- /dev/null +++ b/cores/blinklib/sp.cpp @@ -0,0 +1,85 @@ +/* + * Access the service port + * + * The service port has a high speed (1Mbps) bi-directional serial connection plus an Aux pin that can be used + * as either digitalIO or an analog in. + * + * Mostly useful for debugging, but maybe other stuff to? :) + * + */ + + +#include "sp.h" + +#include + +// Serial port hardware on service port + +#define SP_SERIAL_CTRL_REG UCSR0A +#define SP_SERIAL_DATA_REG UDR0 +#define SP_SERIAL_READY_BIT RXC0 + +// Bit manipulation macros +#define SBI(x,b) (x|= (1<linker + + + + * Created: 10/27/2018 4:05:41 PM + * Author: josh + */ + + +/* + + There is nothing here! NO VECTORS here! No __zero_reg! We just jump stright into the code! + + This saves a lot of flash becuase each game can start litterally at address 0x000 and hit the + ground running with no extra crap at the begining. + + The vectors are up in the bootloader area. Code compiled against this blinklib API + is depenent on that BlinkBIOS code running at reset and setting everythign up for us + inclduing the interrupts. + + Note that we do need to init the .data and .bss variables to thier initial values, but that + is taken care of by library code that will link in here automatically. + +*/ + + // There does not seem to be any good way to avoid this jmp since + // the compiler likes to put the contructors and jump trampolines at the bottom of flash, + // which ended up being 0x0000 when we get rid of all the vectors! + + + .section .vectors,"ax",@progbits + .global __vectors + .func __vectors +__vectors: + jmp __init + .endfunc + + + .section .init0,"ax",@progbits + .func __init +__init: + + .section .init2,"ax",@progbits + + .section .init9,"ax",@progbits + + // Note that we do not need to clear `r1` or set up the stack here + // becuase the BlinkBIOS has already done that by the time we get started. + + jmp mainx + + .endfunc diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 603d70ef..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1,7 +0,0 @@ -theme: jekyll-theme-minimal - -title: Blinks Developers -description: Resources for Blinks developers -logo: /assets/blinkslogo.png -show_downloads: true - diff --git a/docs/_includes/googleDrivePlayer.html b/docs/_includes/googleDrivePlayer.html deleted file mode 100755 index 2824de6f..00000000 --- a/docs/_includes/googleDrivePlayer.html +++ /dev/null @@ -1,11 +0,0 @@ - - -
- -
diff --git a/docs/_includes/twitchPlayer.html b/docs/_includes/twitchPlayer.html deleted file mode 100755 index 270a955e..00000000 --- a/docs/_includes/twitchPlayer.html +++ /dev/null @@ -1,14 +0,0 @@ - - -
- -
\ No newline at end of file diff --git a/docs/_includes/vimeoPlayer.html b/docs/_includes/vimeoPlayer.html deleted file mode 100755 index 7724a95d..00000000 --- a/docs/_includes/vimeoPlayer.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
- -
\ No newline at end of file diff --git a/docs/_includes/youtubePlayer.html b/docs/_includes/youtubePlayer.html deleted file mode 100755 index 0f27f05c..00000000 --- a/docs/_includes/youtubePlayer.html +++ /dev/null @@ -1,11 +0,0 @@ - - -
- -
\ No newline at end of file diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index 0dce6c91..00000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - -{% seo %} - - - - -
-
-

{{ site.title | default: site.github.repository_name }}

- - {% if site.logo %} - Logo - {% endif %} - -

{{ site.description | default: site.github.project_tagline }}

- - - - {% if site.github.is_user_page %} -

View My GitHub Profile

- {% endif %} - - {% if site.show_downloads %} - - - {% endif %} - -

Quickstart Guide

-

Functionality Guide

-

API Reference

-

Glossary

-

Games Primer

- -
- -
- - {{ content }} - -
- -
- - {% if site.google_analytics %} - - {% endif %} - - \ No newline at end of file diff --git a/docs/android-chrome-192x192.png b/docs/android-chrome-192x192.png deleted file mode 100644 index c9edf006..00000000 Binary files a/docs/android-chrome-192x192.png and /dev/null differ diff --git a/docs/android-chrome-256x256.png b/docs/android-chrome-256x256.png deleted file mode 100644 index fe978f89..00000000 Binary files a/docs/android-chrome-256x256.png and /dev/null differ diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 4b2f010c..00000000 --- a/docs/api.md +++ /dev/null @@ -1,188 +0,0 @@ -# Blinks API Reference -[Glossary](glossary.md) - glanceable reference - -## Display -``` -void setColor(color); -// sets all 6 pixels on all faces to the same color - -void setFaceColor(face, color); -// sets a single pixel on a face to this color -``` - -## Colors -``` -Color makeColorRGB(red, green, blue); -// R, G, and B values [0-255] - -Color makeColorHSB(hue, saturation, brightness); -// H, S, and V values [0-255] - -Color dim(color, value); -// returns the color passed in a dimmer state -//(0-255 bucketed into 32 levels of brightness) - -#define RED makeColorRGB(255, 0, 0) -#define ORANGE makeColorRGB(255, 127, 0) -#define YELLOW makeColorRGB(255, 255, 0) -#define GREEN makeColorRGB( 0, 255, 0) -#define CYAN makeColorRGB( 0, 255, 255) -#define BLUE makeColorRGB( 0, , 255) -#define MAGENTA makeColorRGB(255, 0, 255) -#define WHITE makeColorRGB(255, 255, 255) -#define OFF makeColorRGB( 0, 0, 0) -``` - -## Button -``` -/* - * All button handling is done with flags, so when - * you call a function, it returns the value of - * the flag (i.e. whether or not that action - * has occured) and only when you have - * called the function will it reset - * the flag to false - */ - -bool buttonPressed(); -// a flag is set to true on the change from button up to button down -// buttonPressed() returns that flag and sets it back to false - -bool buttonReleased(); -// a flag is set to true on the change from button -// down to button up buttonReleased() returns that -// flag and sets it back to false - -bool buttonSingleClicked(); -// a flag is set to true when the the -// button goes from down to up -// only once in under 330ms. The flag is set exactly -// 330ms after the button is up -// buttonSingleClicked() returns that flag and sets it back to false - -bool buttonDoubleClicked(); -// returns true when a double click has been recorded -// (down-up-down-up under 330ms*) -// ^ also a flag with the same properties as above - -bool buttonMultiClicked(); -// returns true when multiple click has been recorded -// (down-up...down-up under 330ms*) -// ^ also a flag with the same properties as above - -byte buttonClickCount(); -// returns the number of clicks recorded during button multi clicking - -bool buttonLongPressed(); -// returns true when the button has been down for more than 3000ms (3 seconds) -// ^ also a flag with the same properties as above - -bool buttonDown(); -// returns true when the button is down -// ^ also a flag with the same properties as above -``` - -## Communication -``` -void setValueSentOnAllFaces(value); -// sets a value (0-63) to be sent -// repeatedly (every loop() cycle) on all 6 faces - -void setValueSentOnFace(value, face); -// sets a value (0-63) to be sent repeatedly (every loop() cycle) -// on the face specified - -byte getLastValueReceivedOnFace(face); -// retreives the last value seen on the -// specified face even if expired -// (i.e. last message from neighbor) -// defaults to 0 on startup - -bool isValueReceivedOnFaceExpired(face); -// returns true if the message from a -// neighbor has expired -// (i.e. it's been longer than 200ms since we last -// received a message from this neighbor) - -bool didValueOnFaceChange(face); -// returns true if the value on this face is different -// from the last stored seen value on this face - -bool isAlone(); -// returns true if all faces have expired values -// (i.e. it is safe to assume this Blink is now alone) -``` - -## Time -``` -unsigned long millis(); -// monotonically incrementing timer -// remains constant in a single pass of loop() -// i.e. millis() will return the same value at the top -// of loop() and the bottom does not -// increment while asleep -// resets after ~50 days - - -void Timer.set(duration); -// a timer that will expire after duration milliseconds -// used like this: -// Timer myTimer; -// myTimer.set(millisTilExpired); - -bool Timer.isExpired(); -// a timer remains expired once it become -// expired until set again or if -// a timer is set to NEVER, it will never expire -``` - -## Types -``` -byte -word -int -long -float -double -bool -Color -Timer -``` - -## Convenience -``` -FOREACH_FACE(f) { -// f increments from 0-5 (i.e. each face) -} - -COUNT_OF(array); -// pass this function an array and it will returns number of elements in the array -``` - -## Constants -``` -#define FACE_COUNT 6 -// 6 for the number of faces the current Blinks have -// (maybe a new tesselation for heptagons will be found and we'll come out with a new version, but until then, six.) - -#define MAX_BRIGHTNESS 255 -// 255 for the highest brightness level displayed -// Note: brightness actually only has 31 different values, -// however, we scale them to 0-255 to match a familia 8-bit color scheme - -#define NEVER ( (uint32_t)-1 ) -// If you would like the timer to effectively never expire, set it to this constant - -#define SERIAL_NUMBER_LEN 9 -// Length of the globally unique serial number (9 bytes long) -``` - -## Uniqueness -``` -uint16_t rand( uint16_t limit ); -// Return a random number between 0 and limit inclusive. - -byte getSerialNumberByte( byte n ); -// Read the unique serial number for this blink tile -// There are 9 bytes in all, so n can be 0-8 -``` diff --git a/docs/apple-touch-icon.png b/docs/apple-touch-icon.png deleted file mode 100644 index 19e8919c..00000000 Binary files a/docs/apple-touch-icon.png and /dev/null differ diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers.htm b/docs/assets/Blinks Functionality Guide _ Blinks Developers.htm deleted file mode 100644 index 9033a7e1..00000000 --- a/docs/assets/Blinks Functionality Guide _ Blinks Developers.htm +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - -Blinks Functionality Guide | Blinks Developers - - - - - - - - - - - - - - - -
-
-

Blinks Developers

- - - Logo - - -

Resources for Blinks developers

- - - - - - - - - - -

Quickstart Guide

-

Functionality Guide

-

API Reference

-

Glossary

-

Games Primer

- -
- -
- -

Blinks Functionality Guide

- -

Overview

- -

First of all, what are Blinks? Blinks is a digital/physical (“phygital”) tabletop game system that contains either six Blinks or twelve Blinks (the party pack). Each Blink is a “smart” hexagonal tile, designed to be held in the hand or placed next to other Blinks on a table. Magnets inside of each Blink allow the hexagonal tile to maintain an organized grid-like structure without needing a board. Each Blink has 3 key properties:

- -
    -
  1. Blinks are ticklish (respond to touch)
  2. -
  3. Blinks are social (communicate with their neighbors)
  4. -
  5. Blinks love to play games (each one is responsible for remembering a single game, and can teach the others… more on game transmission later)
  6. -
- -

Let’s start with an understanding of the ins and outs of Blinks. If you want to jump right into the API, it is rather self explanatory, head here now.

- -

6 RGB Leds illuminate Blinks.

- -

Blinks communicate to the players through RGB(red, green, blue… like a screen) illumination. You have full control over their hue, saturation, brightness, and can animate them in countless ways (i.e. pulsing, blinking, flashing…). A diffuser makes Blinks look like an even glowing surface; however, there are 6 LEDs or pixels, one for each face of the hexagon. This allows for more complex animation and directionality, such as spinning, flowing from side to side, or counting up or down to name a few.

- -

While illumination is how a single Blink communicates to the players, the arrangement of the Blinks also communicates something to the players. In one example, the arrangement of 10 Blinks into different forms creates different behaviors of the whole. For example, if the Blinks are representing trees in a forest fire, a tightly packed set of 10 will spread the fire quicker than a long single file line of Blinks(trees). The player can reason this by seeing the arrangement of the board, and conversely, affect it by rearranging the board. Which brings me to inputs.

- -

InputsLocated in the center of each Blink, a tactile button is an input you can feel..

- -

The simplest of inputs is a press. Each Blink responds to touch by pressing on a tactile button in the middle of the Blink (Blinks are seamless, so the entire top depresses). The button can be used in a variety of ways, and can respond to a quick click, a long press, a double click…

- -

The button press can be an input to change the state of a Blink, or inform the Blink of a users action. For example, in the forest fire ruleset, a single press strikes lightning upon a Blink. If the Blink is a tree, it will then change state to be on fire, and if it is not a tree, it will simply go on being not a tree, as well as not on fire.

- -

The press can also be used to reveal information contained in a Blink. For example, a Blink representing a place in a game of minesweeper could reveal that it has a mine under it only when held with a long press. The button press in this case didn’t add information to the system but is simply used to reveal information.

- -

Communication each face communicates with neighboring Blinks, conveniently within magnetic range..

- -

The Blinks communicate with each of their six neighbors by receiving information ~20 times per second. The information passed is in the form of a single integer (0-63). If no Blink is present on a single side, the information expires. What does this mean in simple terms? This means that each Blink can tell each neighboring Blink that it is in one of roughly 64 states. In the forest fire example, there are 3 states: tree, fire, soil. But what about lightning? that isn’t a state, since it only affects the local Blink, so it is animated through the illumination, but simply changes the Blink’s state from tree to fire. -Blinks are setup to do this kind of communication often and reliably, but it is not the only way they can communicate. For longer forms of information, such as passing string-like information, there is a shared data buffer that could be utilized. This is experimental and not refined in the API, so I recommend first trying to rely on simple communication with the dozen or so states afforded above.

- -

Time

- -

Game time for the Blinks can be continuous, evaluating their state 20 times a second, or they can be discrete, evaluating their state only when a step is called. A step is a convenience function that allows all connected Blinks to understand that a step has happened. Think of it as a convenient way to let the entire connected board know that a single move has been made.

- -

In the forest fire example, time is discrete, which makes the spread of the fire controlled by the rate of the steps. One could see this as a game mechanic, where each turn is a step forward.

- -

In the game Fracture, a continuous game, Blinks are constantly evaluating their state so they can show players the most up to date representation of the current board state. This results in a board that is quickly responsive to arrangement, while discrete time makes a board that feels responsive to specific state changes or steps.

- -

Random vs. Deterministic

- -

Blinks can respond to their inputs and neighbors deterministically, where they will respond exactly the same way given the same inputs and neighbors every time. This is the case in the example of Fracture or Game of Life. Players can learn how Blinks will behave given a certain arrangement because of this deterministic rule-set.

- -

Games often use randomness for a bit of chance involved in each move. A dice roll, or a weighted random value can be very useful in creating suspense or capturing the real world uncertainty.

- -

Randomness can also be used with Blinks with a bit of care and caution. In the example of the forest fire, each Blink (plot of land) has an n% chance of growing a tree. That percent increases with the number of neighboring trees, to represent fertile ground, but it is random none-the-less. The caviat with randomness and Blinks is that players must feel like Blinks are giving signal, not simply noise. If a player can’t understand why a Blink is behaving in the way it is, it will be confusing for everyone. That said, it is not a technical limitation, if you want a Blink to be a random different color every time you press it, that is an easy program to write.

- -

Memory

- -

While Blinks can maintain a bit of a memory or history of what they have done, neighbors they’ve had, or how much time has passed, this is limited and it is important to remember that if this isn’t transparent to the player, it could be very confusing. I look forward to seeing elegant solutions in which a bit of stygmergy might just seep its way into a game and shed light on a beautiful invisible system that lies just beneath the surface. (more on program space below)

- -

Trickery

- -

As you might have noticed, you have complete control over the way a Blink displays itself to the player and the state a Blink is actually in. It might be useful to have many states an only 2 visibly different appearances to the players. Conversely, it might be useful to have only two states and show many different illuminations.

- -

The possibilities are many, feel free to look through the examples, they aim to walk you through the basics, and we welcome contributions for more.

- -

Please feel free to ask about features, and if I didn’t cover something that is in the API, I should, so point it out, it belongs here, too!

- -

API

-
    -
  • Check out the glossary here
  • -
  • Slightly more verbose version here
  • -
  • Full API here
  • -
- -

Nerdy Details

- -

Each Blink has 16KB of RAM. For not having a screen, this is more than sufficient. In fact, we recommend keeping games under 4KB, so that we can transmit them from Blink to Blink in an efficient manner. The full code-base is published on GitHub where master contains our latest robust release and dev contains in progress features (such as an animation library).

- - -
- -
- - - - \ No newline at end of file diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_click.png b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_click.png deleted file mode 100644 index a2205785..00000000 Binary files a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_click.png and /dev/null differ diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_comm.png b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_comm.png deleted file mode 100644 index 9a494ca7..00000000 Binary files a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_comm.png and /dev/null differ diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_light.png b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_light.png deleted file mode 100644 index 9e7e7bb9..00000000 Binary files a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blink_light.png and /dev/null differ diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blinkslogo.png b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blinkslogo.png deleted file mode 100644 index f7e7866d..00000000 Binary files a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/blinkslogo.png and /dev/null differ diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/scale.fix.js b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/scale.fix.js deleted file mode 100644 index 911d33c3..00000000 --- a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/scale.fix.js +++ /dev/null @@ -1,27 +0,0 @@ -(function(document) { - var metas = document.getElementsByTagName('meta'), - changeViewportContent = function(content) { - for (var i = 0; i < metas.length; i++) { - if (metas[i].name == "viewport") { - metas[i].content = content; - } - } - }, - initialize = function() { - changeViewportContent("width=device-width, minimum-scale=1.0, maximum-scale=1.0"); - }, - gestureStart = function() { - changeViewportContent("width=device-width, minimum-scale=0.25, maximum-scale=1.6"); - }, - gestureEnd = function() { - initialize(); - }; - - - if (navigator.userAgent.match(/iPhone/i)) { - initialize(); - - document.addEventListener("touchstart", gestureStart, false); - document.addEventListener("touchend", gestureEnd, false); - } -})(document); diff --git a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/style.css b/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/style.css deleted file mode 100644 index fda38d76..00000000 --- a/docs/assets/Blinks Functionality Guide _ Blinks Developers_files/style.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:'Noto Sans';font-weight:400;font-style:normal;src:url("../fonts/Noto-Sans-regular/Noto-Sans-regular.eot");src:url("../fonts/Noto-Sans-regular/Noto-Sans-regular.eot?#iefix") format("embedded-opentype"),local("Noto Sans"),local("Noto-Sans-regular"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.woff2") format("woff2"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.woff") format("woff"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.ttf") format("truetype"),url("../fonts/Noto-Sans-regular/Noto-Sans-regular.svg#NotoSans") format("svg")}@font-face{font-family:'Noto Sans';font-weight:700;font-style:normal;src:url("../fonts/Noto-Sans-700/Noto-Sans-700.eot");src:url("../fonts/Noto-Sans-700/Noto-Sans-700.eot?#iefix") format("embedded-opentype"),local("Noto Sans Bold"),local("Noto-Sans-700"),url("../fonts/Noto-Sans-700/Noto-Sans-700.woff2") format("woff2"),url("../fonts/Noto-Sans-700/Noto-Sans-700.woff") format("woff"),url("../fonts/Noto-Sans-700/Noto-Sans-700.ttf") format("truetype"),url("../fonts/Noto-Sans-700/Noto-Sans-700.svg#NotoSans") format("svg")}@font-face{font-family:'Noto Sans';font-weight:400;font-style:italic;src:url("../fonts/Noto-Sans-italic/Noto-Sans-italic.eot");src:url("../fonts/Noto-Sans-italic/Noto-Sans-italic.eot?#iefix") format("embedded-opentype"),local("Noto Sans Italic"),local("Noto-Sans-italic"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.woff2") format("woff2"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.woff") format("woff"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.ttf") format("truetype"),url("../fonts/Noto-Sans-italic/Noto-Sans-italic.svg#NotoSans") format("svg")}@font-face{font-family:'Noto Sans';font-weight:700;font-style:italic;src:url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot");src:url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot?#iefix") format("embedded-opentype"),local("Noto Sans Bold Italic"),local("Noto-Sans-700italic"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2") format("woff2"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff") format("woff"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf") format("truetype"),url("../fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg#NotoSans") format("svg")}.highlight table td{padding:5px}.highlight table pre{margin:0}.highlight .cm{color:#999988;font-style:italic}.highlight .cp{color:#999999;font-weight:bold}.highlight .c1{color:#999988;font-style:italic}.highlight .cs{color:#999999;font-weight:bold;font-style:italic}.highlight .c,.highlight .cd{color:#999988;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .gd{color:#000000;background-color:#ffdddd}.highlight .ge{color:#000000;font-style:italic}.highlight .gr{color:#aa0000}.highlight .gh{color:#999999}.highlight .gi{color:#000000;background-color:#ddffdd}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{font-weight:bold}.highlight .gu{color:#aaaaaa}.highlight .gt{color:#aa0000}.highlight .kc{color:#000000;font-weight:bold}.highlight .kd{color:#000000;font-weight:bold}.highlight .kn{color:#000000;font-weight:bold}.highlight .kp{color:#000000;font-weight:bold}.highlight .kr{color:#000000;font-weight:bold}.highlight .kt{color:#445588;font-weight:bold}.highlight .k,.highlight .kv{color:#000000;font-weight:bold}.highlight .mf{color:#009999}.highlight .mh{color:#009999}.highlight .il{color:#009999}.highlight .mi{color:#009999}.highlight .mo{color:#009999}.highlight .m,.highlight .mb,.highlight .mx{color:#009999}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#d14}.highlight .s2{color:#d14}.highlight .se{color:#d14}.highlight .sh{color:#d14}.highlight .si{color:#d14}.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .bp{color:#999999}.highlight .nb{color:#0086B3}.highlight .nc{color:#445588;font-weight:bold}.highlight .no{color:#008080}.highlight .nd{color:#3c5d5d;font-weight:bold}.highlight .ni{color:#800080}.highlight .ne{color:#990000;font-weight:bold}.highlight .nf{color:#990000;font-weight:bold}.highlight .nl{color:#990000;font-weight:bold}.highlight .nn{color:#555555}.highlight .nt{color:#000080}.highlight .vc{color:#008080}.highlight .vg{color:#008080}.highlight .vi{color:#008080}.highlight .nv{color:#008080}.highlight .ow{color:#000000;font-weight:bold}.highlight .o{color:#000000;font-weight:bold}.highlight .w{color:#bbbbbb}.highlight{background-color:#f8f8f8}body{background-color:#fff;padding:50px;font:14px/1.5 "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;color:#727272;font-weight:400}h1,h2,h3,h4,h5,h6{color:#222;margin:0 0 20px}p,ul,ol,table,pre,dl{margin:0 0 20px}h1,h2,h3{line-height:1.1}h1{font-size:28px}h2{color:#393939}h3,h4,h5,h6{color:#494949}a{color:#267CB9;text-decoration:none}a:hover,a:focus{color:#069;font-weight:bold}a small{font-size:11px;color:#777;margin-top:-0.3em;display:block}a:hover small{color:#777}.wrapper{width:860px;margin:0 auto}blockquote{border-left:1px solid #e5e5e5;margin:0;padding:0 0 0 20px;font-style:italic}code,pre{font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;color:#333}pre{padding:8px 15px;background:#f8f8f8;border-radius:5px;border:1px solid #e5e5e5;overflow-x:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:5px 10px;border-bottom:1px solid #e5e5e5}dt{color:#444;font-weight:700}th{color:#444}img{max-width:100%}header{width:270px;float:left;position:fixed;-webkit-font-smoothing:subpixel-antialiased}header ul{list-style:none;height:40px;padding:0;background:#f4f4f4;border-radius:5px;border:1px solid #e0e0e0;width:270px}header li{width:89px;float:left;border-right:1px solid #e0e0e0;height:40px}header li:first-child a{border-radius:5px 0 0 5px}header li:last-child a{border-radius:0 5px 5px 0}header ul a{line-height:1;font-size:11px;color:#676767;display:block;text-align:center;padding-top:6px;height:34px}header ul a:hover,header ul a:focus{color:#675C5C;font-weight:bold}header ul a:active{background-color:#f0f0f0}strong{color:#222;font-weight:700}header ul li+li+li{border-right:none;width:89px}header ul a strong{font-size:14px;display:block;color:#222}section{width:500px;float:right;padding-bottom:50px}small{font-size:11px}hr{border:0;background:#e5e5e5;height:1px;margin:0 0 20px}footer{width:270px;float:left;position:fixed;bottom:50px;-webkit-font-smoothing:subpixel-antialiased}@media print, screen and (max-width: 960px){div.wrapper{width:auto;margin:0}header,section,footer{float:none;position:static;width:auto}header{padding-right:320px}section{border:1px solid #e5e5e5;border-width:1px 0;padding:20px 0;margin:0 0 20px}header a small{display:inline}header ul{position:absolute;right:50px;top:52px}}@media print, screen and (max-width: 720px){body{word-wrap:break-word}header{padding:0}header ul,header p.view{position:static}pre,code{word-wrap:normal}}@media print, screen and (max-width: 480px){body{padding:15px}header ul{width:99%}header li,header ul li+li+li{width:33%}}@media print{body{padding:0.4in;font-size:12pt;color:#444}} diff --git a/docs/assets/apple-icon-76x76.png b/docs/assets/apple-icon-76x76.png deleted file mode 100644 index cdd7b04d..00000000 Binary files a/docs/assets/apple-icon-76x76.png and /dev/null differ diff --git a/docs/assets/blink_click.png b/docs/assets/blink_click.png deleted file mode 100644 index a2205785..00000000 Binary files a/docs/assets/blink_click.png and /dev/null differ diff --git a/docs/assets/blink_comm.png b/docs/assets/blink_comm.png deleted file mode 100644 index 9a494ca7..00000000 Binary files a/docs/assets/blink_comm.png and /dev/null differ diff --git a/docs/assets/blink_light.png b/docs/assets/blink_light.png deleted file mode 100644 index 9e7e7bb9..00000000 Binary files a/docs/assets/blink_light.png and /dev/null differ diff --git a/docs/assets/blinkslogo.png b/docs/assets/blinkslogo.png deleted file mode 100644 index f7e7866d..00000000 Binary files a/docs/assets/blinkslogo.png and /dev/null differ diff --git a/docs/assets/favicon-32x32.png b/docs/assets/favicon-32x32.png deleted file mode 100644 index afe2223b..00000000 Binary files a/docs/assets/favicon-32x32.png and /dev/null differ diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico deleted file mode 100644 index 7af09b24..00000000 Binary files a/docs/assets/favicon.ico and /dev/null differ diff --git a/docs/assets/ms-icon-310x310.png b/docs/assets/ms-icon-310x310.png deleted file mode 100644 index 83fec34f..00000000 Binary files a/docs/assets/ms-icon-310x310.png and /dev/null differ diff --git a/docs/assets/transfercode.jpeg b/docs/assets/transfercode.jpeg deleted file mode 100644 index f5fc5b99..00000000 Binary files a/docs/assets/transfercode.jpeg and /dev/null differ diff --git a/docs/browserconfig.xml b/docs/browserconfig.xml deleted file mode 100644 index b3930d0f..00000000 --- a/docs/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #da532c - - - diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 156ef95d..00000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/functionality.md b/docs/functionality.md deleted file mode 100644 index b091540f..00000000 --- a/docs/functionality.md +++ /dev/null @@ -1,81 +0,0 @@ -# Blinks Functionality Guide - -## Overview - -First of all, what are Blinks? Blinks is a digital/physical (“phygital”) tabletop game system that contains either six Blinks or twelve Blinks (the party pack). Each Blink is a "smart" hexagonal tile, designed to be held in the hand or placed next to other Blinks on a table. Magnets inside of each Blink allow the hexagonal tile to maintain an organized grid-like structure without needing a board. Each Blink has 3 key properties: - - -1. Blinks are ticklish (respond to touch) -2. Blinks are social (communicate with their neighbors) -3. Blinks love to play games (each one is responsible for remembering a single game, and can teach the others… more on game transmission later) - -Let’s start with an understanding of the ins and outs of Blinks. If you want to jump right into the API, it is rather self explanatory, head here now. - -## LEDs -![6 RGB Leds illuminate Blinks](assets/blink_light.png){:height="250px" width="250px"} - - -Blinks communicate to the players through RGB(red, green, blue… like a screen) illumination. You have full control over their hue, saturation, brightness, and can animate them in countless ways (i.e. pulsing, blinking, flashing...). A diffuser makes Blinks look like an even glowing surface; however, there are 6 LEDs or pixels, one for each face of the hexagon. This allows for more complex animation and directionality, such as spinning, flowing from side to side, or counting up or down to name a few. - -While illumination is how a single Blink communicates to the players, the arrangement of the Blinks also communicates something to the players. In one example, the arrangement of 10 Blinks into different forms creates different behaviors of the whole. For example, if the Blinks are representing trees in a forest fire, a tightly packed set of 10 will spread the fire quicker than a long single file line of Blinks(trees). The player can reason this by seeing the arrangement of the board, and conversely, affect it by rearranging the board. Which brings me to inputs. - - -## Inputs -![Located in the center of each Blink, a tactile button is an input you can feel.](assets/blink_click.png){:height="250px" width="250px"} - - -The simplest of inputs is a press. Each Blink responds to touch by pressing on a tactile button in the middle of the Blink (Blinks are seamless, so the entire top depresses). The button can be used in a variety of ways, and can respond to a quick click, a long press, a double click... - -The button press can be an input to change the state of a Blink, or inform the Blink of a users action. For example, in the forest fire ruleset, a single press strikes lightning upon a Blink. If the Blink is a tree, it will then change state to be on fire, and if it is not a tree, it will simply go on being not a tree, as well as not on fire. - -The press can also be used to reveal information contained in a Blink. For example, a Blink representing a place in a game of minesweeper could reveal that it has a mine under it only when held with a long press. The button press in this case didn't add information to the system but is simply used to reveal information. - - -## Communication -![each face communicates with neighboring Blinks, conveniently within magnetic range.](assets/blink_comm.png){:height="250px" width="250px"} - - -The Blinks communicate with each of their six neighbors by receiving information ~20 times per second. The information passed is in the form of a single integer (0-63). If no Blink is present on a single side, the information expires. What does this mean in simple terms? This means that each Blink can tell each neighboring Blink that it is in one of roughly 64 states. In the forest fire example, there are 3 states: tree, fire, soil. But what about lightning? that isn't a state, since it only affects the local Blink, so it is animated through the illumination, but simply changes the Blink’s state from tree to fire. -Blinks are setup to do this kind of communication often and reliably, but it is not the only way they can communicate. For longer forms of information, such as passing string-like information, there is a shared data buffer that could be utilized. This is experimental and not refined in the API, so I recommend first trying to rely on simple communication with the dozen or so states afforded above. - -## Time - -Game time for the Blinks can be continuous, evaluating their state 20 times a second, or they can be discrete, evaluating their state only when a step is called. A step is a convenience function that allows all connected Blinks to understand that a step has happened. Think of it as a convenient way to let the entire connected board know that a single move has been made. - -In the forest fire example, time is discrete, which makes the spread of the fire controlled by the rate of the steps. One could see this as a game mechanic, where each turn is a step forward. - -In the game Fracture, a continuous game, Blinks are constantly evaluating their state so they can show players the most up to date representation of the current board state. This results in a board that is quickly responsive to arrangement, while discrete time makes a board that feels responsive to specific state changes or steps. - - -## Random vs. Deterministic - -Blinks can respond to their inputs and neighbors deterministically, where they will respond exactly the same way given the same inputs and neighbors every time. This is the case in the example of Fracture or Game of Life. Players can learn how Blinks will behave given a certain arrangement because of this deterministic rule-set. - -Games often use randomness for a bit of chance involved in each move. A dice roll, or a weighted random value can be very useful in creating suspense or capturing the real world uncertainty. - -Randomness can also be used with Blinks with a bit of care and caution. In the example of the forest fire, each Blink (plot of land) has an n% chance of growing a tree. That percent increases with the number of neighboring trees, to represent fertile ground, but it is random none-the-less. The caviat with randomness and Blinks is that players must feel like Blinks are giving signal, not simply noise. If a player can't understand why a Blink is behaving in the way it is, it will be confusing for everyone. That said, it is not a technical limitation, if you want a Blink to be a random different color every time you press it, that is an easy program to write. - - -## Memory - -While Blinks can maintain a bit of a memory or history of what they have done, neighbors they've had, or how much time has passed, this is limited and it is important to remember that if this isn't transparent to the player, it could be very confusing. I look forward to seeing elegant solutions in which a bit of [stygmergy](https://www.wikiwand.com/en/Stigmergy) might just seep its way into a game and shed light on a beautiful invisible system that lies just beneath the surface. (more on program space below) - - -## Trickery - -As you might have noticed, you have complete control over the way a Blink displays itself to the player and the state a Blink is actually in. It might be useful to have many states an only 2 visibly different appearances to the players. Conversely, it might be useful to have only two states and show many different illuminations. - -The possibilities are many, feel free to look through the examples, they aim to walk you through the basics, and we welcome contributions for more. - -Please feel free to ask about features, and if I didn't cover something that is in the API, I should, so point it out, it belongs here, too! - - -## API - - Check out the glossary [here](glossary.md) - - Slightly more verbose version [here](api.md) - - Full API [here](https://github.com/Move38/Move38-Arduino-Platform) - - -## Nerdy Details - -Each Blink has 16KB of RAM. For not having a screen, this is more than sufficient. In fact, we recommend keeping games under 4KB, so that we can transmit them from Blink to Blink in an efficient manner. The [full code-base](https://github.com/Move38/Move38-Arduino-Platform) is published on GitHub where master contains our latest robust release and dev contains in progress features (such as an animation library). diff --git a/docs/glossary.md b/docs/glossary.md deleted file mode 100644 index b8ea0aaf..00000000 --- a/docs/glossary.md +++ /dev/null @@ -1,82 +0,0 @@ -# Glossary -[Blinks API Reference](api.md) - full API reference - -[API Source Code - absolute truth](https://github.com/Move38/Move38-Arduino-Platform) - (would I lie about something like that?) - -## Display - setColor(color) - - setFaceColor(face, color) - - -## Colors -``` -makeColorRGB(red, green, blue) - values[0-255] -makeColorHSB(hue, saturation, brightness) - values[0-255] -dim(color, value) - values[0-255] - -#RED -#ORANGE -#YELLOW -#GREEN -#CYAN -#BLUE -#MAGENTA -#WHITE -#OFF -``` - -## Button -``` -buttonPressed() -buttonReleased() -buttonSingleClicked() -buttonDoubleClicked() -buttonMultiClicked() -buttonClickCount() -buttonLongPressed() -buttonDown() -``` - -## Communication -``` - setValueSentOnAllFaces(value) - setValueSentOnFace(value, face); - getLastValueReceivedOnFace(face); - isValueReceivedOnFaceExpired(face); - didValueOnFaceChange(face); - isAlone(); - - ``` - - - -## Time -``` - millis() - monotically incrementing timer - Timer.set(duration) - Timer.isExpired() -``` - -## Types - byte - word - int - long - float - double - bool - Color - Timer - - -## Convenience - -``` -*FOREACH_FACE(f) { } -*COUNT_OF(array); -*FACE_COUNT 6 -*define MAX_BRIGHTNESS -``` - - – the asterisk signals that this function is syntactic sugar… i.e. you can get the same information by means of the functions included, but it is such a common occurrence that we include it for everyone’s benefit. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index ee72e459..00000000 --- a/docs/index.md +++ /dev/null @@ -1,38 +0,0 @@ - - -# Welcome Blinks Developers! - -Congrats, you got your hands on a [Move38](https://www.move38.com/) dev kit for Blinks!* Seriously, how did you do that?! There are so few in existence, roughly just 0.00000001% of humanity gets to experience what you’re about to! (Deep breath) Okay, we’ll settle down. Let’s just say, you’re special, and we thank you for that 🙂 - -**Welcome to the Blinks Documentation!** We’ve created this resource as a way for Blinks developers to have everything they should need at their fingertips to begin designing games for Blinks as quickly as possible. - -If you just received your Dev Kit, your first stop should be the [Quick Start Guide](quickstart.md). It will help you understand the physical contents of your Dev Kit and also contains instructions on how to install the software and drivers you’ll need to develop Blinks games. - -After you have all the libraries downloaded and installed, where you go next depends on how you like to work. - ----------- - -[Functionality Guide](functionality.md) - Learn exactly what your Blinks are capable of. - -[Games Primer](primer.md) - A list of game mechanics and categories that are currently being utilized. - -[Blinks API Reference](api.md) - list of all the function calls that Blinks needs (it’s short, like really short) - -[Glossary](glossary.md) - bare bones list of all function calls w/o parameters - - -## External Resources - -[¡¡¡Blinks Discourse Forum!!!](http://forum.move38.com/) 🙂 - The Blinks developer community awaits 😍 🤠 🤗😨 - -[Blinks Tutorials](http://forum.move38.com/c/tutorials) - Like tutorials? While we recommend the examples in the `File/examples` menu, sometimes the step by step is a little more helpful. Here’s a couple to get you started. - -[Blinks Source Code (GitHub Repo)](https://github.com/Move38/Move38-Arduino-Platform) - The truth, the whole truth, and nothing but the truth - -Tried Discourse and scoured the documentation but still have questions? -Reach out at blinks@move38.com. - - ----------- - -* If you are reading this and in fact do not yet have your hands on a Blinks Dev-Kit, hang tight, [you are one step closer.](https://blinks.backerkit.com/hosted_preorders) diff --git a/docs/mstile-150x150.png b/docs/mstile-150x150.png deleted file mode 100644 index 5683752b..00000000 Binary files a/docs/mstile-150x150.png and /dev/null differ diff --git a/docs/oldglossary.md b/docs/oldglossary.md deleted file mode 100644 index fea83972..00000000 --- a/docs/oldglossary.md +++ /dev/null @@ -1,126 +0,0 @@ -# Glossary - -[Blinks API Reference](api.md) - a reference with function parameters - - -[API Source Code](https://github.com/Move38/Move38-Arduino-Platform) - absolute truth (would I lie about something like that?) - -## Display - -`setColor(color)` -`setFaceColor( face, color)` - -## Colors - -`makeColorRGB(red, green, blue) - values [0-255]` - -`*makeColorHSB(hue, saturation, brightness) - values [0-255]` - -`*dim(color, value) - values[0-255]` - -`*RED` - -`*ORANGE` - -`*YELLOW` - -`*GREEN` - -`*CYAN` - -`*BLUE` - -`*MAGENTA` - -`*WHITE` - -`*OFF` - -## Button - -`buttonPressed()` - -`buttonSingleClicked()` - -`buttonDoubleClicked()` - -`buttonMultiClicked()` - -`buttonClickCount()` - -`buttonLongPressed()` - -`buttonDown()` - -## Communication - -`setValueSentOnAllFaces(value) ` - -`setValueSentOnFace(value, face)` - -`getLastValueReceivedOnFace(face) ` - -`isValueReceivedOnFaceExpired(face)` - -`*didValueOnFaceChange() ` - -`*isAlone()` - -## Time - -`millis() - monotonically incrementing timer` - -`Timer.set()` - -`Timer.isExpired()` - -`Types` - -`byte` - -`word` - -`int` - -`long` - -`float` - -`double` - -`bool` - -`Color` - -`Timer` - -## Convenience - -`*FOREACH_FACE(f) { }` - -`*COUNT_OF(array)` - -`*FACE_COUNT` - -`*MAX_BRIGHTNESS` - -* – the asterisk signals that this function is syntactic sugar… i.e. you can get the same information by means of the functions included, but it is such a common occurrence that we include it for everyone’s benefit. - - ----------- -# Coming Soon… -## Collaboration – Time & Communication - -`Metronome.start(period)` - -`Metronome.stop()` - -`Metronome.didTick()` - -`Metronome.getPhase()` - -`Step.next()` - -`Step.getHighestSeen()` - -`*Step.didStep() - returns number of times stepped… since` diff --git a/docs/primer.md b/docs/primer.md deleted file mode 100644 index 02bb5c72..00000000 --- a/docs/primer.md +++ /dev/null @@ -1,68 +0,0 @@ -# Blinks Games Primer -This list is by no means exhaustive and each of the mechanics, categories, and so forth are meant to be mixed, matched, or broken. We hate being boxed into categories ourselves, so no need to box you in either. Hopefully this helps to analyze games and find what parts are compelling, fun, engaging, and worth investigating further. - -## Mechanics -### Fracture - -Taking Blinks out of the middle of a pack is difficult, it is surrounded by magnets, so instead, we created Fracture (credit to Mike Lazer-Walker for this mechanic). Break the board of Blinks into two halves (of any size, 6 & 6, or 1 & 11) and then translate, rotate, and put them back together. - - -### Hinge (rotation) - -Think fracture, except instead of freely moving the two parts that you have fractured, with hinge, one of the vertices acts as a hinge (you will feel it with the magnets) and then you can rotate the two parts around that hinge to reconnect. Works quite well with smaller games (6 - 9 pieces) - - -### Flick - -Sound painful? It isn’t. Blinks are up for the game and their enclosures are finger friendly. Test your dexterity in flicking Blinks into a specific arrangement. Play a game of table football or capture the flag with Blinks. - - -### Spin - -No, these are not fidget spinners… wait, do you want a fidget spinner? Okay, I guess you can fidget with them as well as spin them. When a Blink is spinning next to a neighbor, it can measure how fast it is spinning. - - -### Button Mash - -Just as it sounds, the buttons are factory tested to 200,000 presses… will your game prove us wrong? - - -### Push/Pull - -Use the strength of the magnets to make chains of Blinks that need to be dragged from one location to another. The bottoms of Blinks are meant to glide on a tabletop. - - -### Free Range - -Create a board for Blinks and use them as pieces that move around the table. While they are not aware of their absolute location on the table (at least not yet) they can rely on their other traits such as time and button presses. - - -### Stack (highly experimental) - -Blinks sadly don’t communicate vertically, but if you tilt them to 90º and then start stacking them, they will provide a thin and very unstable wall. They don’t know anything about their orientation, but they do know when they are together and when they have broken apart. I bet some cleverness could sense the fall as well. - - -### Suggestions…? - -Please, there are so many ways to interact with Blinks, you might spark the next game we create! - - -## Gameplay -### Map building - -Treat Blinks as an arena that something can travel through or navigate. Maybe this is the bouncing of a ball, the flow of water, or spelunking of sorts. Blinks can rearrange to update the map, and even carry some knowledge of where the player has been. - - -### Puzzle - -In its simplest form, Blinks can hint and help along the way to solving a puzzle. Think of trying to achieve certain conformations or getting a group to behave in a certain way. Another puzzle might be diffusing a bomb in a Mastermind like way… Puzzles can contribute to a Blinks games or be the game in itself. - - -### Real-Time Strategy - -Using time as a variable in Blinks games is a powerful tool since time can be agreed upon across connected Blinks or maintained individually (i.e. each Blink dying over time with its own health meter in Mortals) - - -### MMO - Massively Multiplayer Offline 🙂 - -A common question is, “do Blinks connect to devices or the internet?” Currently, nope. And we think it is a great feature and constraint… Blinks are completely self contained meaning you don’t have to trade your precious cell phone battery life to play a game on Blinks. That said, the internet is the most amazing of distributed systems, why wouldn’t we want to take advantage of such magic. If all goes well, there is a nice roadmap for Blinks, which includes new Blinks such as Blink blanks, allowing you to make and share your own games on Blinks. Blink blonk, an audio enabled Blink and Blink bloop blop, an IOT Blink, enabled with WiFi + BLE. diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index b0892bad..00000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,106 +0,0 @@ -# Blinks Quick Start Guide - - -## What’s Included -- 6x Blinks - - 6x custom PCBs, - - 6x 3D printed bases - - 6x 12 polarity aligned magnets - - 6x CR2032 coin cell batteries - - 6x battery/magnet security screw - - 1x Carry Case (magnetic enclosure for carrying 6 Blinks) - - -- Programmer - - 1x AVR Pocket [AVR programmer](https://www.sparkfun.com/products/9825) (AKA USBtinyISP) - - 1x USB mini cable (3’) - - -- Service Port (used for Serial Monitor) - - 1x Blinks Serial Adapter - - 1x FTDI board (Red w/ usb on one side and 6 pins on the other) - - 1x Molex cable (4 black wires) - - 1x USB mini cable (6”) - - -## Needed Software -- Arduino IDE - [Download it here](https://www.arduino.cc/en/Main/Software) -- Blinks Library - [Download it here](https://github.com/Move38/Move38-Arduino-Platform/zipball/master) - -*If you want to have the latest and contribute to the development of Blinks, here’s our [Github Repo](https://github.com/Move38/Move38-Arduino-Platform/tree/dev) - - -## Installing the Blinks Library - -After you have installed the Arduino IDE (> 1.8.5) and launched the application, you will notice that Arduino has created a folder in your `Documents` folder, aptly named, `Arduino`. Follow these next 5 steps to have the Blinks library installed: - - -1. Create a folder called `hardware` inside of the Arduino folder `~/Documents/Arduino/hardware` (it will be next to ‘libraries’, not nested inside) and one more called `Move38-Blinks-Library` inside of the hardware folder you just created. `~/Documents/Arduino/hardware/Move38-Blinks-Library` -2. Download the Blinks Library if you haven’t already done so -3. Unzip the Blinks Library into a folder called `avr` -5. Move the Blinks Library into the `Move38-Blinks-Library` folder (the path will be `~/Documents/Arduino/hardware/Move38-Blinks-Library/avr/`) -6. Restart Arduino (quit and relaunch) - -If you click on `Tools` in your menu bar, and navigate down to `Board:` you should now see `Move38: Blinks Tile` at the bottom. Select it. - -Next, click on `Tools` in your menu bar, and navigate down to `Programmer:` select the programmer `USBTinyISP` or see below for faster upload time with `Blinks Programmer` - -Now if you go to `File/Examples`, you should see Examples for Blinks Tiles at the bottom of that list as well. Open the first one up and see if it compiles. - - - -## Transfer code to Blink - -![Image of Transferring Blink Code](assets/transfercode.jpeg) - -1. Connect the USB programmer -2. Place the magnetic programming jig on top of the Blink -3. Press `Upload` to transfer - -Not successfully transferring to your Blinks? try these [troubleshooting tips from the forum](http://forum.move38.com/t/error-the-selected-serial-port-does-not-exist-or-your-board-is-not-connected/79/3?u=jbobrow) - - -## Windows Troubleshooting - -No programmer detected? -Check to make sure you have the correct [driver installed](https://learn.adafruit.com/usbtinyisp/drivers), relaunch the Ardunio IDE and then you should be able to select USBTinyISP from the Tools > Programmer drop-down in Arduino. - -For the extra curious, here is the [source code](https://github.com/sparkfun/Pocket_AVR_Programmer/tree/master/Drivers) for that driver. - - -## *Faster Upload Time -1. Download [this](https://move38.com/attic/blinks/programmers.txt) file (right click and save link as) -2. Move file to `/Applications/Arduino.app/Contents/Java/hardware/arduino/avr` (it will replace the existing file here) -3. Restart Arduino if it was open -4. Now select the `Blinks Programmer` when programming and it should be roughly 4x faster upload speeds ⚡ - -Not seeing `Blinks Programmer`? Try out [these steps](http://forum.move38.com/t/faster-upload-time-directory-change-and-modified-programmers-txt/83), found by Alpha Blinks Dev, Ken 🙂 - - -## Using the Blinks Service Port to talk to or listen to your Blink - -The Service Port is by no means necessary, but is a phenomenal tool for debugging code and refining early sketches into robust games. The following steps show you how to use your Blinks Service Port. - -1. Put together the components of your Blinks Service port so they look like this - 1. USB Cable → FTDI → Blinks Serial Adapter → Molex Cable → Blink (w/ Serial port) - 2. -2. Open Arduino -3. click on `Tools` in your menu bar, and navigate down to `Port:` you should now see `COM3` on a PC and `/dev/cu...` on a Mac. Select it. -4. press the magnifying glass in the top right corner of the Arduino IDE to open the Serial Monitor - -Note: The serial port is at a fixed 500k baud rate (set it in the drop-down). - -To write to the Serial port from a sketch, - -1. Add “#include Serial.h” -2. Instantiate a ServicePortSerial class, like this `ServicePortSerial sp` -3. Call the begin method within setup(), like this `sp.begin()` - -Here is the simplest of ServicePort sketches: - - -## Unboxing And Walkthrough Video - -{% include youtubePlayer.html id='UA1Vl7x3Y7g' %} - -And last but not least, [here’s a handy little unboxing video](https://www.youtube.com/watch?v=UA1Vl7x3Y7g) that will walk you through some basics! diff --git a/docs/safari-pinned-tab.svg b/docs/safari-pinned-tab.svg deleted file mode 100644 index 10fc7259..00000000 --- a/docs/safari-pinned-tab.svg +++ /dev/null @@ -1,175 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - - - - diff --git a/docs/site.webmanifest b/docs/site.webmanifest deleted file mode 100644 index de65106f..00000000 --- a/docs/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "", - "short_name": "", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/docs/video-embed.css b/docs/video-embed.css deleted file mode 100755 index c7e2177c..00000000 --- a/docs/video-embed.css +++ /dev/null @@ -1,20 +0,0 @@ -/* Nathan Lam - June 2, 2017 - - Add this css for responsive embedded videos in your .css file. */ - -.embed-container { - position: relative; - padding-bottom: 56.25%; - height: 0; - overflow: hidden; - max-width: 100%; -} - -.embed-container iframe, .embed-container object, .embed-container embed { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} \ No newline at end of file diff --git a/externalprogrammers.txt.bak b/externalprogrammers.txt.bak deleted file mode 100644 index edf81fa0..00000000 --- a/externalprogrammers.txt.bak +++ /dev/null @@ -1,98 +0,0 @@ -################################ -## These programmers will only -## appear if you're using the -## ArduinoEclipse/Sloeber IDE. -## Download this great software -## at: http://eclipse.baeyens.it -################################ - - -avrisp.name=AVR ISP -avrisp.communication=serial -avrisp.protocol=stk500v1 -avrisp.program.protocol=stk500v1 -avrisp.program.tool=avrdude -avrisp.program.extra_params=-P{serial.port} - -avrispmkii.name=AVRISP mkII -avrispmkii.communication=usb -avrispmkii.protocol=stk500v2 -avrispmkii.program.protocol=stk500v2 -avrispmkii.program.tool=avrdude -avrispmkii.program.extra_params=-Pusb - -usbtinyisp.name=USBtinyISP -usbtinyisp.protocol=usbtiny -usbtinyisp.program.tool=avrdude -usbtinyisp.program.extra_params= - -arduinoisp.name=ArduinoISP -arduinoisp.protocol=arduinoisp -arduinoisp.program.tool=avrdude -arduinoisp.program.extra_params= - -arduinoisporg.name=ArduinoISP.org -arduinoisporg.protocol=arduinoisporg -arduinoisporg.program.tool=avrdude -arduinoisporg.program.extra_params= - -usbasp.name=USBasp -usbasp.communication=usb -usbasp.protocol=usbasp -usbasp.program.protocol=usbasp -usbasp.program.tool=avrdude -usbasp.program.extra_params=-Pusb - -parallel.name=Parallel Programmer -parallel.protocol=dapa -parallel.force=true -# parallel.delay=200 -parallel.program.tool=avrdude -parallel.program.extra_params=-F - -arduinoasisp.name=Arduino as ISP -arduinoasisp.communication=serial -arduinoasisp.protocol=stk500v1 -arduinoasisp.speed=19200 -arduinoasisp.program.protocol=stk500v1 -arduinoasisp.program.speed=19200 -arduinoasisp.program.tool=avrdude -arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed} - -usbGemma.name=Arduino Gemma -usbGemma.protocol=arduinogemma -usbGemma.program.tool=avrdude -usbGemma.program.extra_params= -usbGemma.config.path={runtime.platform.path}/bootloaders/gemma/avrdude.conf - -# STK500 firmware version v1 and v2 use different serial protocols. -# Using the 'stk500' protocol tells avrdude to try and autodetect the -# firmware version. If this leads to problems, we might need to add -# stk500v1 and stk500v2 entries to allow explicitely selecting the -# firmware version. -stk500.name=Atmel STK500 development board -stk500.communication=serial -stk500.protocol=stk500 -stk500.program.protocol=stk500 -stk500.program.tool=avrdude -stk500.program.extra_params=-P{serial.port} - -## Notes about Dangerous Prototypes Bus Pirate as ISP -## Bus Pirate V3 need Firmware v5.10 or later -## Bus Pirate V4 need Firmware v6.3-r2151 or later -## Could happen that BP does not have enough current to power an Arduino board -## through the ICSP connector. In this case disconnect the +Vcc from ICSP connector -## and power Arduino board in the normal way. -buspirate.name=BusPirate as ISP -buspirate.communication=serial -buspirate.protocol=buspirate -buspirate.program.protocol=buspirate -buspirate.program.tool=avrdude -buspirate.program.extra_params=-P{serial.port} - -atmel_ice.name=Atmel-ICE (AVR) -atmel_ice.communication=usb -atmel_ice.protocol=atmelice_isp -atmel_ice.program.protocol=atmelice_isp -atmel_ice.program.tool=avrdude -atmel_ice.program.extra_params=-Pusb diff --git a/libraries/Examples01/examples/B-ButtonPress/B-ButtonPress.ino b/libraries/Examples01/examples/B-ButtonPress/B-ButtonPress.ino index 58997749..d8fabfde 100644 --- a/libraries/Examples01/examples/B-ButtonPress/B-ButtonPress.ino +++ b/libraries/Examples01/examples/B-ButtonPress/B-ButtonPress.ino @@ -12,7 +12,7 @@ void loop() { } else { - setColor( OFF ); + setColor( GREEN ); } } diff --git a/libraries/Examples01/examples/F-ColorWheel/F-ColorWheel.ino b/libraries/Examples01/examples/F-ColorWheel/F-ColorWheel.ino index 82f77c05..b1dee6b7 100644 --- a/libraries/Examples01/examples/F-ColorWheel/F-ColorWheel.ino +++ b/libraries/Examples01/examples/F-ColorWheel/F-ColorWheel.ino @@ -22,10 +22,8 @@ void loop() { // (255 is 11111111 in binary, and 11111111 + 00000001 = 00000000) hue++; - nextStep.set(10); // Step to (slightly) different color 100 timer per second - whole cycle will take ~2.5 seconds. + nextStep.set(10); // Step to (slightly) different color 100 times per second - whole cycle will take 255 steps *10ms = ~2.5 seconds. } } - - diff --git a/libraries/Examples02/examples/A-ColorByNeighbor/A-ColorByNeighbor.ino b/libraries/Examples02/examples/A-ColorByNeighbor/A-ColorByNeighbor.ino index 9c5b5fcd..7d5a761d 100644 --- a/libraries/Examples02/examples/A-ColorByNeighbor/A-ColorByNeighbor.ino +++ b/libraries/Examples02/examples/A-ColorByNeighbor/A-ColorByNeighbor.ino @@ -9,7 +9,7 @@ // color by number of neighbors Color colors[] = { - dim(WHITE,5), // 0 neighbors + dim(WHITE,40), // 0 neighbors RED, // 1 neighbors YELLOW, // 2 neighbors GREEN, // 3 neighbors diff --git a/libraries/Examples02/examples/B-ColorRamps/B-ColorRamps.ino b/libraries/Examples02/examples/B-ColorRamps/B-ColorRamps.ino index e47e4170..1d498289 100644 --- a/libraries/Examples02/examples/B-ColorRamps/B-ColorRamps.ino +++ b/libraries/Examples02/examples/B-ColorRamps/B-ColorRamps.ino @@ -13,8 +13,8 @@ void setup() { // ~1.17 secs/ramp // We do an up ramp and then a down ramp for each color -#define STEP_SIZE 30 -#define STEP_TIME_MS 100 +#define STEP_SIZE 10 +#define STEP_TIME_MS 30 Color colors[] = { BLUE , RED , GREEN }; diff --git a/libraries/Examples02/examples/C-SerialNumberPrinter/C-SerialNumberPrinter.ino b/libraries/Examples02/examples/C-SerialNumberPrinter/C-SerialNumberPrinter.ino index f26e1610..f6062841 100644 --- a/libraries/Examples02/examples/C-SerialNumberPrinter/C-SerialNumberPrinter.ino +++ b/libraries/Examples02/examples/C-SerialNumberPrinter/C-SerialNumberPrinter.ino @@ -63,4 +63,3 @@ void loop() { while (1); } - diff --git a/libraries/Examples02/examples/D-SerialTest/D-SerialTest.ino b/libraries/Examples02/examples/D-SerialTest/D-SerialTest.ino new file mode 100644 index 00000000..f4819247 --- /dev/null +++ b/libraries/Examples02/examples/D-SerialTest/D-SerialTest.ino @@ -0,0 +1,74 @@ +/* + * Example showing how to use the serial port. + * + * Enter the letters "R", "G", or "B" over the serial port to change the color of the blink. + * Push the button on the blink to set back to WHITE. + * + * To use this you'll need to connect a serial terminal to the Blinks service port. + * More info on how to do that here... + * https://github.com/bigjosh/Move38-Arduino-Platform/blob/master/Service%20Port.MD + */ + +#include "Serial.h" + +ServicePortSerial Serial; + +void setup() { + + Serial.begin(); + Serial.println("Send the letter R,G, or B to change color..."); + + setColor( WHITE ) ; + +} + + +void loop() { + + if (Serial.available()) { + + int c = Serial.read(); // We know this will return a non-negative value since we checked that there was a byte available above + + switch (c) { + + case 'R': + case 'r': + setColor(RED); + Serial.println("Color set to RED."); + break; + + case 'G': + case 'g': + setColor(GREEN); + Serial.println("Color set to GREEN."); + break; + + case 'B': + case 'b': + setColor(BLUE); + Serial.println("Color set to BLUE."); + break; + + case '\r': + case '\n': + break; // Ignore carridge return/enter key + + default: + Serial.println("I didn't understand."); + break; + + } + + } + + + if (buttonPressed()) { + + + Serial.println("Button pressed."); + setColor(WHITE); + Serial.println("Color set to WHITE."); + + } + +} diff --git a/libraries/Examples02/examples/E-ButtonGym/E-ButtonGym.ino b/libraries/Examples02/examples/E-ButtonGym/E-ButtonGym.ino new file mode 100644 index 00000000..b835bf1b --- /dev/null +++ b/libraries/Examples02/examples/E-ButtonGym/E-ButtonGym.ino @@ -0,0 +1,100 @@ +/* + * + * Exercise the button functions to see how they work and + * and also show off some fancy C++ footwork! + * + * Each of the button events lights one of the pixels + * so you can see that it happened. Check `indicators` for the + * list of events and what pixel/color they will show up as. + * + */ + +// How long should an inidcator stay lit after trigger? +static const word fade_time_ms = 200; + +void setup() { + + // No setup needed for this simple example! + +} + + +class fadingIndicator_t { + + + private: + + bool (*testFunction)(); + + byte face; + + Color color; + + + unsigned long trigger_time_ms; + + byte get_current_brightness() { + + if ( millis() > trigger_time_ms + fade_time_ms ) { + return 0; + } + + word age_ms = millis() - trigger_time_ms; + + //word brightness = fade_time_ms - age_ms; + + word inverted_brightness = map( age_ms , 0 , fade_time_ms , 0 , 255 ); + + return 255-inverted_brightness; + + } + + public: + + inline void trigger(void) { + + trigger_time_ms = millis(); + + } + + inline void show() { + setFaceColor( face , dim( color , get_current_brightness() ) ); + } + + + inline void update(void ) { + + if ( (*testFunction)() ) { + + trigger(); + + } + + } + + fadingIndicator_t( byte face, Color color , bool (*testFunction)() ) : face(face), color(color) , testFunction( testFunction ) {} + +}; + +fadingIndicator_t indicators[] { + + { 0 , RED , buttonPressed }, + { 1 , GREEN , buttonReleased }, + { 2 , BLUE , buttonSingleClicked }, + { 3 , MAGENTA, buttonDoubleClicked }, + { 4 , YELLOW , buttonMultiClicked }, + { 5 , CYAN , buttonLongPressed }, + +}; + + +void loop() { + + for(auto &item : indicators ) { + + item.update(); + item.show(); + + } + +} diff --git a/libraries/Examples02/examples/F-SimpleDatagramDemo/F-SimpleDatagramDemo.ino b/libraries/Examples02/examples/F-SimpleDatagramDemo/F-SimpleDatagramDemo.ino new file mode 100644 index 00000000..0126afe0 --- /dev/null +++ b/libraries/Examples02/examples/F-SimpleDatagramDemo/F-SimpleDatagramDemo.ino @@ -0,0 +1,127 @@ +// SimpleDatagramDemo +// Simplest example of how to send and recieve datagrams over the IR link. +// Face shows: +// GREEN when it sees a neighboor and is getting a valid value from it (the number `42` is sent in this demo) +// CYAN when it sees a neighboor and is getting an invalid value from it (anything other than the number `42` in this demo) +// BLUE blink for 250ms when it sees a valid datagram (the three bytes {'C','A','T'} in the demo) +// RED blink for 250ms when it sees an invalid datagram (anything but the three bytes {'C','A','T'} in the demo) +// +// Pressing the button sends the sample datagram on all faces + +// A datagram is a multibyte message that is sent on a best-efforts basis +// Each datagram sent will be recieved at most one time (it might never be recieved) +// Datagrams can be up to `IR_DATAGRAM_LEN` bytes long + +// Send somethign on all faces so we can check that we got it + +#define MAGIC_VALUE 42 + +// Leave the current color on this face until this timer expires +static Timer showColorOnFaceTimer[ FACE_COUNT ]; + +#define SHOW_COLOR_TIME_MS 250 // Long enough to see + +byte sampleDatagram[] { 'C' , 'A' , 'T' }; // A sample 3 byte long datagram + + +void setup() { + setValueSentOnAllFaces(MAGIC_VALUE); +} + + +/* + * Compare memory regions. Returns 0 if all bytes in the blocks match. + * Lifted from https://github.com/f32c/arduino/blob/master/hardware/fpga/f32c/system/src/memcmp.c + */ + + int memcmp(const void *s1, const void *s2, unsigned n) +{ + if (n != 0) { + const unsigned char *p1 = s1, *p2 = s2; + + do { + if (*p1++ != *p2++) + return (*--p1 - *--p2); + } while (--n != 0); + } + return (0); +} + + +void loop() { + + // First check all faces for an incoming datagram + + FOREACH_FACE(f) { + + if ( isDatagramReadyOnFace( f ) ) { + + const byte *datagramPayload = getDatagramOnFace(f); + + // Check that the length and all of the data btyes of the recieved datagram match what we were expecting + // Note that `memcmp()` returns 0 if it is a match + + if ( getDatagramLengthOnFace(f)==sizeof( sampleDatagram ) && !memcmp( datagramPayload , sampleDatagram , sizeof( sampleDatagram )) ) { + + // This is the datagram we are looking for! + + setColorOnFace( BLUE , f ); + + } else { + + // Oops, we goty a datagram, but not the one we are loooking for + + setColorOnFace( RED , f ); + + } + + + showColorOnFaceTimer[f].set( SHOW_COLOR_TIME_MS ); + + // We are done with the datagram, so free up the buffer so we can get another on this face + markDatagramReadOnFace( f ); + + + } else if ( showColorOnFaceTimer[f].isExpired() ) { + + if ( !isValueReceivedOnFaceExpired( f ) ) { + + // Show green if we do have a neighbor + + if (getLastValueReceivedOnFace(f) == MAGIC_VALUE ) { + + setColorOnFace( GREEN , f ); + + } else { + + setColorOnFace( CYAN, f ); + + + } + + } else { + + // Or off if no neighbor + + setColorOnFace( OFF , f ); + + } + + } + + } + + + if (buttonPressed()) { + + // When the button is click, trigger a datagram send on all faces + + FOREACH_FACE(f) { + + sendDatagramOnFace( &sampleDatagram , sizeof( sampleDatagram ) , f ); + + } + + } + +} // loop() diff --git a/libraries/Examples02/examples/G-IRButtonPressTester/G-IRButtonPressTester.ino b/libraries/Examples02/examples/G-IRButtonPressTester/G-IRButtonPressTester.ino new file mode 100644 index 00000000..8a273008 --- /dev/null +++ b/libraries/Examples02/examples/G-IRButtonPressTester/G-IRButtonPressTester.ino @@ -0,0 +1,95 @@ +/* + * IR Button Press + * + * Great for testing if blinks work and can communicate over IR + * + * Each face shows: + * YELLOW - No neighbor on this face + * BLUE - Neighbor button UP + * GREEN - Neighbor button DOWN + * RED - Error. Saw unexpected datum on this face. Resets after 0.5 second. + * + */ + + +void setup() { + // Blank +} + +// Pick some unlikely values so we are more likely to show an error if a different +// game is runnning on the oposing blink + +#define VALUE_BUTTON_UP 12 +#define VALUE_BUTTON_DOWN 21 + +// Did we get an error onthis face recently? +Timer errorOnFaceTimer[ FACE_COUNT ]; + +const int showErrTime_ms = 500; // Show the errror for 0.5 second so people can see it + +void loop() { + + // Make a little pulsation so we know it is working + + uint8_t brightness = sin8_C( (millis()/4) & 0xff ); + + FOREACH_FACE(f) { + + Color color; + + // Set the color on this face based on what we see... + + if ( !isValueReceivedOnFaceExpired( f ) ) { // Have we seen an neighbor on this face recently? + + switch ( getLastValueReceivedOnFace( f ) ) { + + case VALUE_BUTTON_UP : + color = BLUE; + break; + + case VALUE_BUTTON_DOWN: + color=GREEN; + break; + + default: + // anything else is an error + // We set a timer so this face will show red for a little while to make sure it is seen + errorOnFaceTimer[f].set( showErrTime_ms ); + break; + + } + + } else { + + // No neighbor on this face right now + + color=YELLOW; + + } + + // The red (error) will cover any other color on the face unil the timer times out + + if ( !errorOnFaceTimer[f].isExpired() ) { + + color = RED; + + } + + setColorOnFace( dim( color , brightness ) , f ); + + } + + // Finally update the value we send to others to match our current button state + + + if ( buttonDown() ) { + + setValueSentOnAllFaces( VALUE_BUTTON_DOWN ) ; + + } else { + + setValueSentOnAllFaces( VALUE_BUTTON_UP ) ; + + } + +} diff --git a/libraries/Examples03/examples/A-MortalsGame/A-MortalsGame.ino b/libraries/Examples03/examples/A-MortalsGame/A-MortalsGame.ino deleted file mode 100644 index acdebd1c..00000000 --- a/libraries/Examples03/examples/A-MortalsGame/A-MortalsGame.ino +++ /dev/null @@ -1,182 +0,0 @@ -/* -* Mortals for Blinks -* -* Setup: 2 player game. Tiles die slowly over time, all at the same rate. -* Moving a single tile to a new place allows it to suck life from -* surrounding tiles, friend or foe. -* -* Blinks start with 60 seconds of life -* -* When a tile is moved alone, it sucks 5 seconds of health from each one of -* its newly found neighbors. A non-moved neighbor with a new neighbor looses 5 seconds -* and animates in the direction it lost the life (i.e. where the neighbor showed up) -* -* Tiles resets health to full when triple press occurs -* -* States for game piece. -* Alive/Dead -* Team -* Attack, Injured -* -* Technical Details: -* A long press on the tile changes the color of the tile for prototyping (switch state 1 or 2) -* -* -*/ - -#define ATTACK_VALUE 5 // Amount of health you loose when attacked. -#define ATTACK_DURRATION_MS 100 // Time between when we see first new neighbor and when we stop attacking. -#define HEALTH_STEP_TIME_MS 1000 // Health decremented by 1 unit this often - -#define INJURED_DURRATION_MS 100 // How long we stay injured after we are attacked. Prevents multiple hits on the same attack cycle. - -#define INITIAL_HEALTH 60 -#define MAX_HEALTH 90 - -word health; - -Timer healthTimer; // Count down to next time we loose a unit of health - -enum State { - DEAD, - ALIVE, - ENGUARDE, // I am ready to attack! - ATTACKING, // Short window when I have already come across my first victim and started attacking - INJURED -}; - -byte mode = DEAD; - -Timer modeTimeout; // Started when we enter ATTACKING, when it expires we switch back to normal ALIVE. -// Started when we are injured to make sure we don't get injured multiple times on the same attack - -void setup() { - blinkStateBegin(); -} - - -void loop() { - - // Update our mode first - - if(buttonDoubleClicked()) { - // reset game piece - mode=ALIVE; - health=INITIAL_HEALTH; - healthTimer.set(HEALTH_STEP_TIME_MS); - } - - if (healthTimer.isExpired()) { - - if (health>0) { - - health--; - healthTimer.set(HEALTH_STEP_TIME_MS); - - } else { - - mode = DEAD; - - } - - } - - if ( mode != DEAD ) { - - if(isAlone()) { - - mode = ENGUARDE; // Being lonesome makes us ready to attack! - - } else { // !isAlone() - - if (mode==ENGUARDE) { // We were ornery, but saw someone so we begin our attack in earnest! - - mode=ATTACKING; - modeTimeout.set( ATTACK_DURRATION_MS ); - } - - } - - - if (mode==ATTACKING || mode == INJURED ) { - - if (modeTimeout.isExpired()) { - mode=ALIVE; - } - } - } // !DEAD - - FOREACH_FACE(f) { - - - if(!isValueReceivedOnFaceExpired(f)) { - - byte neighborMode = getLastValueReceivedOnFace(f); - - if ( mode == ATTACKING ) { - - // We take our flesh when we see that someone we attacked is actually injured - - if ( neighborMode == INJURED ) { - - // TODO: We should really keep a per-face attack timer to lock down the case where we attack the same tile twice in a single interaction. - - health = min( health + ATTACK_VALUE , MAX_HEALTH ); - - } - - } else if ( mode == ALIVE ) { - - if ( neighborMode == ATTACKING ) { - - health = max( health - ATTACK_VALUE , 0 ) ; - - mode = INJURED; - - modeTimeout.set( INJURED_DURRATION_MS ); - - } - - } else if (mode==INJURED) { - - if (modeTimeout.isExpired()) { - - mode = ALIVE; - - } - } - } - } - - - // Update our display based on new state - - switch (mode) { - - case DEAD: - setColor( dim( RED , 40 ) ); - break; - - case ALIVE: - setColor( dim( GREEN , (health * MAX_BRIGHTNESS ) / MAX_HEALTH ) ); - break; - - case ENGUARDE: - setColor( CYAN ); - break; - - case ATTACKING: - setColor( BLUE ); - break; - - case INJURED: - setColor( ORANGE ); - break; - - } - - setValueSentOnAllFaces( mode ); // Tell everyone around how we are feeling - -} - - diff --git a/libraries/Examples03/examples/Astro/Astro.ino b/libraries/Examples03/examples/Astro/Astro.ino new file mode 100644 index 00000000..5d1cb654 --- /dev/null +++ b/libraries/Examples03/examples/Astro/Astro.ino @@ -0,0 +1,462 @@ +/* + * Astro + * by Move38, Inc. 2019 + * Lead development by Dan King + * original game by Diamond and Colby + * + * Rules: https://github.com/Move38/Astro/blob/master/README.md + * + * -------------------- + * Blinks by Move38 + * Brought to life via Kickstarter 2018 + * + * @madewithblinks + * www.move38.com + * -------------------- + */ + +#define RESET_INTERVAL 3000 + +enum blinkRoles {ASTEROID, SHIP}; +byte blinkRole = ASTEROID; + +Timer animTimer; +byte animFrame = 0; + +#define DIED_MINED 0 +#define DIED_NATURAL 5 +#define NO_ORE_TARGET 7 + +////ASTEROID VARIABLES +byte oreLayout[6]; +byte oreBrightness[6] = {0, 0, 0, 0, 0, 0}; +Color oreColors [5] = {OFF, ORANGE, CYAN, YELLOW, GREEN}; // ORE COLORS ARE ORANGE, GREEN, CYAN, and YELLOW +Timer resetTimer; +byte isMinable[6]; +byte fadeColorIndex; + +////SHIP VARIABLES +byte missionCount = 6; +byte missionComplete; +byte gameComplete; +byte isMining[6] = {false, false, false, false, false, false}; +byte oreTarget = NO_ORE_TARGET; +byte oreCollected; +int miningTime = 500; +byte displayMissionCompleteColor; +byte displayMissionCompleteIndex = 0; + +bool bChangeRole = false; +bool bLongPress = false; + +void setup() { + // put your setup code here, to run once: + randomize(); // make sure our astroid is unique + newAsteroid(); + updateAsteroid(); + newMission(); +} + +void loop() { + + // discard role change from force sleep + if(hasWoken()) { + bLongPress = false; + } + + if (buttonLongPressed()) { + bLongPress = true; + } + + if(buttonReleased()) { + if(bLongPress) { + // now change the role + bChangeRole = true; + bLongPress = false; + } + } + + switch (blinkRole) { + case ASTEROID: + asteroidLoop(); + asteroidDisplay(); + break; + case SHIP: + shipLoop(); + shipDisplay(); + break; + } + + // Long press display to confirm long press + if(bLongPress) { + setColorOnFace(CYAN,0); + setColorOnFace(CYAN,1); + setColorOnFace(ORANGE,2); + setColorOnFace(YELLOW,3); + setColorOnFace(GREEN,4); + setColorOnFace(GREEN,5); + } +} + +void asteroidLoop() { + if (bChangeRole) { + blinkRole = SHIP; + missionCount = 6; + newMission(); + gameComplete = false; + bChangeRole = false; + } + + //ok, so let's look for ships that need ore + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getBlinkRole(neighborData) == SHIP) { //this neighbor is a ship + //so here we check to see what our relationship to them is + if (isMinable[f]) {//this is a face we have offered ore to + if (getShipMining(neighborData) == 1) {//this ship is mining! we can stop offering! + isMinable[f] = false; + } + } else {//this is a face we COULD offer ore to + if (oreCheck(getShipTarget(neighborData))) {//this is asking for something we have + removeOre(getShipTarget(neighborData)); + isMinable[f] = true; + } + } + } + } else {//no neighbor + isMinable[f] = false; + } + } + + //let's check to see if we should renew ourselves! + if (resetTimer.isExpired() && isAlone()) { + updateAsteroid(); + resetTimer.set(random(1000) + RESET_INTERVAL); + } + //set up communication + FOREACH_FACE(f) { + byte sendData = (blinkRole << 4) + (isMinable[f] << 3); + setValueSentOnFace(sendData, f); + } +} + +void newAsteroid() { + //clear layout + FOREACH_FACE(f) { + oreLayout[f] = 0; + } +} + +bool oreCheck(byte type) { + bool isAvailable = false; + FOREACH_FACE(f) { + if (oreLayout[f] == type) { + isAvailable = true; + } + } + return (isAvailable); +} + +void removeOre(byte type) { + FOREACH_FACE(f) { + if (oreLayout[f] == type) { + oreLayout[f] = DIED_MINED; + } + } +} + +void updateAsteroid() { + byte oreCount = 0; + FOREACH_FACE(f) { + if (isOrePresentAtIndex(f)) { + oreCount++; + } + } + + if (oreCount == 0) { //add two + oreLayout[findEmptySpot()] = findNewColor(); + oreLayout[findEmptySpot()] = findNewColor(); + } else if (oreCount == 4) { //remove one + byte oreToRemove = findFullSpot(); + fadeColorIndex = oreLayout[oreToRemove]; + oreLayout[oreToRemove] = DIED_NATURAL; + } else {//add one + oreLayout[findEmptySpot()] = findNewColor(); + } +} + +byte findNewColor() { + byte newColor; + + bool usedColors[5] = {false, false, false, false, false}; + //run through each face and mark that color as used in the array + FOREACH_FACE(f) { + usedColors[oreLayout[f]] = true; + } + + //now do the shuffle search doodad + byte searchOrder[5] = {0, 1, 2, 3, 4}; + //shuffle array + for (byte i = 0; i < 10; i++) { + byte swapA = random(4); + byte swapB = random(4); + byte temp = searchOrder[swapA]; + searchOrder[swapA] = searchOrder[swapB]; + searchOrder[swapB] = temp; + } + + for (byte i = 0; i < 5; i++) { + if (usedColors[searchOrder[i]] == false) { //this color is not used + newColor = searchOrder[i]; + } + } + + return newColor; + //return 1; +} + +byte findEmptySpot() { + byte emptyFace; + byte searchOrder[6] = {0, 1, 2, 3, 4, 5}; + //shuffle array + for (byte i = 0; i < 10; i++) { + byte swapA = random(5); + byte swapB = random(5); + byte temp = searchOrder[swapA]; + searchOrder[swapA] = searchOrder[swapB]; + searchOrder[swapB] = temp; + } + + //now do the search in this order. The last 0 you find is the empty spot we are going to return + FOREACH_FACE(f) { + if ( !isOrePresentAtIndex(searchOrder[f]) ) { + emptyFace = searchOrder[f]; + } + } + + return emptyFace; +} + +byte findFullSpot() { + byte fullFace; + byte searchOrder[6] = {0, 1, 2, 3, 4, 5}; + //shuffle array + for (byte i = 0; i < 10; i++) { + byte swapA = random(5); + byte swapB = random(5); + byte temp = searchOrder[swapA]; + searchOrder[swapA] = searchOrder[swapB]; + searchOrder[swapB] = temp; + } + + //now do the search in this order. The last 0 you find is the empty spot we are going to return + FOREACH_FACE(f) { + if ( isOrePresentAtIndex(searchOrder[f]) ) { + fullFace = searchOrder[f]; + } + } + + return fullFace; +} + +void shipLoop() { + if (bChangeRole) { + blinkRole = ASTEROID; + newAsteroid(); + bChangeRole = false; + } + + //let's look for asteroids that want to trade + //only do it if we are allowed to + if (!missionComplete) { + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//a neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getBlinkRole(neighborData) == ASTEROID) {//an asteroid! + //so, what is my relationship to this asteroid? + if (isMining[f]) { //I'm already mining here + if (getAsteroidMinable(neighborData) == 0) {//he's done, so I'm done + isMining[f] = false; + } + } else {//I could be mining here if they'd let me + if (getAsteroidMinable(neighborData) == 1) {//ore is available, take it + oreCollected++; + isMining[f] = true; + } + } + } + } else {//no neighbor + isMining[f] = false; + } + } + + //finally, check to see if the mission is complete + if (oreCollected == missionCount) { + missionComplete = true; + displayMissionCompleteColor = oreTarget; + displayMissionCompleteIndex = 0; + resetTimer.set(0); + // we'll get our next ore target, so no need to do this next line +// oreTarget = NO_ORE_TARGET; //an ore that doesn't exist + //oh, also, is gameComplete? + if (missionCount == 1) { + gameComplete = true; + } + } + } + + //new mission time? + if (buttonDoubleClicked()) { + if (!gameComplete) { //only do this if the game isn't over + if (missionComplete) { + missionCount--; + newMission(); + } else { + newMission(); + } + } + + } + + //set up communication + FOREACH_FACE(f) { + byte sendData = (blinkRole << 4) + (isMining[f] << 3) + (oreTarget); + setValueSentOnFace(sendData, f); + } + +} + +bool isOrePresentAtIndex( byte i ) { + return (oreLayout[i] != DIED_NATURAL && oreLayout[i] != DIED_MINED); +} + +void newMission() { + missionComplete = false; + byte newOreTarget = oreTarget; + while (newOreTarget == oreTarget) { + newOreTarget = random(3) + 1; + } + oreTarget = newOreTarget; + + oreCollected = 0; +} + +void shipDisplay() { + if (gameComplete) { //big fancy celebration! + // Wipe animation + // TODO: Make this rotate one space at a time, it currently rotates 2 spaces and we have no idea why. Do you know why? + if (resetTimer.isExpired()) { // add a new face for the next color + + // every 80 ms + resetTimer.set(50); + + // increment index (0-11) + displayMissionCompleteIndex = (displayMissionCompleteIndex + 1) % ( 6 * 7 * 4); + + byte index = ((displayMissionCompleteIndex / 7) % 4) + 1; + setColorOnFace(oreColors[index], displayMissionCompleteIndex % 6); + } + + } + else if (missionComplete) { //small celebration + + // Wipe animation + // TODO: Make this rotate one space at a time, it currently rotates 2 spaces and we have no idea why. Do you know why? + if (resetTimer.isExpired()) { // add a new face for the next color + + // every 80 ms + resetTimer.set(50); + + // increment index (0-11) + displayMissionCompleteIndex = (displayMissionCompleteIndex + 1) % ( 6 * 7 ); + + + if (((displayMissionCompleteIndex / 7) % 2) == 0) { + setColorOnFace(oreColors[displayMissionCompleteColor], displayMissionCompleteIndex % 6); + } + else { + setColorOnFace(WHITE, displayMissionCompleteIndex % 6); + } + + } + + } + else {//just display ore + FOREACH_FACE(f) { + if (missionCount > f) { + if (oreCollected > f) { + // show the ore brightly acquired + setColorOnFace(oreColors[oreTarget], f); + } else { + // show the ore is needed here + if (!resetTimer.isExpired()) { + if (resetTimer.getRemaining() > 900 ) { + setColorOnFace(oreColors[oreTarget], f); + } + else if ( resetTimer.getRemaining() <= 900 && resetTimer.getRemaining() > 800 ) { + setColorOnFace(dim(oreColors[oreTarget], 128), f); + } + else if ( resetTimer.getRemaining() <= 800 && resetTimer.getRemaining() > 700 ) { + setColorOnFace(oreColors[oreTarget], f); + } + else if ( resetTimer.getRemaining() <= 700 && resetTimer.getRemaining() > 0 ) { + setColorOnFace(dim(oreColors[oreTarget], 128), f); + } + } + else { + resetTimer.set(1000); + } + } + } else { + setColorOnFace(OFF, f); + } + } + } +} + +void asteroidDisplay() { + //run through each face and check on the brightness + FOREACH_FACE(f) { + if (isOrePresentAtIndex(f)) { //should be ore here + if (oreBrightness[f] < 255) { //hey, this isn't full brightness yet + oreBrightness[f] += 5; + } + } else {//should not be ore here + if (oreBrightness[f] > 0) { //hey, this isn't off + oreBrightness[f] -= 5; + } + } + } + + FOREACH_FACE(f) { + Color displayColor = oreColors[oreLayout[f]]; + byte displayBrightness = oreBrightness[f]; + if (displayBrightness > 0) { //a fading ore thing + // did I die of natural causes + if (oreLayout[f] == DIED_NATURAL) { + displayColor = oreColors[fadeColorIndex]; + } + else if (oreLayout[f] == DIED_MINED) { + displayColor = WHITE; + } + } + setColorOnFace(dim(displayColor, displayBrightness), f); + } +} + +byte getBlinkRole(byte data) { + return (data >> 4);//the first two bits +} + +byte getShipTarget(byte data) { + return (data & 7);//the last three bits +} + +byte getShipMining(byte data) { + return ((data >> 3) & 1);//just the third bit +} + +byte getAsteroidMinable(byte data) { + return ((data >> 3) & 1);//just the third bit +} diff --git a/libraries/Examples03/examples/Berry/Berry.ino b/libraries/Examples03/examples/Berry/Berry.ino new file mode 100644 index 00000000..5ba3a856 --- /dev/null +++ b/libraries/Examples03/examples/Berry/Berry.ino @@ -0,0 +1,151 @@ +/* + * Berry + * by VV Studio + * at IndieCade East 2018 Game Jam + * Lead development by Jonathan Bobrow, Move38 Inc. + * original game by ViVi and Vanilla + * + * Rules: https://github.com/Move38/Berry/blob/master/README.md + * + * -------------------- + * Blinks by Move38 + * Brought to life via Kickstarter 2018 + * + * @madewithblinks + * www.move38.com + * -------------------- + */ + +Color colors[] = { BLUE, RED, YELLOW }; +byte currentColorIndex = 0; +byte faceIndex = 0; +byte faceStartIndex = 0; + +bool isWaiting = false; + +#define FACE_DURATION 60 +#define WAIT_DURATION 2000 + +Timer faceTimer; +Timer waitTimer; + +void setup() { + // put your setup code here, to run once: + +} + +void loop() { + // put your main code here, to run repeatedly: + + if ( buttonSingleClicked() ) { + + currentColorIndex++; + + if (currentColorIndex >= COUNT_OF(colors)) { + currentColorIndex = 0; + } + + } + + if ( waitTimer.isExpired() ) { + if ( faceTimer.isExpired() ) { + faceTimer.set( FACE_DURATION ); + faceIndex++; + + if (faceIndex >= 7) { + faceIndex = 0; + waitTimer.set( WAIT_DURATION ); + isWaiting = true; + + // shift the starting point + faceStartIndex++; + if (faceStartIndex >= 6) { + faceStartIndex = 0; + } + } + else { + isWaiting = false; + } + } + } + + // display color + setColor( colors[currentColorIndex] ); + + // show locked sides + if (isPositionLocked()) { + // show the state of locked animation on all faces + byte bri = 153 + (sin8_C((millis() / 6) % 255)*2)/5; + setColor(dim(colors[currentColorIndex], bri)); + } + + // show next color + if (!isWaiting) { + byte nextColorIndex = (currentColorIndex + 1) % 3; + byte face = (faceStartIndex + faceIndex - 1) % FACE_COUNT; + setFaceColor( face, colors[nextColorIndex] ); + } +} + +bool isPositionLocked() { + // based on the arrangement of neighbors, am I locked... + bool neighborPattern[6]; + bool lockedA[6] = {1, 0, 1, 0, 1, 0}; + bool lockedB[6] = {1, 0, 1, 0, 0, 0}; + + FOREACH_FACE(f) { + neighborPattern[f] = !isValueReceivedOnFaceExpired(f); + } + + // neighbors across from each other + for (byte i = 0; i < 3; i++) { + if (neighborPattern[i] && neighborPattern[i + 3]) { + return true; + } + } + + // special case lock patterns + if ( isThisPatternPresent(lockedA, neighborPattern)) { + return true; + } + if ( isThisPatternPresent(lockedB, neighborPattern)) { + return true; + } + + return false; +} + +// check to see if pattern is in the array +// return true if the pattern is in fact in the array +// pattern is always 6 bools +// source is always 12 bools (2 x 6 bools) +bool isThisPatternPresent( bool pat[], bool source[]) { + + // first double the source to be cyclical + bool source_double[12]; + + for (byte i = 0; i < 12; i++) { + source_double[i] = source[i % 6]; + } + + // then find the pattern + byte pat_index = 0; + + for (byte i = 0; i < 12; i++) { + if (source_double[i] == pat[pat_index]) { + // increment index + pat_index++; + + if ( pat_index == 6 ) { + // found the entire pattern + return true; + } + } + else { + // set index back to 0 + pat_index = 0; + } + } + + return false; +} diff --git a/libraries/Examples03/examples/BombBrigade/BombBrigade.ino b/libraries/Examples03/examples/BombBrigade/BombBrigade.ino new file mode 100644 index 00000000..b9593b65 --- /dev/null +++ b/libraries/Examples03/examples/BombBrigade/BombBrigade.ino @@ -0,0 +1,518 @@ +/* + BombBrigade + by Jeff Kowalski, Holly Gore, Collin Gallo + Lead development by Jonathan Bobrow + original game by Jeff Kowalski, Holly Gore, Collin Gallo + + Rules: https://github.com/Move38/BombBrigade/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +#define SHIELD_MAX_HEALTH 4 +#define SHIELD_MIN_HEALTH 0 + +#define MAX_CLICK_COUNT 10 + +#define SHOW_FACE_DURATION_MS 750 + +enum Modes { + SPREAD_READY, // tell others to get ready for the game to start + SPREAD_RESOLVE, // waiting for READY to be fully spread + + READY, // waiting for the game to start + BOMB, // center piece you are trying to defuse + SHIELD, // surrounding pieces to protect you from the bomb + SPARK, // let the shield know it's been damaged + SPARK_SPECIAL, // let the shield know it's been damaged fully + EXPLOSION // let the shield help our bomb in displaying an explosion +}; + +byte mode = READY; + +byte shieldHealth; + +Timer bombTickTimer; +Timer bombShowFaceTimer; + +bool bSpinning; +bool bExplode; +bool bExplodeIntoShield; +byte bombTickFace; +byte bombClickCount; + +byte bombCountDownCount; + +bool bShareExplosion; +byte shareExplosionFace; + +uint32_t timeOfReset = 0; + +bool bReset = false; + +void setup() { + + // Initialize all of our variables + resetAll(); + mode = READY; +} + +void loop() { + + /* + Button Actions + */ + if ( buttonPressed() ) { + + if ( mode == BOMB ) { + + // we were just pushed, give user feedback on which face they just landed on + bombShowFaceTimer.set( SHOW_FACE_DURATION_MS ); + + if (bombClickCount < MAX_CLICK_COUNT) { + + bombClickCount++; + + // chance of explode based on clickCount + if ( random(100) < bombClickCount * 5 ) { + bExplode = true; + bSpinning = false; + } + } + else { + // we've been clicked the maximum amount of times + // time to explode + bExplode = true; + bSpinning = false; + } + } // END BOMB + + else if (mode == SHIELD ) { + + // respond with a fun hello... don't effect game state + + } + + else if (mode == READY ) { + + // respond with a fun hello... don't effect game state + + } + } + + + if ( buttonDoubleClicked() ) { + + if ( mode == READY ) { + // become the bomb + mode = BOMB; + + // and start spinning + resetSpin(); + bSpinning = true; + } + + else if ( mode == BOMB ) { + // start the spinning again + if (bSpinning == false ) { // don't allow a reset mid-round + resetSpin(); + bSpinning = true; + } + } + + else if ( mode == SHIELD ) { + // respond with a fun hello... don't effect game state + } + } + + //long press business + if (hasWoken()) { + bReset = false; + } + + if (buttonLongPressed()) { + bReset = true; + } + + if (buttonReleased() && bReset) { + resetAll(); + bReset = false; + } + + /* + Game Logic + */ + + propogateReset(); + + switch (mode) { + + case READY: + // keep a look out for incoming signal saying we are a shield + // if we get that message, change to shield mode + FOREACH_FACE( f ) { + if ( !isValueReceivedOnFaceExpired( f ) ) { + byte neighbor = getLastValueReceivedOnFace( f ); + + if (neighbor == BOMB) { + mode = SHIELD; + } + } + } + break; + + case BOMB: + // if we are spinning, spin the speed expected + if ( bSpinning ) { + if (!bombShowFaceTimer.isExpired()) { + // hold on this face to show spark on this face + } + else { + + if ( bombTickTimer.isExpired() ) { + bombTickTimer.set( getTickRate( bombClickCount ) ); + bombTickFace++; + if ( bombTickFace >= FACE_COUNT ) { + bombTickFace = 0; + } + + // move our countdown for us + if ( bombCountDownCount != 0 ) { + bombCountDownCount--; + } + } + } + } + else { + // we're exploded + if ( !isValueReceivedOnFaceExpired( bombTickFace ) ) { + // if someone present where we exploded, we safe + bExplodeIntoShield = true; + } + } + // if we've sparked, check to see if we've hit a sheild, or nothing + // if nothing, then the spark becomes an explosion + // if we hit a shield, the spark shows direction + // if we have only one shield and we blow it away, we win + break; + + case SHIELD: + // if we see a spark lower our shield value by 1 + bShareExplosion = false; + + FOREACH_FACE( f ) { + if ( !isValueReceivedOnFaceExpired( f ) ) { + + byte neighbor = getLastValueReceivedOnFace( f ); + bool didNeighborJustChange = didValueOnFaceChange( f ); + + if ( neighbor == SPARK && didNeighborJustChange ) { + + // if our shield value is 0, become an explosion + if ( shieldHealth == SHIELD_MIN_HEALTH ) { + // explode this shield (maybe rainbow fun here) + + } else { + shieldHealth--; + } + } + + // if we see an explosion, react to it + if ( neighbor == EXPLOSION ) { + // explosion here + bShareExplosion = true; + shareExplosionFace = f; + } + } + } + // if we see an explosion, help animate that explosion + break; + + default: break; + } + + /* + Display + */ + switch (mode) { + + case SPREAD_READY: + case SPREAD_RESOLVE: + case READY: + // display readiness or waiting + // slowly progressing rainbow + { + // ping pong the hue between blue and yellow/orange and then back never green... + word hue = ((millis() - timeOfReset) / 40) % 350; + if (hue > 175) { + hue = (350 - hue + 127) % 255; // shift to start in the blue + setColor(makeColorHSB(hue, 255, 255)); + } + else { + hue = (hue + 127) % 255; // shift to start in the blue + setColor(makeColorHSB(hue, 255, 255)); + } + break; + } + case BOMB: + // display countdown + // or spinner + if ( bSpinning ) { + + if ( bombCountDownCount == 0 ) { + // when done with the countdown, no need to have the cool wipe effect :) + byte prevFace = (FACE_COUNT + bombTickFace - 1) % FACE_COUNT; + setFaceColor( prevFace, OFF); + setFaceColor( bombTickFace, ORANGE ); + + // flicker if we are paused and showing face + if (!bombShowFaceTimer.isExpired()) { + setFaceColor( bombTickFace, dim( YELLOW, random(255))); // flicker YELLOW + } + } + else { + switch (bombCountDownCount) { + case 5: setFaceColor( bombTickFace, makeColorRGB(255, 255, 150) ); break; // WHITE-YELLOW + case 4: setFaceColor( bombTickFace, makeColorRGB(255, 235, 75) ); break; // -- + case 3: setFaceColor( bombTickFace, makeColorRGB(255, 215, 30) ); break; // -- + case 2: setFaceColor( bombTickFace, makeColorRGB(255, 195, 0) ); break; // -- + case 1: setFaceColor( bombTickFace, makeColorRGB(255, 175, 0) ); break; // YELLOW-ORANGE + } + } + } + else { + if ( bExplode ) { + if ( bExplodeIntoShield ) { + FOREACH_FACE( f ) { + setFaceColor( f, GREEN); // show that we are safe + } + setFaceColor( bombTickFace, dim( ORANGE, random(255))); // flicker YELLOW + } + else { + FOREACH_FACE( f ) { + setFaceColor( f, makeColorHSB(random(25), 255, random(1) * 255)); // show that we are out... huge explosion + } + setFaceColor( bombTickFace, WHITE ); + } + // show white if safe + // red if out.... + } + } + // or spark + // or explosion + break; + + case SHIELD: + // display shield level + if ( shieldHealth == SHIELD_MIN_HEALTH ) { + // show explosion + + if ( isAlone() ) { + // rotating rainbow now that we have a token + setFaceColor( (millis() / 40) % 6, makeColorHSB( ( millis() / 5) % 255, 255, 255) ); // ROTATING RAINBOW + } + else { + // disco explosion + setColor(OFF); + setFaceColor( random(5), makeColorHSB( ( millis() / 5) % 255, 255, 255) ); // ROTATING RAINBOW + setFaceColor( random(5), makeColorHSB( ( (millis() + 127) / 5) % 255, 255, 255) ); // ROTATING RAINBOW + } + } + else { + setColor( getShieldColor( shieldHealth ) ); + + // or helping show internal explosion + if (bShareExplosion) { + shareExplosion( shareExplosionFace ); + } + + } + break; + + default: break; + } + + if (bReset) { + setColor(CYAN); + } + + /* + Communications (Sending) + */ + switch (mode) { + + case SPREAD_READY: + setValueSentOnAllFaces( mode ); + break; + + case SPREAD_RESOLVE: + setValueSentOnAllFaces( mode ); + break; + + case READY: + // broadcast ready state... nothing to do here + setValueSentOnAllFaces( mode ); + break; + + case BOMB: + // broadcast bomb (let shields know you are a bomb) + setValueSentOnAllFaces( mode ); + + // if sparked, send in a direction + if ( bExplode ) { + + if ( bExplodeIntoShield ) { + setValueSentOnFace( SPARK, bombTickFace ); + } + else { + // share the explosion with others + setValueSentOnAllFaces( EXPLOSION ); + } + } + // once spark received, return to bomb broadcast + break; + + case SHIELD: + setValueSentOnAllFaces( SHIELD ); + // confirm spark received + // share shield strength to the bomb + break; + + default: break; + } +} + +/* + Get ready to spin again +*/ +void resetSpin() { + // reset the spin variables + // set spin speed to slowest + // start the spin countdown + bSpinning = false; + bombClickCount = 0; + bExplode = false; + bExplodeIntoShield = false; + bombCountDownCount = FACE_COUNT; +} + +/* + Spread the good word, reset is called for :) +*/ + +void propogateReset() { + if (mode == SPREAD_READY) {//check if all of my neighbors know we are propogating + + mode = SPREAD_RESOLVE;//assume I can resolve as long as it all works out + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + + if (neighborData == SPREAD_READY || neighborData == SPREAD_RESOLVE) { + //this neighbor is spreading, which I'm cool with + } else {//This neighbor hasn't gotten the message yet + mode = SPREAD_READY;//not ready to resolve + } + } + } + } else if (mode == SPREAD_RESOLVE) { + mode = READY;//assume I can become ready as long as it all works out + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (neighborData == SPREAD_READY) {//this neighbor is still informing other people + mode = SPREAD_RESOLVE;//not ready to go to READY state + } + } + } + } else {//any other mode, which is not part of the spread (INERT) + FOREACH_FACE( f ) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (neighborData == SPREAD_READY) { + resetAll(); + } + } + } + } +} + +/* + Reset all of our variables for a new game +*/ +void resetAll() { + mode = SPREAD_READY; + shieldHealth = SHIELD_MAX_HEALTH; + bSpinning = false; + bombTickFace = 0; + bombClickCount = 0; + bExplode = false; + bExplodeIntoShield = false; + bombCountDownCount = FACE_COUNT; + bShareExplosion = false; + timeOfReset = millis(); +} + +/* + Return a frequency at which the bomb should spin + Fine tune this to get more an more exciting + The optimal tuning does not seem to be linear +*/ +byte getTickRate(byte clickCount) { + + byte tickRate = 200; + + switch (clickCount) { + case 0: tickRate = 120; break; + case 1: tickRate = 100; break; + case 2: tickRate = 80; break; + case 3: tickRate = 60; break; + case 4: tickRate = 50; break; + case 5: tickRate = 45; break; + case 6: tickRate = 40; break; + case 7: tickRate = 35; break; + case 8: tickRate = 30; break; + case 9: tickRate = 25; break; + case 10: tickRate = 20; break; + } + + return tickRate; +} + +Color getShieldColor( byte health ) { + + Color shieldColor = OFF; // default + + switch ( health ) { + case 0: shieldColor = WHITE; break; // WHITE - TODO: replace w/ EXPLOSION + case 1: shieldColor = makeColorHSB( 0, 255, 255); break; // RED + case 2: shieldColor = makeColorHSB( 25, 255, 255); break; // ORANGE + case 3: shieldColor = makeColorHSB( 50, 255, 255); break; // YELLOW + case 4: shieldColor = makeColorHSB( 75, 255, 255); break; // GREEN + } + + return shieldColor; +} + +/* + Share explosion +*/ +void shareExplosion( byte face ) { + + setFaceColor( shareExplosionFace, makeColorHSB(random(25), 255, 255 - ((millis() / 2) % 255) ) ); + + // use the neighboring faces as well + byte prevFace = (FACE_COUNT + face - 1) % FACE_COUNT; + byte nextFace = (face + 1) % FACE_COUNT; + + setFaceColor( prevFace, makeColorHSB(random(25), 255, 255 - (((millis() - 40) / 2) % 255) ) ); + setFaceColor( nextFace, makeColorHSB(random(25), 255, 255 - (((millis() - 40) / 2) % 255) ) ); + +} diff --git a/libraries/Examples03/examples/FlicFlop/FlicFlop.ino b/libraries/Examples03/examples/FlicFlop/FlicFlop.ino new file mode 100644 index 00000000..c65f84e1 --- /dev/null +++ b/libraries/Examples03/examples/FlicFlop/FlicFlop.ino @@ -0,0 +1,298 @@ +/* + FlicFlop + by Move38, Inc. 2019 + Lead development by Dan King + original game by Nick Bentley, Jonathan Bobrow, Dan King + + Rules: https://github.com/Move38/Astro/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +enum gameStates {FLICKER_UNSCORED, FLICKER_SCORED, FLICKER_DISPLAY, FLOPPER}; +byte gameState = FLICKER_UNSCORED; +bool longPressCheck = false; + +#define TEAM_COUNT 3 +byte scoringTeam = 0; +byte signalTeam = 0; + +enum celebrationStates {INERT, CELEBRATE, TRANSITION}; +byte celebrationState = INERT; + +Timer flopTimer; +#define FLOP_INTERVAL 2000 + + +Timer animTimer; +#define ANIMATION_INTERVAL 200 +byte animationInterval = ANIMATION_INTERVAL; + +#define ANIMATION_CELEBRATE_INTERVAL 20 +Timer celebrationTimer; +#define CELEBRATE_TIME 2000 + +byte spinFace = 0; +byte teamHues[4] = {0, 45, 125, 230}; + +void setup() { + // put your setup code here, to run once: + +} + +void loop() { + + if (hasWoken()) { + longPressCheck = false; + } + + // put your main code here, to run repeatedly: + if (gameState == FLOPPER) { + flopperLoop(); + flopperDisplay(); + } else { + flickerLoop(); + flickerDisplay(); + } + + //animate the spin face + spinFaceAnimator(); + + //set up communication + byte sendData = (signalTeam << 4) + (scoringTeam << 2) + (celebrationState); + setValueSentOnAllFaces(sendData); + + if (buttonSingleClicked()) { + beginCelebration(); + } + + celebrationLoop(); + + if(longPressCheck) { + if(isAlone()){ + setColor(WHITE); + setColorOnFace(OFF, spinFace); + } + } + + //dump button presses + buttonDoubleClicked(); + buttonLongPressed(); +} + +void flopperLoop() { + + if (flopTimer.isExpired()) { + signalTeam = (signalTeam % TEAM_COUNT) + 1; + flopTimer.set(FLOP_INTERVAL); + } + + //change to flicker? + if (buttonLongPressed()) { + if (isAlone()) { + //set the transition boolean to true + longPressCheck = true; + } + } + + if (buttonReleased()) { + if (longPressCheck) { + longPressCheck = false;//regardless of outcome, this needs to be false + if (isAlone()) { + scoringTeam = 0; + signalTeam = 0; + gameState = FLICKER_UNSCORED; + } + } + } + +} + +void flickerLoop() { + + if (gameState == FLICKER_UNSCORED) { + //listen for neighbors with signal teams + //become that signal team + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + + if (getSignalTeam(neighborData) != 0) {//this guy is signalling + + //become scored + gameState = FLICKER_SCORED; + scoringTeam = getSignalTeam(neighborData); + signalTeam = scoringTeam; + + //kick off a celebration + beginCelebration(); + } + } + } + + //also, listen for long press to become flopper + if (buttonLongPressed()) { + if (isAlone()) { + //set the transition boolean to true + longPressCheck = true; + } + } + + if (buttonReleased()) { + if (longPressCheck) { + longPressCheck = false;//regardless of outcome, this needs to be false + if (isAlone()) { + gameState = FLOPPER; + signalTeam = 1; + scoringTeam = 0; + } + } + } + + } else if (gameState == FLICKER_SCORED) { + //listen for signals to change your signal + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (getSignalTeam(neighborData) == signalTeam + 1) { + signalTeam = getSignalTeam(neighborData); + } else if (signalTeam == 3 && getSignalTeam(neighborData) == 1) { + signalTeam = 1; + } + } + + if (isAlone()) { + gameState = FLICKER_DISPLAY; + signalTeam = 0; + } + } + + } else if (gameState == FLICKER_DISPLAY) { + //listen for things with signal teams to rejoin the game + //listen for things with no scoring team to go back to unscored + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + + if (getSignalTeam(neighborData) != 0) { //I have rejoined the game because this neighbor has a signal team + gameState = FLICKER_SCORED; + signalTeam = getSignalTeam(neighborData); + + } else if (getScoringTeam(neighborData) == 0) { //this neighbor has no signal team, and also has no scoring team. Unscored! + gameState = FLICKER_UNSCORED; + scoringTeam = 0; + + } + } + } + + //we can also be double-clicked back to unscored + if (buttonDoubleClicked()) { + gameState = FLICKER_UNSCORED; + scoringTeam = 0; + } + } + +} + +void beginCelebration() { + animTimer.set(0); + celebrationState = CELEBRATE; + animationInterval = ANIMATION_CELEBRATE_INTERVAL; + celebrationTimer.set(CELEBRATE_TIME); +} + +void celebrationLoop() { + //here we check for the INERT/CELEBRATE/TRANSITION stuff + if (celebrationState == INERT) { + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getCelebrationState(getLastValueReceivedOnFace(f)) == CELEBRATE) { + beginCelebration(); + } + } + } + } else if (celebrationState == CELEBRATE) { + //only move on to TRANSITION if no neighbors are still INERT + celebrationState = TRANSITION; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getCelebrationState(getLastValueReceivedOnFace(f)) == INERT) { + celebrationState = CELEBRATE;//revert the change from above + } + } + } + } else if (celebrationState == TRANSITION) { + //only move to INERT if no neighbors are in CELEBRATE + celebrationState = INERT; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getCelebrationState(getLastValueReceivedOnFace(f)) == CELEBRATE) { + celebrationState = TRANSITION;//revert the change from above + } + } + } + } + + //set the animation interval correctly based on the celebration timer + if (celebrationTimer.isExpired()) { + animationInterval = ANIMATION_INTERVAL; + } else { + animationInterval = map(CELEBRATE_TIME - celebrationTimer.getRemaining(), 0, CELEBRATE_TIME, ANIMATION_CELEBRATE_INTERVAL, ANIMATION_INTERVAL); + } +} + +///////////////// +//DISPLAY LOOPS// +///////////////// + +void spinFaceAnimator() { + if (animTimer.isExpired()) { + spinFace = (spinFace + 1) % 6; + animTimer.set(animationInterval); + } +} + +void flopperDisplay() { + setColor(makeColorHSB(teamHues[signalTeam], 255, 255)); + setColorOnFace(OFF, spinFace); +} + +void flickerDisplay() { + setColor(OFF); + + if (gameState == FLICKER_UNSCORED) { + setColorOnFace(WHITE, spinFace); + } else if (gameState == FLICKER_SCORED) { + setColorOnFace(makeColorHSB(teamHues[scoringTeam], 255, 255), spinFace); + setColorOnFace(makeColorHSB(teamHues[signalTeam], 255, 255), (spinFace + 2) % 6); + setColorOnFace(makeColorHSB(teamHues[signalTeam], 255, 255), (spinFace + 3) % 6); + setColorOnFace(makeColorHSB(teamHues[signalTeam], 255, 255), (spinFace + 4) % 6); + } else if (gameState == FLICKER_DISPLAY) { + setColorOnFace(makeColorHSB(teamHues[scoringTeam], 255, 255), spinFace); + setColorOnFace(makeColorHSB(teamHues[scoringTeam], 255, 255), (spinFace + 2) % 6); + setColorOnFace(makeColorHSB(teamHues[scoringTeam], 255, 255), (spinFace + 4) % 6); + } +} + +///////////////////////// +//CONVENIENCE FUNCTIONS// +///////////////////////// + +byte getSignalTeam(byte data) { + return (data >> 4);//first and second bit +} + +byte getScoringTeam(byte data) { + return ((data >> 2) & 3);//third and fourth bit +} + +byte getCelebrationState(byte data) { + return (data & 3);//5th and 6th bit +} diff --git a/libraries/Examples03/examples/Fracture/Fracture.ino b/libraries/Examples03/examples/Fracture/Fracture.ino new file mode 100644 index 00000000..27d6688a --- /dev/null +++ b/libraries/Examples03/examples/Fracture/Fracture.ino @@ -0,0 +1,171 @@ +/* + * Fracture + * by Move38, Inc. 2019 + * Lead development by Jonathan Bobrow, Daniel King + * original game by Celia Pearce, Em Lazer-Walker, Jonathan Bobrow, Joshua Sloane + * + * Rules: https://github.com/Move38/Fracture/blob/master/README.md + * + * -------------------- + * Blinks by Move38 + * Brought to life via Kickstarter 2018 + * + * @madewithblinks + * www.move38.com + * -------------------- + */ + +#define HAPPY_FLASH_DURATION 500 +#define EDGE_FADE_DURAION 500 +#define SPARKLE_OFFSET 80 +#define SPARKLE_DURATION 800 +#define SPARKLE_CYCLE_DURATION 1600 + +Color displayColor; + +//Color teamColors[] = {RED, BLUE, YELLOW, GREEN}; +byte teamHues[6] = {22, 49, 82, 99, 160, 200}; + +byte teamIndex = 0; + +Timer happyFlashTimer; +bool happyFlashOn; + +byte sparkleOffset[6] = {0, 3, 5, 1, 4, 2}; + +Timer edgeTimer[6]; +bool edgeAcquired; + +bool hasRecentlySeenNeighbor[6]; + +void setup() { +} + +void loop() { + + // change team if triple clicked + if (buttonDoubleClicked()) { + teamIndex++; + if (teamIndex >= COUNT_OF(teamHues)) { + teamIndex = 0; + } + } + + byte numNeighbors = 0; + bool noNeighborsOfSameColor = true; + + // look at neighbors + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + + if (hasRecentlySeenNeighbor[f] == false) { + edgeAcquired = true; + edgeTimer[f].set(EDGE_FADE_DURAION); + } + hasRecentlySeenNeighbor[f] = true; + + numNeighbors++; + + // if their color is the same as mine... not happy + if (getLastValueReceivedOnFace(f) == teamIndex) { + noNeighborsOfSameColor = false; + } + } + else { + if (hasRecentlySeenNeighbor[f] == true) { + edgeAcquired = false; + edgeTimer[f].set(EDGE_FADE_DURAION); + } + hasRecentlySeenNeighbor[f] = false; + } + } + + bool isHappy = false; + + // if I have two neighbors or more and my neighbors are not my color i'm happy + if (numNeighbors >= 2 && noNeighborsOfSameColor) { + isHappy = true; + } + + // if I'm happy + if (isHappy) { + displayHappy(); + } + else { + displayNotHappy(); + } + + // display fracture animation or mend animation + FOREACH_FACE(f) { + if (!edgeTimer[f].isExpired()) { + if (edgeAcquired) { + // if we just gained a neighbor saturate from white + byte sat = 255 - (255 * edgeTimer[f].getRemaining() ) / EDGE_FADE_DURAION; + setColorOnFace(makeColorHSB(teamHues[teamIndex], sat, 255), f); + } + else { + // if we just lost a neighbor fade up from dark + byte bri = 255 - (255 * edgeTimer[f].getRemaining() ) / EDGE_FADE_DURAION; + setColorOnFace(makeColorHSB(teamHues[teamIndex], 255, bri), f); + } + } + } + + setValueSentOnAllFaces(teamIndex); +} + +void displayHappy() { + + // have the color on the Blink raise and lower to feel more alive + byte bri = 185 + sin8_C( (millis() / 14) % 255) * 70 / 255; // oscillate between values 185and 255 + setColor(dim(getColorForTeam(teamIndex), bri)); + + // lets do a celebration on each face in an order + word delta = millis() % SPARKLE_CYCLE_DURATION; // 2 second cycle + + if (delta > SPARKLE_DURATION) { + delta = SPARKLE_DURATION; + } + + FOREACH_FACE(f) { + + // if the face has started it's glow + uint16_t sparkleStart = sparkleOffset[f] * SPARKLE_OFFSET; + uint16_t sparkleEnd = sparkleStart + SPARKLE_DURATION - (6 * SPARKLE_OFFSET); + + if ( delta > sparkleStart ) { + // minimum of 125, maximum of 255 + word phaseShift = 60 * f; + byte amplitude = 55; + byte midline = 185; + byte rate = 6; + byte lowBri = midline + (amplitude * sin8_C( (phaseShift + millis() / rate) % 255) / 100); + byte brightness; + byte saturation; + + if ( delta < sparkleEnd ) { + brightness = map(delta, sparkleStart, sparkleStart + SPARKLE_DURATION - (6 * SPARKLE_OFFSET), 255, lowBri); + saturation = map(delta, sparkleStart, sparkleStart + SPARKLE_DURATION - (6 * SPARKLE_OFFSET), 0, 255); + } + else { + //brightness = lowBri; + saturation = 255; + } + + Color faceColor = makeColorHSB(teamHues[teamIndex], saturation, 255); + setColorOnFace(faceColor, f); + } + } + +} + +void displayNotHappy() { + // have the color on the Blink raise and lower to feel more alive + byte bri = 185 + sin8_C( (millis() / 14) % 255) * 70 / 255; // oscillate between values 185and 255 + setColor(dim(getColorForTeam(teamIndex), bri)); +} + + +Color getColorForTeam(byte t) { + return makeColorHSB(teamHues[t], 255, 255); +} diff --git a/libraries/Examples03/examples/Honey/Honey.ino b/libraries/Examples03/examples/Honey/Honey.ino new file mode 100644 index 00000000..8beebc02 --- /dev/null +++ b/libraries/Examples03/examples/Honey/Honey.ino @@ -0,0 +1,566 @@ +/* + Honey + by Move38, Inc. 2019 + Lead development by Dan King + original game by Junege Hong, Dan King, Jonathan Bobrow + + Rules: https://github.com/Move38/Honey/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +enum blinkRoles {FLOWER, WORKER, BROOD, QUEEN}; +byte blinkRole = FLOWER; + +byte blinkNeighbors; +bool shouldEvolve = false; + +////RESOURCE VARIABLES +byte resourceCollected = 0; +#define RESOURCE_STACK 6 +Timer resourceTimer; +#define RESOURCE_TICK_INTERVAL 83 + +bool isFull; +long fullStartTime = 0; +bool isLagging; +Timer lagTimer; +#define RESOURCE_FULL_LAG 1500 +#define FULL_PULSE_INTERVAL 1000 + +Timer evolveTimer; +#define EVOLVE_INTERVAL 1000 + +bool isExporting = false; +byte exportFace = 0; +Timer exportTimer; +#define EXPORT_INTERVAL 1000 + +byte importHold = 0; +Timer importTimer; +Timer fadeUpTimer; + +////COMMUNICATION VARIABLES +enum signalTypes {INERT, SUPPLY, DEMAND, TRADING}; +byte tradingSignals[6]; +bool isTrading = false; + +enum celebrationStates {NOMINAL, HOORAY, RESOLVING}; +byte celebrationState = NOMINAL; + +////DISPLAY VARIABLES +byte hueByRole[4] = {78, 43, 22, 200}; +byte saturationReduction = 10; +#define BEE_SATURATION 128 +#define FULL_SATURATION 170 +#define RESOURCE_DIM 100 + +byte spinPosition = 0; +byte spinSteps = 7; +Timer spinTimer; +#define SPIN_INTERVAL 200 +bool spinClockwise = true; + +bool isCelebrating = false; +Timer celebrationTimer; +#define CELEBRATION_INTERVAL 4000 + +bool bPress = false; + +///////// +//LOOPS// +///////// + +void setup() { + // put your setup code here, to run once: +} + +void loop() { + //change role when ready? + + if (hasWoken()) { + bPress = false; + } + + if (buttonLongPressed()) { + bPress = true; + } + + if (buttonReleased() && bPress) { + toggleShouldEvolve(); + bPress = false; + } + + //check for flower reversion + if (buttonDoubleClicked()) { + if (isAlone()) { + buttonPressed(); // NOTE: This is sloppy, it allows the flower to dismiss the first pressed event caused at the same time as the double click. + resourceCollected = 0; + isFull = false; + isLagging = false; + evolveTimer.set(1000); + shouldEvolve = false; + blinkRole = FLOWER; + } + } + + isTrading = false; + //make sure isTrading is accurate + FOREACH_FACE(f) { + if (tradingSignals[f] == TRADING) { + isTrading = true; + } + } + + //run loops + switch (blinkRole) { + case FLOWER: + flowerLoop(); + break; + case WORKER: + workerLoop(); + break; + case BROOD: + broodLoop(); + break; + case QUEEN: + queenLoop(); + break; + } + + //resolve celebration state + if (celebrationState == NOMINAL) { + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (getNeighborCelebrationState(getLastValueReceivedOnFace(f)) == HOORAY) {//hooray is being propogated + isCelebrating = true; + celebrationTimer.set(CELEBRATION_INTERVAL); + celebrationState = HOORAY; + if (blinkRole != QUEEN) { + resourceCollected = 0; + } + } + } + } + } else if (celebrationState == HOORAY) { + celebrationState = RESOLVING;//default to this, then change below if we find a NOMINAL + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (getNeighborCelebrationState(getLastValueReceivedOnFace(f)) == NOMINAL) {//this neighbor still needs to celebrate + celebrationState = HOORAY; + } + } + } + } else if (celebrationState == RESOLVING) { + celebrationState = NOMINAL; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (getNeighborCelebrationState(getLastValueReceivedOnFace(f)) == HOORAY) {//this neighbor still needs to resolve + celebrationState = RESOLVING; + } + } + } + } + + if (celebrationTimer.isExpired()) { + isCelebrating = false; + } + + //set up communication + FOREACH_FACE(f) { + byte sendData = (blinkRole << 4) + (tradingSignals[f] << 2) + (celebrationState); + setValueSentOnFace(sendData, f); + } + + hiveDisplay(); + + // dump button presses + buttonPressed(); +} + +void toggleShouldEvolve() { + if (shouldEvolve) { + shouldEvolve = false; + } else { + shouldEvolve = true; + } +} + +void flowerLoop() { + if (isFull) { + fullLoop(WORKER); + } else { + if (isExporting) { + if (exportTimer.isExpired()) { + isExporting = false; + } + } else { + + //auto-increment + if (isTouching(WORKER) > 0) { + autoResource(RESOURCE_TICK_INTERVAL / isTouching(WORKER)); //gets faster with more workers + } + + //increment in large chunks if clicked + if (buttonPressed()) { + resourceCollected += RESOURCE_STACK; + } + + //check fullness + if (resourceCollected >= RESOURCE_STACK * 6) { + isFull = true; + fullStartTime = millis(); + isLagging = true; + lagTimer.set(RESOURCE_FULL_LAG); + } + } + } +} + +void workerLoop() { + if (isFull) { + fullLoop(BROOD); + } else { + incompleteLoop(FLOWER); + } +} + +void broodLoop() { + if (isFull) { + fullLoop(QUEEN); + } else { + incompleteLoop(WORKER); + } +} + +void queenLoop() { + if (isFull) { + //we do the lagging animation as usual here, then check if we are done + if (lagTimer.isExpired()) { + isCelebrating = true; + celebrationTimer.set(CELEBRATION_INTERVAL); + celebrationState = HOORAY; + isFull = false; + resourceCollected = 0; + } + } else { + incompleteLoop(BROOD); + } +} + +void fullLoop(byte primaryExportRole) { + if (shouldEvolve) {//we are in evolve mode + blinkRole = primaryExportRole; + resourceCollected = 0; + isFull = false; + isLagging = false; + evolveTimer.set(1000); + shouldEvolve = false; + } else {//we are in transfer mode + if (isLagging) {//still lagging. check if we're done + if (lagTimer.isExpired()) { + isLagging = false; + } + } else {//done lagging, DO EXPORT + FOREACH_FACE(f) { + if (isValueReceivedOnFaceExpired(f)) { //empty face + tradingSignals[f] = INERT; + } else {//an actual neighbor + byte neighborData = getLastValueReceivedOnFace(f); + byte tradeFace = 6; + switch (tradingSignals[f]) { + case INERT://look to see if my neighbor is someone I can offer my resource to + if (isTouching(primaryExportRole) > 0) {//if I'm touching a my primary export type, always offer to that + if (getNeighborRole(neighborData) == primaryExportRole) { + tradingSignals[f] = SUPPLY; + } else {//don't accidentally offer stuff to the wrong people + tradingSignals[f] = INERT; + } + } + break; + case SUPPLY://look for neighbors who have offered to take my resource. if I am trading elsewhere, don't do this + if (!isTrading) {//no trade signals out, so we can create one + if (getNeighborTradingSignal(neighborData) == DEMAND) { + tradingSignals[f] = TRADING; + isTrading = true; + } + } + break; + case TRADING://so now I look for my trading neighbor to go to TRADING, so I can complete the trade and go to INERT + if (getNeighborTradingSignal(neighborData) == TRADING) {//alright, a trade is happening + tradingSignals[f] = INERT; + resourceCollected = 0; + isFull = false; + isExporting = true; + exportTimer.set(EXPORT_INTERVAL); + exportFace = f; + } else if (getNeighborTradingSignal(neighborData) == INERT) {//huh, some sort of interruption + tradingSignals[f] = INERT; + isTrading = false; + } + break; + } + } + } + } + } +} + +void incompleteLoop(byte singleStackImportRole) { + if (isExporting) { + if (exportTimer.isExpired()) { + isExporting = false; + } + } else { + //resolve imports + if (importTimer.isExpired()) { + if (importHold > 0) { + if (fadeUpTimer.isExpired()) { + fadeUpTimer.set(50); + resourceCollected++; + importHold--; + } + } + } + //do more imports + FOREACH_FACE(f) { + if (isValueReceivedOnFaceExpired(f)) {//just making sure any unoccupied faces go INERT + tradingSignals[f] = INERT; + } else {//ok, so this face is occupied. Let's do some work + byte neighborData = getLastValueReceivedOnFace(f); + switch (tradingSignals[f]) { + case INERT:// Look for a neighbor who might cause me to go into DEMAND + //if I have a compatible neighbor in SUPPLY mode, we go to DEMAND + if (getNeighborTradingSignal(neighborData) == SUPPLY && getNeighborRole(neighborData) == singleStackImportRole) { + tradingSignals[f] = DEMAND; + } + break; + case DEMAND:// Look for a neighbor who could send me back to INERT or into TRADING + //if I have demanded from a neighbor, and it has reacted, I react back + if (getNeighborTradingSignal(neighborData) == TRADING) {//ooh, a trade is offered + tradingSignals[f] = TRADING; + } else if (getNeighborTradingSignal(neighborData) == INERT) {//oh, they have gone inert. Bummer + tradingSignals[f] = INERT; + } + break; + case TRADING:// Look for a neighbor that will send me back to INERT and complete a trade + //if that neighbor has gone inert, then the trade is COMPLETE + if (getNeighborTradingSignal(neighborData) == INERT) { + tradingSignals[f] = INERT; + importTimer.set(EXPORT_INTERVAL); + importHold += RESOURCE_STACK; + } + break; + } + }//end found face + }//end face loop + //now that we've potentially imported, do a fullness check + if (resourceCollected >= RESOURCE_STACK * 6) { + isFull = true; + fullStartTime = millis(); + isLagging = true; + if (blinkRole == QUEEN) { + lagTimer.set(RESOURCE_FULL_LAG * 2); + } else { + lagTimer.set(RESOURCE_FULL_LAG); + } + + } + } +} + +void autoResource(int interval) { + if (resourceTimer.isExpired()) { + //tick the resource + resourceCollected++; + //reset the timer + resourceTimer.set(interval); + } +} + +byte isTouching(byte roleType) { + byte touchCheck = 0; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //something here + if (getNeighborRole(getLastValueReceivedOnFace(f)) == roleType) { + touchCheck++; + } + } + } + return touchCheck; +} + +byte getNeighborRole(byte data) { + return (data >> 4);//1st and 2nd bits +} + +byte getNeighborTradingSignal(byte data) { + return ((data >> 2) & 3);//3rd and 4th bits +} + +byte getNeighborCelebrationState(byte data) { + return (data & 3);//5th and 6th bits +} + +/////////// +//DISPLAY// +/////////// + +void hiveDisplay() { + + byte displayHue = hueByRole[blinkRole]; + if (isFull) { + //just kinda waiting around + byte displaySaturation; + long animationPosition = (millis() - fullStartTime) % FULL_PULSE_INTERVAL;//we are this far into the pulse animation + //are we in the first half or the second half? + if (animationPosition < FULL_PULSE_INTERVAL / 2) {//white >> color + displaySaturation = map_m(animationPosition, 0, FULL_PULSE_INTERVAL / 2, 255, FULL_SATURATION); + } else {//color >> white + displaySaturation = map_m(animationPosition - FULL_PULSE_INTERVAL / 2, 0, FULL_PULSE_INTERVAL / 2, FULL_SATURATION, 255); + } + setColor(makeColorHSB(displayHue, displaySaturation, 255)); + } else { + if (isExporting) {//doing the export thing + FOREACH_FACE(f) { + byte brightnessVal = getFaceValueForSendAnimation(exportFace, f, EXPORT_INTERVAL, exportTimer.getRemaining(), RESOURCE_DIM, 255); + byte saturationVal = getFaceValueForSendAnimation(exportFace, f, EXPORT_INTERVAL, exportTimer.getRemaining(), 255, FULL_SATURATION); + setColorOnFace(makeColorHSB(hueByRole[blinkRole], saturationVal, brightnessVal), f); + } + } else if (!evolveTimer.isExpired()) {//I'm evolving. This display takes precedence! + byte flashState = map_m(evolveTimer.getRemaining(), 0, EVOLVE_INTERVAL, 255, 0); + byte dimState = map_m(evolveTimer.getRemaining(), 0, EVOLVE_INTERVAL, RESOURCE_DIM, 255); + setColor(makeColorHSB(displayHue, flashState, dimState)); + } else {//not evolving, not exporting, do normal display + byte fullFaces = resourceCollected / RESOURCE_STACK;//returns 0-6 + byte displayBrightness = 0; + + FOREACH_FACE(f) { + if (f < fullFaces) {//this face is definitely full + displayBrightness = 255; + } else if (f == fullFaces) {//this is the one being worked on now + displayBrightness = map_m(resourceCollected % RESOURCE_STACK, 0, RESOURCE_STACK, RESOURCE_DIM, 255); + //displaySaturation = 255 - ((resourceCollected % RESOURCE_STACK) * saturationReduction); + } else {//this is empty + displayBrightness = RESOURCE_DIM; + } + + setColorOnFace(makeColorHSB(displayHue, 255, displayBrightness), f); + } + } + } + + //now that we have displayed the state of the world, display the little bee + if (isCelebrating) {//celebration bee spin + //do the special queen celebration + if (blinkRole == QUEEN) { + byte brightnessVal = map_m(celebrationTimer.getRemaining(), CELEBRATION_INTERVAL, 0, RESOURCE_DIM, 255); + byte saturationVal = map_m(celebrationTimer.getRemaining(), CELEBRATION_INTERVAL, 0, 255, 0); + setColor(makeColorHSB(hueByRole[QUEEN], brightnessVal, saturationVal)); + } + + if (spinTimer.isExpired()) { + + if (spinClockwise) { + spinPosition = (spinPosition + 1) % 6; + } else { + spinPosition = (spinPosition + 5) % 6; + } + + long celebrationSpinInterval = map_m(celebrationTimer.getRemaining(), CELEBRATION_INTERVAL, 0, SPIN_INTERVAL / 5, SPIN_INTERVAL); + celebrationSpinInterval = (celebrationSpinInterval * celebrationSpinInterval) / SPIN_INTERVAL; + spinTimer.set(celebrationSpinInterval); + } + + Color beeColor = makeColorHSB(hueByRole[QUEEN], 255, 255); + setColorOnFace(beeColor, spinPosition); + + } else if (!isFull && !isExporting && blinkRole != QUEEN) { //normal bee spin, absent on queens + if (spinTimer.isExpired()) { + + if (spinClockwise) { + spinPosition = (spinPosition + 1) % 6; + } else { + spinPosition = (spinPosition + 5) % 6; + } + + spinSteps --; + + if (spinSteps == 0) { //should I sit for a second and think about life? + spinTimer.set(SPIN_INTERVAL * 3); + spinSteps = random(11) + 9; + //so now that I'm sitting here, should I change my direction? + if (random(2) > 0) { + spinClockwise = !spinClockwise; + } + } else { + spinTimer.set(SPIN_INTERVAL); + } + } + + //display the little bee + Color beeColor; + if (shouldEvolve) { + beeColor = makeColorHSB(hueByRole[blinkRole + 1], 255, 255); + } else { + beeColor = makeColorHSB(hueByRole[blinkRole], BEE_SATURATION, 255); + } + setColorOnFace(beeColor, spinPosition); + + } + + if (bPress) { + setColor(WHITE); + } +} + +byte getFaceValueForSendAnimation(byte actionFace, byte f, long duration, long progress, byte low, byte high) { + long offset = duration / 6; + byte dist = (actionFace + 6 - f) % 6; + byte phase; + switch (dist) { + case 0: phase = 0; break; + case 1: phase = 1; break; + case 2: phase = 2; break; + case 3: phase = 3; break; + case 4: phase = 2; break; + case 5: phase = 1; break; + } + long t0 = (phase * offset) + (duration - (offset * 3)); + long t1 = (phase * offset); + byte value = map_m(progress, t0, t1, high, low); + if (progress > t0) value = high; + if (progress < t1) value = low; + return value; +} + +/////////////// +//CONVENIENCE// +/////////////// + +byte nextClockwise (byte face) { + if (face == 5) { + return 0; + } else { + return face + 1; + } +} + +byte nextCounterclockwise (byte face) { + if (face == 0) { + return 5; + } else { + return face - 1; + } +} + +long map_m(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} diff --git a/libraries/Examples03/examples/Mortals/Mortals.ino b/libraries/Examples03/examples/Mortals/Mortals.ino new file mode 100644 index 00000000..54b08900 --- /dev/null +++ b/libraries/Examples03/examples/Mortals/Mortals.ino @@ -0,0 +1,537 @@ + /* + * Mortals + * by Move38, Inc. 2019 + * Lead development by Jonathan Bobrow + * original game by Nick Bentley, Jonathan Bobrow, Justin Ha + * + * Rules: https://github.com/Move38/Mortals/blob/master/README.md + * + * -------------------- + * Blinks by Move38 + * Brought to life via Kickstarter 2018 + * + * @madewithblinks + * www.move38.com + * -------------------- + */ + +#define ATTACK_VALUE 5 // Amount of health you loose when attacked. +#define ATTACK_DURRATION_MS 500 // Time between when we see first new neighbor and when we stop attacking. +#define HEALTH_STEP_TIME_MS 1000 // Health decremented by 1 unit this often + +#define INJURED_DURRATION_MS 750 // How long we stay injured after we are attacked. Prevents multiple hits on the same attack cycle. +#define INJURY_DECAY_VALUE 10 // How much the injury decays each interval +#define INJURY_DECAY_INTERVAL_MS 30 // How often we decay the the injury + +#define INITIAL_HEALTH 60 +#define MAX_HEALTH 90 + +#define MAX_TEAMS 2 + +#define COINTOSS_FLIP_DURATION 100 // how long we commit to our cointoss for +#define GAME_START_DURATION 300 // wait for all teammates to get the signal to start + +byte team = 0; + +int health; + +Timer healthTimer; // Count down to next time we loose a unit of health +Timer injuryDecayTimer; // Timing to fade away the injury + +#define START_DELAY 100 +Timer startTimer; + +byte injuryBrightness = 0; +byte injuredFace; + +byte deathBrightness = 0; + +bool attackSuccess[6]; + +bool bChangeTeam = false; + +enum State { + DEAD, + ALIVE, + ENGUARDE, // I am ready to attack! + ATTACKING, // Short window when I have already come across my first victim and started attacking + INJURED +}; + +byte mode = DEAD; + +enum GameState { + PLAY, + WAITING, + START +}; + +byte gameState = WAITING; + +byte neighbors[6]; + +Timer modeTimeout; // Started when we enter ATTACKING, when it expires we switch back to normal ALIVE. +// Started when we are injured to make sure we don't get injured multiple times on the same attack + + +void setup() { + // perhaps we should initialize everything here to be safe +} + + +void loop() { + // discard team change from force sleep + if (hasWoken()) { + bChangeTeam = false; + } + + if (buttonDoubleClicked()) { + if (gameState == WAITING) { + changeGameState( START ); + } else { + // reset game and go into waiting mode + mode = DEAD; + changeGameState( WAITING ); + } + } + + + if (buttonLongPressed()) { + if (gameState == WAITING) { + // change team + bChangeTeam = true; + } + } + + if (buttonReleased()) { + if (bChangeTeam) { + // now change the team + team = getNextTeam(); + bChangeTeam = false; + } + } + + // get our neighbor data + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + neighbors[f] = getLastValueReceivedOnFace(f); + } + } + + if (healthTimer.isExpired()) { + + if (health > 0) { + + byte numDeadNeighbors = 0; + + //Dead Blinks will also drain life + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getGameMode(neighbors[f]) == DEAD) { + numDeadNeighbors++; + } + } + } + + //Remove extra health for every dead neighbor attached + health = (health - 1) - numDeadNeighbors; + healthTimer.set(HEALTH_STEP_TIME_MS); + + } else { + + mode = DEAD; + + } + + } + + if ( mode != DEAD ) { + + if (isAlone()) { + + mode = ENGUARDE; // Being lonesome makes us ready to attack! + + } else { // !isAlone() + + if (mode == ENGUARDE) { // We were ornery, but saw someone so we begin our attack in earnest! + + mode = ATTACKING; + modeTimeout.set( ATTACK_DURRATION_MS ); + } + + } + + + if (mode == ATTACKING || mode == INJURED ) { + + if (modeTimeout.isExpired()) { + mode = ALIVE; + FOREACH_FACE(f) { + if (attackSuccess[f]) { + health = min( health + ATTACK_VALUE , MAX_HEALTH ); + } + } + } + } + } // !DEAD + + // check our surroundings + FOREACH_FACE(f) { + + + if (!isValueReceivedOnFaceExpired(f)) { + + byte neighborMode = getGameMode(neighbors[f]); + + if ( mode == ATTACKING ) { + + // We take our flesh when we see that someone we attacked is actually injured + + if ( neighborMode == INJURED ) { + + // TODO: We should really keep a per-face attack timer to lock down the case where we attack the same tile twice in a since interaction. + + attackSuccess[f] = true; + } + + } else if ( mode == ALIVE ) { + + if ( neighborMode == ATTACKING ) { + + health = max( health - ATTACK_VALUE , 0 ) ; + + mode = INJURED; + + injuredFace = f; // set the face we are injured on + + injuryBrightness = 255; // Start very injured + + modeTimeout.set( INJURED_DURRATION_MS ); + + } + + } else if (mode == INJURED) { + + if (modeTimeout.isExpired()) { + + mode = ALIVE; + + } + } + } + } + + // Update our display based on new state + + switch (mode) { + + case DEAD: + displayGhost(); + break; + + case ALIVE: + resetAttackSuccess(); + displayAlive(); + break; + + case ENGUARDE: + displayEnguarde(); + break; + + case ATTACKING: + displayAttack(); + break; + + case INJURED: + displayInjured( injuredFace ); + break; + } + + + // let's start updating game state + switch (gameState) { + case PLAY: playUpdate(); break; + case WAITING: waitingUpdate(); break; + case START: startUpdate(); break; + } + + if (bChangeTeam) { + // display a team change signal + FOREACH_FACE(f) { + if (f < 3) { + setColorOnFace(teamColor(team), f); + } + else { + setColorOnFace(teamColor(getNextTeam()), f); + } + } + } + + byte data = (gameState << 3) + mode; + setValueSentOnAllFaces( data ); // Tell everyone around how we are feeling + +} + + +/* + ------------------------------------------------------------------------------------- + START GAME LOGIC + ------------------------------------------------------------------------------------- +*/ +void changeGameState(byte state) { + + switch (state) { + case PLAY: break; + case WAITING: break; + case START: startTimer.set(START_DELAY); break; + } + gameState = state; +} + +void startGame() { + if (startTimer.isExpired()) { + mode = ALIVE; + changeGameState( PLAY ); + health = INITIAL_HEALTH; + healthTimer.set(HEALTH_STEP_TIME_MS); + } +} + +byte getNextTeam() { + return (team + 1) % MAX_TEAMS; +} + +void playUpdate() { + // if neighbor is in waiting mode, become waiting + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getGameState(neighbors[f]) == WAITING) { + changeGameState( WAITING ); + mode = DEAD; + } + } + } +} + +void waitingUpdate() { + // if neighbor is in start mode, transition to start mode + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getGameState(neighbors[f]) == START) { + changeGameState( START ); + } + } + } +} + +void startUpdate() { + // if all neighbors are in start + bool allReady = true; + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + if (getGameState(neighbors[f]) != START && getGameState(neighbors[f]) != PLAY) { + allReady = false; + } + } + } + + if (isAlone()) { + allReady = false; + } + + if (allReady) { + startGame(); + } +} + +void resetAttackSuccess() { + FOREACH_FACE(f) { + attackSuccess[f] = false; + } +} + +/* + ------------------------------------------------------------------------------------- + END GAME LOGIC + ------------------------------------------------------------------------------------- +*/ + + +/* + ------------------------------------------------------------------------------------- + START DISPLAY + ------------------------------------------------------------------------------------- +*/ + + +/* + Display state for living Mortals +*/ +void displayAlive() { + setColor(OFF); + FOREACH_FACE(f) { + + if ( f <= (health / 10) ) { + // show health on the number of faces to represent 10 health for each light + setColorOnFace(teamColor( team ), f); + // TODO: FLASH the last 10 seconds of life + } + else { + // turn out the lights on faces to show a loss of health over time + setColorOnFace(OFF, f); + } + } + + if (health <= 0 ) { + + // glow bright white and fade out when we die + setColor( dim(WHITE, deathBrightness) ); + + if (deathBrightness > 7) { + deathBrightness -= 8; + } + } + + // don't show the sucking of energy when in the death phase + if (deathBrightness == 255) { + // show the dead sucking life + FOREACH_FACE(f) { + + if (!isValueReceivedOnFaceExpired(f)) { + + if (getGameMode(neighbors[f]) == DEAD) { + + // pulse red on injured face + // TODO: Create a pulse algorithm that is less memory intensive + byte bri = 143 + (111 * sin_d(millis()));//breathe(600, 32, 255); + + if ( f <= (health / 10) ) { + // if the tile is alive and showing life on this face, alternate red and team color + if ( (millis() / 600) % 2 == 0 ) { + setColorOnFace( dim(RED, bri), f); + } + else { + setColorOnFace( dim( teamColor( team ), bri), f); + } + } + else { + // else show red + setColorOnFace( dim(RED, bri), f); + } + } + } + } + } +} + +/* + Display state for injured Mortal + takes the face we were injured on + +*/ +void displayInjured(byte face) { + + // first we display our health + displayAlive(); + + // then we update the side that was injured + if ( injuryDecayTimer.isExpired() ) { + injuryDecayTimer.set( INJURY_DECAY_INTERVAL_MS ); + injuryBrightness -= INJURY_DECAY_VALUE; + } + + // brighten the sides with neighbors + if ( injuryBrightness > 32 ) { + setFaceColor( face, dim( RED, injuryBrightness) ); + } + +} + +/* + +*/ +void displayGhost() { + + setColor(OFF); + + // check game state + if ( gameState == PLAY ) { + FOREACH_FACE(f) { + + setFaceColor(f, dim( RED, 64 + 32 * sin_d( (60 * f + millis() / 8) % 360))); // slow dim rotation, just take my word for it :) + + } + } + else if (gameState == WAITING ) { + + setColor( dim(teamColor( team ), 92) ); + setFaceColor( 1, dim( teamColor( team ), 159 + 96 * sin_d( ( millis() / 4) % 360) ) ); + + } + else if (gameState == START ) { + setColor(WHITE); // quick flash of white to start the game (only a single frame) + deathBrightness = 255; // reset the death brightness + } +} + +/* + +*/ +void displayEnguarde() { + + setColor( OFF ); + + setFaceColor( (millis() / 100) % FACE_COUNT, teamColor( team ) ); + +} + +/* + +*/ +void displayAttack() { + + setColor( OFF ); + + setFaceColor( random(FACE_COUNT), teamColor( team ) ); + +} +/* + ------------------------------------------------------------------------------------- + END DISPLAY + ------------------------------------------------------------------------------------- +*/ + + +/* + ------------------------------------------------------------------------------------- + HELPER FUNCTIONS + ------------------------------------------------------------------------------------- +*/ + +/* + Sin in degrees ( standard sin() takes radians ) +*/ + +float sin_d( uint16_t degrees ) { + + return sin( ( degrees / 360.0F ) * 2.0F * PI ); +} + +/* + get the team color for our team +*/ +Color teamColor( byte t ) { + switch (t) { + case 0: return makeColorRGB(190, 0, 255); + case 1: return makeColorRGB(100, 255, 0); + } +} + +byte getGameMode(byte data) { + return data & 7; // 00000111 -> keeps the last 3 digits in binary +} + +byte getGameState(byte data) { + return data >> 3; // 00000XXX -> moves all digits to the right 3 times +} \ No newline at end of file diff --git a/libraries/Examples03/examples/Puzzle101/Puzzle101.ino b/libraries/Examples03/examples/Puzzle101/Puzzle101.ino new file mode 100644 index 00000000..233dd2d2 --- /dev/null +++ b/libraries/Examples03/examples/Puzzle101/Puzzle101.ino @@ -0,0 +1,542 @@ +/* + Puzzle101 + by Move38, Inc. 2019 + Lead development by Dan King + original game by Vanilla Liu, Dan King + + Rules: https://github.com/Move38/Puzzle101/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +////COMMUNICATION VARIABLES//// +enum gameModes {SETUPAUTO, PACKETREADY, PACKETSENDING, PACKETLISTENING, PACKETRECEIVED, GAMEAUTO}; +byte gameMode = SETUPAUTO; +byte packetStates[6] = {PACKETREADY, PACKETREADY, PACKETREADY, PACKETREADY, PACKETREADY, PACKETREADY}; + +///ALGORITHM VARIABLES//// +byte piecesPlaced = 0; +enum connections {UNDECLARED, APIECE, BPIECE, CPIECE, DPIECE, EPIECE, FPIECE, NONEIGHBOR}; +byte neighborsArr[6][6];//filled with the values from above, denotes neighbors. [x][y] x is piece, y is face +byte colorsArr[6][6];//filled with 0-3, denotes color of connection. [x][y] x is piece, y is face + +////ASSEMBLY VARIABLES//// +bool canBeginAlgorithm = false; +bool isMaster = false; +byte masterFace = 0;//for receivers, this is the face where the master was found +Timer sparkleTimer; + +Timer packetTimer; +#define TIMEOUT_DURATION 700 + +////GAME VARIABLES//// +Color autoColors[5] = {OFF, makeColorRGB(255, 0, 128), makeColorRGB(255, 255, 0), makeColorRGB(0, 128, 255), WHITE}; +byte faceColors[6] = {0, 0, 0, 0, 0, 0}; +byte faceBrightness[6] = {0, 0, 0, 0, 0, 0}; +byte faceSolved[6]; +byte colorDim = 160; +byte whiteDim = 64; + +// SYNCHRONIZED CELEBRATION +Timer syncTimer; +#define PERIOD_DURATION 2000 +#define BUFFER_DURATION 200 +byte neighborState[6]; +byte syncVal = 0; + +void setup() { + randomize(); +} + +void loop() { + switch (gameMode) { + case SETUPAUTO: + setupAutoLoop(); + assembleDisplay(); + break; + case PACKETREADY: + communicationMasterLoop(); + communicationDisplay(); + break; + case PACKETSENDING: + communicationMasterLoop(); + communicationDisplay(); + break; + case PACKETLISTENING: + communicationReceiverLoop(); + communicationDisplay(); + break; + case PACKETRECEIVED: + communicationReceiverLoop(); + communicationDisplay(); + break; + case GAMEAUTO: + gameLoop(); + syncLoop(); + gameDisplay(); + break; + } + + //clear button presses + buttonDoubleClicked(); + + //set communications + FOREACH_FACE(f) { + byte sendData = (syncVal << 5) + (gameMode << 2) + (faceColors[f]); + setValueSentOnFace(sendData, f); + } +} + +/////////////// +//SETUP LOOPS// +/////////////// + +void setupAutoLoop() { + //all we do here is wait until we have 5 neighbors + byte numNeighbors = 0; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getGameMode(neighborData) == SETUPAUTO) { //this neighbor is ready for puzzling + numNeighbors++; + faceBrightness[f] = 255; + } else { + faceBrightness[f] = whiteDim; + } + } else { + faceBrightness[f] = whiteDim; + } + } + + if (numNeighbors == 5) { + canBeginAlgorithm = true; + } else { + canBeginAlgorithm = false; + } + + if (buttonDoubleClicked() && canBeginAlgorithm == true) {//this lets us become the master blink + makePuzzle();//RUN THE ALGORITHM + gameMode = PACKETREADY; + canBeginAlgorithm = false; + isMaster = true; + } + + FOREACH_FACE(f) {//here we listen for other blinks to turn us into receiver blinks + if (!isValueReceivedOnFaceExpired(f)) {//neighbor here + byte neighborData = getLastValueReceivedOnFace(f); + if (getGameMode(neighborData) == PACKETREADY) { //this neighbor will send a puzzle soon + gameMode = PACKETLISTENING; + masterFace = f;//will only listen for packets on this face + } + } + } +} + +///////////// +//GAME LOOP// +///////////// + +void gameLoop() { + //all we do here is look at our faces and see if they are touching like colors + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + byte neighborColor = getColorInfo(neighborData); + if (neighborColor == faceColors[f]) { //hey, a match! + faceBrightness[f] = 255; + faceSolved[f] = true; + } else {//no match :( + faceBrightness[f] = colorDim; + faceSolved[f] = false; + } + + //look for neighbors turning us back to setup + + if (getGameMode(neighborData) == SETUPAUTO) { + gameMode = SETUPAUTO; + } + + + } else {//no neighbor + faceBrightness[f] = colorDim; + faceSolved[f] = false; + } + } + + //if we are double clicked, we go to assemble mode + if (buttonDoubleClicked()) { + if (gameMode == GAMEAUTO) { + gameMode = SETUPAUTO; + } + } +} + +///////////////// +//DISPLAY LOOPS// +///////////////// + +void assembleDisplay() { + if (sparkleTimer.isExpired() && canBeginAlgorithm) { + FOREACH_FACE(f) { + setColorOnFace(autoColors[random(3) + 1], f); + sparkleTimer.set(50); + } + } + + if (!canBeginAlgorithm) { + FOREACH_FACE(f) { + setColorOnFace(dim(WHITE, faceBrightness[f]), f); + } + } +} + +void gameDisplay() { + + Color displayColor; + FOREACH_FACE(f) { + displayColor = autoColors[faceColors[f]]; + byte displayBrightness; + if (faceSolved[f]) { + displayBrightness = sin8_C(map(syncTimer.getRemaining(), 0, PERIOD_DURATION, 0, 255)); + } + else { + displayBrightness = 255; + } + setColorOnFace(dim(displayColor, displayBrightness), f); + } +} + +/////////////////////// +//COMMUNICATION LOOPS// +/////////////////////// + +void communicationMasterLoop() { + + if (gameMode == PACKETREADY) {//here we wait to send packets to listening neighbors + + byte neighborsListening = 0; + byte emptyFace; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (getGameMode(neighborData) == PACKETLISTENING) {//this neighbor is ready to get a packet. + neighborsListening++; + } + } else { + emptyFace = f; + } + } + + if (neighborsListening == 5) { + gameMode = PACKETSENDING; + sendPuzzlePackets(emptyFace); + packetTimer.set(TIMEOUT_DURATION); + } + + } else if (gameMode == PACKETSENDING) {//here we listen for neighbors who have received packets + + byte neighborsReceived = 0; + byte emptyFace; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (getGameMode(neighborData) == PACKETRECEIVED) {//this neighbor is ready to play + neighborsReceived++; + } + } else { + emptyFace = f; + } + } + + if (neighborsReceived == 5) { //hooray, we did it! + gameMode = GAMEAUTO; + return; + } + + if (gameMode != GAMEAUTO && packetTimer.isExpired()) { //so we've gone a long time without this working out + sendPuzzlePackets(emptyFace); + packetTimer.set(TIMEOUT_DURATION); + } + } + + if (buttonDoubleClicked()) { + gameMode = SETUPAUTO; + } +} + +void sendPuzzlePackets(byte blankFace) { + //declare packets + byte packet[6][6]; + + //SEND PACKETS + FOREACH_FACE(f) { + FOREACH_FACE(ff) { + packet[f][ff] = colorsArr[f][ff]; + } + sendDatagramOnFace( &packet[f], sizeof(packet[f]), f); + } + + //assign self the correct info + FOREACH_FACE(f) { + faceColors[f] = colorsArr[blankFace][f]; + } +} + +void communicationReceiverLoop() { + if (gameMode == PACKETLISTENING) { + + //listen for a packet on master face + if (isDatagramReadyOnFace(masterFace)) {//is there a packet? + if (getDatagramLengthOnFace(masterFace) == 6) {//is it the right length? + byte *data = (byte *) getDatagramOnFace(masterFace);//grab the data + //fill our array with this data + FOREACH_FACE(f) { + faceColors[f] = data[f]; + } + //let them know we heard them + gameMode = PACKETRECEIVED; + + // mark datagram as received + markDatagramReadOnFace(masterFace); + } + } + + //also listen for the master face to suddenly change back to setup, which is bad + if (getGameMode(getLastValueReceivedOnFace(masterFace)) == SETUPAUTO) { //looks like we are reverting + gameMode = SETUPAUTO; + } + + } else if (gameMode == PACKETRECEIVED) { + //wait for the master blink to transition to game + if (getGameMode(getLastValueReceivedOnFace(masterFace)) == GAMEAUTO) { //time to play! + gameMode = GAMEAUTO; + } + } + + if (buttonDoubleClicked()) { + gameMode = SETUPAUTO; + } +} + +byte getSyncVal(byte data) { + return (data >> 5) & 1; +} + +byte getGameMode(byte data) { + return (data >> 2) & 7;//1st, 2nd, 3rd, and 4th bits +} + +byte getColorInfo(byte data) { + return (data & 3);//returns the 5th and 6th bits +} + +void communicationDisplay() { + if (sparkleTimer.isExpired()) { + FOREACH_FACE(f) { + setColorOnFace(autoColors[random(3) + 1], f); + sparkleTimer.set(50); + } + } +} + +/////////////////////////////// +//PUZZLE GENERATION ALGORITHM// +/////////////////////////////// + +void makePuzzle() { + resetAll(); + piecesPlaced++;//this symbolically places the first blink in the center + //place 2-4 NONEIGHBORS in first ring + byte emptySpots = random(2) + 2;//this is how many NONEIGHBORS we're putting in + FOREACH_FACE(f) { + if (f < emptySpots) { + neighborsArr[0][f] = NONEIGHBOR; + } + } + + for (int j = 0; j < 12; j++) {//quick shuffle method, random enough for our needs + byte swapA = random(5); + byte swapB = random(5); + byte temp = neighborsArr[0][swapA]; + neighborsArr[0][swapA] = neighborsArr[0][swapB]; + neighborsArr[0][swapB] = temp; + } + + //place blinks in remainings open spots + for (byte j = 0; j < 6 - emptySpots; j++) { + addBlink(0, 0); + } + + byte remainingBlinks = 6 - piecesPlaced; + byte lastRingBlinkIndex = piecesPlaced - 1; + for (byte k = 0; k < remainingBlinks; k++) { + addBlink(1, lastRingBlinkIndex); + } + colorConnections(); +} + +void resetAll() { + piecesPlaced = 0; + + FOREACH_FACE(f) { + FOREACH_FACE(i) { + neighborsArr[f][i] = 0; + colorsArr[f][i] = 0; + } + } +} + +void addBlink(byte minSearchIndex, byte maxSearchIndex) { + //we begin by evaluating how many eligible spots remain + byte eligiblePositions = 0; + for (byte i = minSearchIndex; i <= maxSearchIndex; i++) { + FOREACH_FACE(f) { + if (neighborsArr[i][f] == 0) { //this is an eligible spot + eligiblePositions ++; + } + } + }//end of eligible positions counter + + //now choose a random one of those eligible positions + byte chosenPosition = random(eligiblePositions - 1) + 1;//necessary math to get 1-X values + byte blinkIndex; + byte faceIndex; + //now determine which blink this is coming off of + byte positionCountdown = 0; + for (byte i = minSearchIndex; i <= maxSearchIndex; i++) {//same loop as above + FOREACH_FACE(f) { + if (neighborsArr[i][f] == 0) { //this is an eligible spot + positionCountdown ++; + if (positionCountdown == chosenPosition) { + //this is it. Record the position! + blinkIndex = i; + faceIndex = f; + } + } + } + }//end of position finder + + //so first we simply place the connection data on the connecting faces + neighborsArr[blinkIndex][faceIndex] = getCurrentPiece();//placing the new blink on the ring blink + neighborsArr[piecesPlaced][getNeighborFace(faceIndex)] = blinkIndex + 1;//placing the ring blink on the new blink + piecesPlaced++; + + //first, the counterclockwise face of the blinked we attached to + byte counterclockwiseNeighborInfo = neighborsArr[blinkIndex][nextCounterclockwise(faceIndex)]; + if (counterclockwiseNeighborInfo != UNDECLARED) { //there is a neighbor or NONEIGHBOR on the next counterclockwise face of the blink we placed onto + //we tell the new blink it has a neighbor or NONEIGHBOR clockwise from our connection + byte newNeighborConnectionFace = nextClockwise(getNeighborFace(faceIndex)); + neighborsArr[piecesPlaced - 1][newNeighborConnectionFace] = counterclockwiseNeighborInfo; + + if (counterclockwiseNeighborInfo != NONEIGHBOR) { //if it's an actual blink, it needs to know about the new connection + neighborsArr[counterclockwiseNeighborInfo - 1][getNeighborFace(newNeighborConnectionFace)] = piecesPlaced; + } + } + + //now, the clockwise face (everything reversed, but identical) + byte clockwiseNeighborInfo = neighborsArr[blinkIndex][nextClockwise(faceIndex)]; + if (clockwiseNeighborInfo != UNDECLARED) { //there is a neighbor or NONEIGHBOR on the next clockwise face of the blink we placed onto + //we tell the new blink it has a neighbor or NONEIGHBOR counterclockwise from our connection + byte newNeighborConnectionFace = nextCounterclockwise(getNeighborFace(faceIndex)); + neighborsArr[piecesPlaced - 1][newNeighborConnectionFace] = clockwiseNeighborInfo; + + if (clockwiseNeighborInfo != NONEIGHBOR) { //if it's an actual blink, it needs to know about the new connection + neighborsArr[clockwiseNeighborInfo - 1][getNeighborFace(newNeighborConnectionFace)] = piecesPlaced; + } + } +} + +void colorConnections() { + //you look through all the neighbor info. When you find a connection with no color, you make it + FOREACH_FACE(f) { + FOREACH_FACE(ff) { + if (neighborsArr[f][ff] != UNDECLARED && neighborsArr[f][ff] != NONEIGHBOR) { //there is a connection here + byte foundIndex = neighborsArr[f][ff] - 1; + if (colorsArr[f][ff] == 0) { //we haven't made this connection yet! + //put a random color there + byte connectionColor = random(2) + 1; + colorsArr[f][ff] = connectionColor; + FOREACH_FACE(fff) { //go through the faces of the connecting blink, find the connection to the current blink + if (neighborsArr[foundIndex][fff] == f + 1) {//the connection on the found blink's face is the current blink + colorsArr[foundIndex][fff] = connectionColor; + } + } + } + } + } + } +} + +byte getNeighborFace(byte face) { + return ((face + 3) % 6); +} + +byte nextClockwise (byte face) { + if (face == 5) { + return 0; + } else { + return face + 1; + } +} + +byte nextCounterclockwise (byte face) { + if (face == 0) { + return 5; + } else { + return face - 1; + } +} + +byte getCurrentPiece () { + // Because a piece is represented by a value simply 1 greater than the pieces placed numner + // this is more efficient to compile than the original switch statement. I understand this + // is less clear to read, but it saves us needed space. Check the enum up top to understand + // that 0 should return PIECE_A and 1 should return PIECE_B (which are shifted by one) + return piecesPlaced + 1; +} + +/* + * Keep ourselves on the same time loop as our neighbors + * if a neighbor passed go, + * we want to pass go as well + * (if we didn't just pass go) + * ... or collect $200 + */ +void syncLoop() { + + bool didNeighborChange = false; + + // look at our neighbors to determine if one of them passed go (changed value) + // note: absent neighbors changing to not absent don't count + FOREACH_FACE(f) { + if (isValueReceivedOnFaceExpired(f)) { + neighborState[f] = 2; // this is an absent neighbor + } + else { + byte data = getLastValueReceivedOnFace(f); + if (neighborState[f] != 2) { // wasn't absent + if (getSyncVal(data) != neighborState[f]) { // passed go (changed value) + didNeighborChange = true; + } + } + + neighborState[f] = getSyncVal(data); // update our record of state now that we've check it + } + } + + // if our neighbor passed go and we haven't done so within the buffer period, catch up and pass go as well + // if we are due to pass go, i.e. timer expired, do so + if ( (didNeighborChange && syncTimer.getRemaining() < PERIOD_DURATION - BUFFER_DURATION) + || syncTimer.isExpired() + ) { + + syncTimer.set(PERIOD_DURATION); // aim to pass go in the defined duration + syncVal = !syncVal; // change our value everytime we pass go + } +} diff --git a/libraries/Examples03/examples/SpeedRacer/SpeedRacer.ino b/libraries/Examples03/examples/SpeedRacer/SpeedRacer.ino new file mode 100644 index 00000000..a434ebe5 --- /dev/null +++ b/libraries/Examples03/examples/SpeedRacer/SpeedRacer.ino @@ -0,0 +1,668 @@ +/* + Speed Racer + by Move38, Inc. 2019 + Lead development by Dan King + original game by Dan King, Jonathan Bobrow + + Rules: https://github.com/Move38/SpeedRacer/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +enum faceRoadStates {LOOSE, ROAD, SIDEWALK, CRASH}; +byte faceRoadInfo[6]; + +enum handshakeStates {NOCAR, HAVECAR, READY, CARSENT}; +byte handshakeState[6]; +Timer datagramTimeout; +#define DATAGRAM_TIMEOUT_LIMIT 150 + +byte turns[3][6] = { {5, 25, 50, 75, 95, 25}, // left hand turn + {5, 33, 66, 95, 66, 33}, // straight away + {5, 25, 95, 75, 50, 25} // right hand turn +}; + +bool isLoose = true; + +bool hasDirection = false; +byte entranceFace = 0; +byte exitFace = 0; + +bool haveCar = false; +byte carProgress = 0;//from 0-100 is the regular progress + +bool isCarPassed[6]; +uint32_t timeCarPassed[6]; +byte carBrightnessOnFace[6]; + +#define FADE_DURATION 1500 +#define FADE_ROAD_DURATION 500 +#define CRASH_DURATION 2000 + +uint32_t timeOfShockwave = 0; + +byte currentSpeed = 1; + +enum CarClass { + STANDARD, + BOOSTED +}; + +byte searchOrder[6] = {0, 1, 2, 3, 4, 5}; // used for searching faces, needs to be shuffled + +byte currentCarClass = STANDARD; + +#define SPEED_INCREMENTS_STANDARD 35 +#define SPEED_INCREMENTS_BOOSTED 70 + +#define MIN_TRANSIT_TIME_STANDARD 666 // HIGHWAY TO HELL +#define MAX_TRANSIT_TIME_STANDARD 1200 +#define MIN_TRANSIT_TIME_BOOSTED 200 +#define MAX_TRANSIT_TIME_BOOSTED 1000 + +word currentTransitTime; + +Timer transitTimer; + +byte carHues[4] = {80, 111, 160, 215}; // TODO: Set these colors purposefully +byte currentCarHue = 0; // index of the car color + +bool crashHere = false; +uint32_t timeOfCrash = 0; +Timer crashTimer; +#define CRASH_TIME 2500 + +#define CAR_FADE_IN_DIST 200 // kind of like headlights + +enum ShockwaveStates { + INERT, + SHOCKWAVE, + TRANSITION +}; +byte shockwaveState = INERT; + +/* + SETUP +*/ + +void setup() { + randomize(); + shuffleSearchOrder(); +} + +/* + LOOP +*/ + +void loop() { + //run loops + if (isLoose) { + looseLoop(); + } else if (crashHere) { + crashLoop(); + } else if (haveCar) { + roadLoopCar(); + } else { + roadLoopNoCar(); + } + + //shockwave handling + shockwaveLoop(); + + //run graphics + graphics(); + + //update communication + FOREACH_FACE(f) { + byte sendData = (faceRoadInfo[f] << 4) + (handshakeState[f] << 2) + shockwaveState; + setValueSentOnFace(sendData, f); + } + + //clear button presses + buttonSingleClicked(); + buttonDoubleClicked(); +} + +void looseLoop() { + if (!isAlone()) { + //so I look at all faces, see what my options are + bool foundRoadNeighbor = false; + bool foundLooseNeighbor = false; + byte currentChoice; + FOREACH_FACE(f) { + //should I still be looking? + if (!foundRoadNeighbor) {//only look if I haven't found a road neighbor + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getRoadState(neighborData) == ROAD) {//so this neighbor is a road. Sweet! + foundRoadNeighbor = true; + currentChoice = f; + } else if (getRoadState(neighborData) == LOOSE) { + foundLooseNeighbor = true; + currentChoice = f; + } + } + } + } + + //if we have found any legit neighbor, we can transition out of loose + if (foundRoadNeighbor || foundLooseNeighbor) { + FOREACH_FACE(f) { + faceRoadInfo[f] = SIDEWALK; + } + faceRoadInfo[currentChoice] = ROAD; + entranceFace = currentChoice; // Helps us draw the road + completeRoad(currentChoice); + isLoose = false; + + } else { + //TODO: error state for bad placement + } + } +} + +void completeRoad(byte startFace) { + //so we've been fed a starting point + //we need to assign an exit point based on some rules + bool foundRoadExit = false; + byte currentChoice = (startFace + 2 + random(1) + random(1)) % 6; //random(1) + random(1) -> assigns a straightaway 50% of the time + + //now run through the legal exits and check for preferred exits + FOREACH_FACE(f) { + if (isValidExit(startFace, f)) { + if (!foundRoadExit) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getRoadState(neighborData) == ROAD) { + foundRoadExit = true; + currentChoice = f; + } else if (getRoadState(neighborData) == LOOSE) { + currentChoice = f; + } + } + } + } + }//end face loop + + //so after this process, we can be confident that a ROAD has been chosen + //or failing that, a LOOSE has been chosen + //or failing that just a random face has been chosen + faceRoadInfo[currentChoice] = ROAD; + exitFace = currentChoice; // Helps us draw the road +} + +bool isValidExit(byte startFace, byte exitFace) { + if (exitFace == (startFace + 2) % 6) { + return true; + } else if (exitFace == (startFace + 3) % 6) { + return true; + } else if (exitFace == (startFace + 4) % 6) { + return true; + } else { + return false; + } +} + +void roadLoopNoCar() { + + FOREACH_FACE(f) { + if (faceRoadInfo[f] == ROAD) {//things can arrive here + if (handshakeState[f] == NOCAR) {//I have no car here. Does my neighbor have a car? + if (!isValueReceivedOnFaceExpired(f)) {//there's someone on this face + byte neighborData = getLastValueReceivedOnFace(f); + if (getHandshakeState(neighborData) == HAVECAR) { + handshakeState[f] = READY; + } + } + } else if (handshakeState[f] == READY) {//I am ready. Look for changes + //did my neighbor disappear, change to CRASH or NOCAR? + if (isValueReceivedOnFaceExpired(f)) {//my neighbor has left. Guess that's not happening + handshakeState[f] = NOCAR; + } else {//neighbor still there. have they changed in another way? + byte neighborData = getLastValueReceivedOnFace(f); + if (getRoadState(neighborData) != ROAD) { //huh, I guess they changed in a weird way + handshakeState[f] = NOCAR; + } else {//they're still a road. did their handshakeState change? + if (getHandshakeState(neighborData) == NOCAR || getHandshakeState(neighborData) == READY) {//another weird failure + handshakeState[f] = NOCAR; + } else if (getHandshakeState(neighborData) == CARSENT) { + //look for the speedDatagram + if (isDatagramReadyOnFace(f)) {//is there a packet? + if (getDatagramLengthOnFace(f) == 3) {//is it the right length? + byte *data = (byte *) getDatagramOnFace(f);//grab the data + currentSpeed = data[0]; + currentCarClass = data[1]; + currentCarHue = data[2]; + + //THEY HAVE SENT THE CAR. BECOME THE ACTIVE GUY + FOREACH_FACE(ff) { + handshakeState[ff] = NOCAR; + } + haveCar = true; + resetIsCarPassed(); + currentTransitTime = map(getSpeedIncrements() - currentSpeed, 0, getSpeedIncrements(), getMinTransitTime(), getMaxTransitTime()); + transitTimer.set(currentTransitTime); + + hasDirection = true; + entranceFace = f; + exitFace = findOtherSide(entranceFace); + handshakeState[entranceFace] = HAVECAR; + handshakeState[exitFace] = HAVECAR; + + markDatagramReadOnFace( f ); // free datagram buffer + } + } + } + } + } + } + } + } + + + //if you become alone, GO LOOSE + if (isAlone()) { + goLoose(); + } + + //if I'm clicked, I will attempt to spawn the car (only happens if there is a legitimate exit choice) + if (buttonSingleClicked()) { + spawnCar(STANDARD); + } + + if (buttonDoubleClicked()) { + spawnCar(BOOSTED); + } + +} + +void spawnCar(byte carClass) { + FOREACH_FACE(face) { + byte f = searchOrder[face]; + if (!hasDirection) { + if (faceRoadInfo[f] == ROAD) {//this could be my exit + if (!isValueReceivedOnFaceExpired(f)) {//there is someone there + byte neighborData = getLastValueReceivedOnFace(f); + if (getRoadState(neighborData) == ROAD) {//so this is a road I can send to. DO IT ALL + //set direction + hasDirection = true; + exitFace = f; + entranceFace = findOtherSide(exitFace); + + //set outgoing data + FOREACH_FACE(ff) { + handshakeState[ff] = NOCAR; + } + handshakeState[exitFace] = HAVECAR; + + // set the car class + currentCarClass = carClass; + + // choose a hue for this car + currentCarHue = random(3); + + // launch car + haveCar = true; + resetIsCarPassed(); + currentTransitTime = map(getSpeedIncrements() - currentSpeed, 0, getSpeedIncrements(), getMinTransitTime(), getMaxTransitTime()); + transitTimer.set(currentTransitTime); + } + } + } + } + } + shuffleSearchOrder(); // thanks random search order, next. +} + +void goLoose() { + isLoose = true; + + FOREACH_FACE(f) { + faceRoadInfo[f] = LOOSE; + isCarPassed[f] = false; + timeCarPassed[f] = 0; + carBrightnessOnFace[f] = 0; + } + + loseCar(); +} + +void loseCar() { + hasDirection = false; + haveCar = false; + carProgress = 0;//from 0-100 is the regular progress + currentSpeed = 1; + crashHere = false; + timeOfCrash = 0; + FOREACH_FACE(f) { + handshakeState[f] = NOCAR; + } +} + +void resumeRoad() { + FOREACH_FACE(f) { + faceRoadInfo[f] = SIDEWALK; + } + faceRoadInfo[entranceFace] = ROAD; + faceRoadInfo[exitFace] = ROAD; +} + +byte findOtherSide(byte entrance) { + FOREACH_FACE(f) { + if (isValidExit(entrance, f)) { + if (faceRoadInfo[f] == ROAD) { + return f; + } + } + } +} + +void roadLoopCar() { + + if (handshakeState[exitFace] == HAVECAR) { + //wait for the timer to expire and pass the car + if (transitTimer.isExpired()) { + //ok, so here is where shit gets tricky + if (!isValueReceivedOnFaceExpired(exitFace)) {//there is someone on my exitFace + byte neighborData = getLastValueReceivedOnFace(exitFace); + if (getRoadState(neighborData) == ROAD) { + if (getHandshakeState(neighborData) == READY) { + handshakeState[exitFace] = CARSENT; + + byte speedDatagram[3]; // holds speed, car class, car hue + if (currentSpeed + 1 <= getSpeedIncrements()) { + speedDatagram[0] = currentSpeed + 1; + } else { + speedDatagram[0] = currentSpeed; + } + speedDatagram[1] = currentCarClass; + speedDatagram[2] = currentCarHue; + sendDatagramOnFace(&speedDatagram, sizeof(speedDatagram), exitFace); + + datagramTimeout.set(DATAGRAM_TIMEOUT_LIMIT); + + } else { + //CRASH because not ready + crashBlink(); + } + } else { + //CRASH crash because not road + crashBlink(); + } + } else { + //CRASH because not there! + crashBlink(); + } + } + } else if (handshakeState[exitFace] == CARSENT) { + if (!isValueReceivedOnFaceExpired(exitFace)) {//there's someone on my exit face + if (getHandshakeState(getLastValueReceivedOnFace(exitFace)) == HAVECAR) {//the car has been successfully passed + handshakeState[exitFace] = NOCAR; + loseCar(); + } + } + //if I'm still in CARSENT and my datagram timeout has expired, then we can assume the car is lost and we've crashed + if (handshakeState[exitFace] == CARSENT) { + if (datagramTimeout.isExpired()) { + //CRASH because timeout + crashBlink(); + } + } + } + + // Car progress for animation loop (when did the car pass each face) + FOREACH_FACE(f) { + // did the car just pass us + if (!isCarPassed[f]) { + carProgress = (100 * (currentTransitTime - transitTimer.getRemaining())) / currentTransitTime; + if (didCarPassFace(f, carProgress, entranceFace, exitFace)) { + timeCarPassed[f] = millis(); + isCarPassed[f] = true; + } + } + } +} + +void crashBlink() { + shockwaveState = SHOCKWAVE; + timeOfShockwave = millis(); + isLoose = false; + timeOfCrash = millis(); + crashHere = true; + FOREACH_FACE(f) { + faceRoadInfo[f] = CRASH; + } + crashTimer.set(CRASH_TIME); +} + +void crashLoop() { + if (crashTimer.isExpired()) { + loseCar(); + resumeRoad(); + } +} + +/* + This function does the following: + + if inert + if neighbor in shockwave + shockwave + if shockwave + if no neighbors are inert + trans + if trans + if no neighbors in shockwave + inert +*/ +void shockwaveLoop() { + bool bInert = false; + bool bShock = false; + bool bTrans = false; + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte state = getShockwaveState(getLastValueReceivedOnFace(f)); + if (state == INERT) { + bInert = true; + } + else if (state == SHOCKWAVE) { + bShock = true; + } + else if (state == TRANSITION) { + bTrans = true; + } + } + } + + if (shockwaveState == INERT) { + if (bShock) { + shockwaveState = SHOCKWAVE; + timeOfShockwave = millis(); + } + } + else if (shockwaveState == SHOCKWAVE) { + if (!bInert) { + shockwaveState = TRANSITION; + } + } + else if (shockwaveState == TRANSITION) { + if (!bShock) { + shockwaveState = INERT; + } + } +} + +byte getRoadState(byte neighborData) { + return (neighborData >> 4);//1st and 2nd bits +} + +byte getHandshakeState(byte neighborData) { + return ((neighborData >> 2) & 3);//3rd and 4th bits +} + +byte getShockwaveState(byte neighborData) { + return (neighborData & 3);//5th and 6th bits +} + +/* + THE REAL DEAL +*/ +void graphics() { + // clear buffer + setColor(OFF); + + FOREACH_FACE(f) { + + // first draw the car fade + if (millis() - timeCarPassed[f] > FADE_DURATION) { + carBrightnessOnFace[f] = 0; + + // draw the road + if (faceRoadInfo[f] == ROAD) { + if (millis() - timeCarPassed[f] < FADE_ROAD_DURATION + FADE_DURATION) { + byte roadBrightness = (millis() - timeCarPassed[f] - FADE_DURATION) / 2; + setColorOnFace(dim(YELLOW, roadBrightness), f); + } + else { + setColorOnFace(YELLOW, f); + } + } + + } + else { + // in the beginning, quick fade in + if (millis() - timeCarPassed[f] > CAR_FADE_IN_DIST ) { + carBrightnessOnFace[f] = 255 - map(millis() - timeCarPassed[f] - CAR_FADE_IN_DIST, 0, FADE_DURATION - CAR_FADE_IN_DIST, 0, 255); + } + else { + carBrightnessOnFace[f] = map(millis() - timeCarPassed[f], 0, CAR_FADE_IN_DIST, 0, 255); + } + + // Draw our car + if (currentCarClass == STANDARD) { + setColorOnFace(makeColorHSB(carHues[currentCarHue], 255, carBrightnessOnFace[f]), f); + } + else { + setColorOnFace(makeColorHSB(carHues[currentCarHue], 0, carBrightnessOnFace[f]), f); + } + + } + } + + if (isLoose) { + standbyGraphics(); + } + + if (millis() - timeOfShockwave < 500) { + Color shockwaveColor = makeColorHSB((millis() - timeOfShockwave) / 12, 255, 255); + setColorOnFace(shockwaveColor, entranceFace); // should really be 3x as long, with a delay for the travel of the effect + setColorOnFace(shockwaveColor, exitFace); + } + + if ( millis() - timeOfCrash < CRASH_TIME ) { + setColor(RED); + // show fiery wreckage + FOREACH_FACE(f) { + byte shakiness = map(millis() - timeOfCrash, 0, CRASH_TIME, 0, 30); + //byte bri = 200 - map(millis() - timeOfCrash, 0, CRASH_TIME, 0, 200) + random(55); + //setColorOnFace(makeColorHSB(0, random(55) + 200, bri), f); + setColorOnFace(makeColorHSB(30 - shakiness, 255, 255 - (shakiness * 6) - random(55)), f); + } + + // crashGraphics(); + } +} + +/* + SPEED CONVENIENCE FUNCTIONS +*/ + +word getSpeedIncrements() { + if (currentCarClass == STANDARD) { + return SPEED_INCREMENTS_STANDARD; + } + else { + return SPEED_INCREMENTS_BOOSTED; + } +} + +word getMinTransitTime() { + if (currentCarClass == STANDARD) { + return MIN_TRANSIT_TIME_STANDARD; + } + else { + return MIN_TRANSIT_TIME_BOOSTED; + } +} + +word getMaxTransitTime() { + if (currentCarClass == STANDARD) { + return MAX_TRANSIT_TIME_STANDARD; + } + else { + return MAX_TRANSIT_TIME_BOOSTED; + } +} + +/* + RANDOMIZE OUR SEARCH ORDER + reference: http://www.cplusplus.com/reference/algorithm/random_shuffle/ +*/ + +void shuffleSearchOrder() { + + for (byte i = 5; i > 0; i--) { + // start with the right most, replace it with one of the 5 to the left + // then move one to the left, and do this with the 4 to the left. 3, 2, 1 + byte swapA = i; + byte swapB = random(i - 1); + byte temp = searchOrder[swapA]; + searchOrder[swapA] = searchOrder[swapB]; + searchOrder[swapB] = temp; + } +} + +/* + GRAPHICS + Fade out the car based on a trail length or timing fade away +*/ + +void standbyGraphics() { + + // circle around with a trail + // 2 with trails on opposite sides + word rotation = (millis() / 3) % 360; + byte head = rotation / 60; + + FOREACH_FACE(f) { + + byte distFromHead = (6 + head - f) % 6; // returns # of positions away from the head + + if (distFromHead == 0 || distFromHead == 3) { + setColorOnFace(ORANGE, f); + } + } +} + +void resetIsCarPassed() { + FOREACH_FACE(f) { + isCarPassed[f] = false; + } +} + +bool didCarPassFace(byte face, byte pos, byte from, byte to) { + + // are we going straight, turning left, or turning right + byte center; + byte faceRotated = (6 + face - from) % 6; + byte dir = ((from + 6 - to) % 6) - 2; + + return pos > turns[dir][faceRotated]; +} diff --git a/libraries/Examples03/examples/WHAM/WHAM.ino b/libraries/Examples03/examples/WHAM/WHAM.ino new file mode 100644 index 00000000..32f1c6d3 --- /dev/null +++ b/libraries/Examples03/examples/WHAM/WHAM.ino @@ -0,0 +1,590 @@ +/* + WHAM! + by Move38, Inc. 2019 + Lead development by Dan King + original game by Dan King, Jonathan Bobrow + based on concept for Whack-A-Mole + + Rules: https://github.com/Move38/WHAM/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +enum gameStates {SETUP, GAME, DEATH, VICTORY};//cycles through the game +byte gameState = SETUP; +byte grassHue = 70; + +#define DIFFICULTY_MIN 1 //starting round +#define DIFFICULTY_MAX 15 //final difficulty level, though the game continues +byte difficultyLevel = 0; + +#define VICTORY_ROUND_COUNT 30 +enum goVictorySignals {INERT, WAVE, SETTLE};//used in game state +byte goVictorySignal = INERT; +byte roundCounter = 0; +Timer roundTimer; +bool roundActive = false; +byte lifeSignal = 0; + +enum goStrikeSignals {INERT0, INERT1, INERT2, GO, RESOLVING};//used in game state +byte goStrikeSignal = INERT0; +bool isRippling = false; +#define RIPPLING_INTERVAL 500 +Timer ripplingTimer; + +#define SETUP_FADE_UP_INTERVAL 500 +#define SETUP_RED_INTERVAL 1000 +#define SETUP_FADE_DELAY 3000 +byte setupFadeFace; +Timer setupFadeTimer; +word redTime; + +#define EMERGE_INTERVAL_MAX 2000 +#define EMERGE_INTERVAL_MIN 500 +#define EMERGE_DRIFT 200 +Timer emergeTimer;//triggered when the GO signal is received, interval shrinks as difficultyLevel increases + +bool isAbove = false; +#define ABOVE_INTERVAL_MAX 3000 +#define ABOVE_INTERVAL_MIN 1500 +Timer aboveTimer; + +bool isFlashing = false; +#define FLASHING_INTERVAL 500 +Timer flashingTimer; + +bool isStriking = false; +#define STRIKING_INTERVAL 200 +Timer strikingTimer; +byte strikes = 0;//communicated in game mode, incremented which each strike +Color strikeColors[3] = {YELLOW, ORANGE, RED}; + +bool isSourceOfDeath; +long timeOfDeath; +#define DEATH_ANIMATION_INTERVAL 750 + +void setup() { + // put your setup code here, to run once: + randomize(); + setupFadeFace = random(5); + redTime = SETUP_RED_INTERVAL + random(SETUP_RED_INTERVAL / 2); + setupFadeTimer.set(redTime + SETUP_FADE_UP_INTERVAL + random(SETUP_FADE_DELAY)); +} + +void loop() { + // put your main code here, to run repeatedly: + switch (gameState) { + case SETUP: + setupLoop(); + setupDisplayLoop(); + break; + case GAME: + gameLoop(); + gameDisplayLoop(); + break; + case DEATH: + deathLoop(); + deathDisplayLoop(); + break; + case VICTORY: + victoryLoop(); + victoryDisplayLoop(); + } + + //dump button data + buttonSingleClicked(); + buttonDoubleClicked(); + buttonPressed(); + + //do communication (done here to avoid miscommunication) + byte sendData; + switch (gameState) { + case SETUP: + sendData = (gameState << 4); + break; + case GAME: + sendData = (gameState << 4) + (goStrikeSignal << 1) + (lifeSignal); + break; + case DEATH: + sendData = (gameState << 4); + break; + case VICTORY: + sendData = (gameState << 4) + (goVictorySignal << 2); + } + setValueSentOnAllFaces(sendData); +} + +////////////// +//GAME LOOPS// +////////////// + +void setupLoop() { + + //listen for neighbors with higher player counts and conform + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//a neighbor! + byte neighborGameState = getGameState(getLastValueReceivedOnFace(f)); + } + } + + //listen for double-clicks to move into game mode + if (buttonDoubleClicked()) { + gameState = GAME; + roundActive = false; + roundTimer.set(EMERGE_INTERVAL_MAX); + isFlashing = true; + flashingTimer.set(FLASHING_INTERVAL); + } + + //listen for neighbors in game mode to move to game mode myself and become receiver + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//a neighbor! + byte neighborGameState = getGameState(getLastValueReceivedOnFace(f)); + if (neighborGameState == GAME) { + gameState = GAME; + roundActive = false; + roundTimer.set(EMERGE_INTERVAL_MAX); + isFlashing = true; + flashingTimer.set(FLASHING_INTERVAL); + } + } + } +} + +void gameLoop() { + + //start new round? + if (!roundActive) { + bool newRoundInitiated = false; + + //look for neighbors commanding us to start a round + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //neighbor! + if (getGameState(getLastValueReceivedOnFace(f)) == GAME) { //a trusted neighbor + if (getGoStrikeSignal(getLastValueReceivedOnFace(f)) == GO) {//telling us to go + newRoundInitiated = true; + } + } + } + } + + //listen for internal timer telling us to go + if (!roundActive && roundTimer.isExpired()) { + newRoundInitiated = true; + } + + //we get to start a new round! + if (newRoundInitiated) { + roundCounter++; + if (roundCounter > VICTORY_ROUND_COUNT) {//GAME OVER: VICTORY + gameState = VICTORY; + // word emergeInterval = map(difficultyLevel, DIFFICULTY_MIN, DIFFICULTY_MAX, EMERGE_INTERVAL_MAX, EMERGE_INTERVAL_MIN); + word emergeInterval = EMERGE_INTERVAL_MAX - ((difficultyLevel * (EMERGE_INTERVAL_MAX - EMERGE_INTERVAL_MIN)) / (DIFFICULTY_MAX - DIFFICULTY_MIN)); + word driftVal = (EMERGE_DRIFT / 3) * random(3); + roundTimer.set(emergeInterval + driftVal); + } else {//GAME IS STILL ON + if (difficultyLevel < DIFFICULTY_MAX) { + difficultyLevel++; + } + + //we also need to declare out intention to go up + lifeSignal = random(1);//using this in place of real stuff for a moment + + isRippling = true; + ripplingTimer.set(RIPPLING_INTERVAL); + goStrikeSignal = GO; + roundActive = true; + + // word emergeInterval = map(difficultyLevel, DIFFICULTY_MIN, DIFFICULTY_MAX, EMERGE_INTERVAL_MAX, EMERGE_INTERVAL_MIN); + word emergeInterval = EMERGE_INTERVAL_MAX - (((difficultyLevel - DIFFICULTY_MIN) * (EMERGE_INTERVAL_MAX - EMERGE_INTERVAL_MIN)) / (DIFFICULTY_MAX - DIFFICULTY_MIN)); + emergeTimer.set(emergeInterval + random(EMERGE_DRIFT)); + // word aboveInterval = map(difficultyLevel, DIFFICULTY_MIN, DIFFICULTY_MAX, ABOVE_INTERVAL_MAX, ABOVE_INTERVAL_MIN); + word aboveInterval = ABOVE_INTERVAL_MAX - (((difficultyLevel - DIFFICULTY_MIN) * (ABOVE_INTERVAL_MAX - ABOVE_INTERVAL_MIN)) / (DIFFICULTY_MAX - DIFFICULTY_MIN)); + + word roundInterval = emergeInterval + EMERGE_DRIFT + aboveInterval + FLASHING_INTERVAL + emergeInterval; + roundTimer.set(roundInterval); + } + } + } + + //resolve goStrikeSignal propogation + if (goStrikeSignal == GO) {//we are going. Do all our neighbors know this? + bool canResolve = true; + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (isGoStrikeInert(getGoStrikeSignal(getLastValueReceivedOnFace(f)))) {//this neighbor has not been told + canResolve = false; + } + } + } + + if (canResolve) { + goStrikeSignal = RESOLVING; + } + } else if (goStrikeSignal == RESOLVING) { + bool canInert = true; + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (getGoStrikeSignal(getLastValueReceivedOnFace(f)) == GO) {//this neighbor still has work to do + canInert = false; + } + } + } + + if (canInert) { + switch (strikes) { + case 0: + goStrikeSignal = INERT0; + break; + case 1: + goStrikeSignal = INERT1; + break; + case 2: + goStrikeSignal = INERT2; + break; + } + } + } + + //PLAY THE GAME OF LIFE + if (isGoStrikeInert(goStrikeSignal) && roundActive) { + byte neighborsUp = 0; + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //neighbor! + if (getLifeSignal(getLastValueReceivedOnFace(f)) == 1) { + neighborsUp++; + } + } + } + + if (neighborsUp == 0) {//too few up neighbors. ARISE! + lifeSignal = 1; + } else if (neighborsUp > 3) {//too many neighbors. DESCEND! + lifeSignal = 0; + } + } + + //listen for my emerge timer to expire so I can go above + if (roundActive && emergeTimer.isExpired()) { + roundActive = false; + + if (lifeSignal == 1) { + isAbove = true; + // word fadeTime = map(difficultyLevel, DIFFICULTY_MIN, DIFFICULTY_MAX, ABOVE_INTERVAL_MAX, ABOVE_INTERVAL_MIN); + word fadeTime = ABOVE_INTERVAL_MAX - (((difficultyLevel - DIFFICULTY_MIN) * (ABOVE_INTERVAL_MAX - ABOVE_INTERVAL_MIN)) / (DIFFICULTY_MAX - DIFFICULTY_MIN)); + aboveTimer.set(fadeTime); + } + + } + + //listen for button presses + if (buttonPressed()) { + if (isAbove) { //there is a mole here + isAbove = false;//kill the mole + isFlashing = true;//start the flash + flashingTimer.set(FLASHING_INTERVAL); + roundActive = false; + } else {//there is no mole here + strikes++; + //we need to check if we are in an inert state and update that + if (isGoStrikeInert(goStrikeSignal)) { //update our INERT type + switch (strikes) { + case 0: + goStrikeSignal = INERT0; + break; + case 1: + goStrikeSignal = INERT1; + break; + case 2: + goStrikeSignal = INERT2; + break; + } + } + strikingTimer.set(STRIKING_INTERVAL); + isStriking = true; + if (strikes == 3) { + gameState = DEATH; + isSourceOfDeath = true; + } + + + } + }//end button press check + + //listen for strikes from neighbors + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //neighbor! + byte neighborGameState = getGameState(getLastValueReceivedOnFace(f)); + if (neighborGameState == GAME) { //this neighbor is in game state, so we can trust their communication + if (isGoStrikeInert(getGoStrikeSignal(getLastValueReceivedOnFace(f)))) {//this is an inert neighbror, they communicating + byte neighborStrikes = getStrikes(getLastValueReceivedOnFace(f)); + if (neighborStrikes > strikes) { //that neighbor is reporting more strikes than me. Take that number + strikes = neighborStrikes; + if (isGoStrikeInert(goStrikeSignal)) { //update our INERT type + switch (strikes) { + case 0: + goStrikeSignal = INERT0; + break; + case 1: + goStrikeSignal = INERT1; + break; + case 2: + goStrikeSignal = INERT2; + break; + } + } + isStriking = true; + strikingTimer.set(STRIKING_INTERVAL); + } + } + } + } + } + + //listen for my mole to cause death + if (isAbove && aboveTimer.isExpired()) { //my fade timer expired and I haven't been clicked, so... + gameState = DEATH; + isSourceOfDeath = true; + timeOfDeath = millis(); + } + + //listen for death + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//a neighbor! + byte neighborGameState = getGameState(getLastValueReceivedOnFace(f)); + if (neighborGameState == DEATH) { + gameState = DEATH; + isSourceOfDeath = false; + timeOfDeath = millis(); + } + } + }//end death check +} + +void deathLoop() { + setupCheck(); +} + +void victoryLoop() { + //listen for neighbors in death, because that maybe could happen but probably won't + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + if (getGameState(getLastValueReceivedOnFace(f)) == DEATH) { + gameState = DEATH; + isSourceOfDeath = false; + } + } + } + + //randomly flash + if (emergeTimer.isExpired()) { + isFlashing = true; + flashingTimer.set(FLASHING_INTERVAL / 2); + + //reset the timer + emergeTimer.set((FLASHING_INTERVAL / 2) + random(FLASHING_INTERVAL / 2)); + } + + setupCheck(); +} + +void setupCheck() { + //listen for double clicks to go back to setup + if (buttonDoubleClicked()) { + gameState = SETUP; + resetAllVariables(); + } + + //listen for signal to go to setup + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//a neighbor! + byte neighborGameState = getGameState(getLastValueReceivedOnFace(f)); + if (neighborGameState == SETUP) { + gameState = SETUP; + resetAllVariables(); + } + } + }//end setup check +} + +void resetAllVariables() { + //RESET ALL GAME VARIABLES + goStrikeSignal = INERT0; + goVictorySignal = INERT; + difficultyLevel = 0; + roundActive = false; + roundCounter = 0; + strikes = 0; + lifeSignal = 0; + isSourceOfDeath = false; + isAbove = false; + isFlashing = false; + isRippling = false; + isStriking = false; +} + +///////////////// +//DISPLAY LOOPS// +///////////////// + +void setupDisplayLoop() { + + setColor(makeColorHSB(grassHue, 255, 255)); + + if (setupFadeTimer.isExpired()) { + setupFadeFace = (setupFadeFace + random(4)) % 6; + redTime = SETUP_RED_INTERVAL + random(SETUP_RED_INTERVAL / 2); + setupFadeTimer.set(redTime + SETUP_FADE_UP_INTERVAL + random(SETUP_FADE_DELAY)); + } + + Color fadeColor; + byte saturation; + + if (setupFadeTimer.getRemaining() < redTime + SETUP_FADE_UP_INTERVAL) {//we are inside the animation + if (setupFadeTimer.getRemaining() < SETUP_FADE_UP_INTERVAL) {//we are fading from white to green + saturation = 255 - map(setupFadeTimer.getRemaining(), 0, SETUP_FADE_UP_INTERVAL, 0, 255); + fadeColor = makeColorHSB(grassHue, saturation, 255); + } else {//we are red + fadeColor = RED; + } + + setColorOnFace(fadeColor, setupFadeFace); + } +} + +void gameDisplayLoop() { + //do each animation + if (isFlashing) {//fade from white to green based on flashingTimer + // byte currentSaturation = map(flashingTimer.getRemaining(), 0, FLASHING_INTERVAL, 255, 0); + byte currentSaturation = 255 - ((255 * flashingTimer.getRemaining()) / FLASHING_INTERVAL); + setColor(makeColorHSB(grassHue, currentSaturation, 255)); + } else if (isAbove) {//fade from [color] to off based on aboveTimer + long currentInterval = ABOVE_INTERVAL_MAX - (((difficultyLevel - DIFFICULTY_MIN) * (ABOVE_INTERVAL_MAX - ABOVE_INTERVAL_MIN) ) / (DIFFICULTY_MAX - DIFFICULTY_MIN)); + byte currentFullPips = (aboveTimer.getRemaining()) / (currentInterval / 6);//6 >>> 0 + byte dimmingPipBrightness = map(aboveTimer.getRemaining() - ((currentInterval / 6) * currentFullPips), 0, currentInterval / 6, 0, 255); + + FOREACH_FACE(f) { + if (f < currentFullPips) { + setColorOnFace(RED, f); + } else if (f == currentFullPips) { + setColorOnFace(dim(RED, dimmingPipBrightness), f); + } else { + setColorOnFace(OFF, f); + } + //should my face be all the way on? + } + + // long currentTime = aboveTimer.getRemaining(); + // byte brightnessSubtraction = 255 - ((255 * currentTime) / currentInterval); + // brightnessSubtraction = (brightnessSubtraction * brightnessSubtraction) / 255; + // brightnessSubtraction = (brightnessSubtraction * brightnessSubtraction) / 255; + // byte currentBrightness = 255 - brightnessSubtraction; + // setColor(dim(currentColor, currentBrightness)); + + } else if (isStriking) {//flash [color] for a moment + //which color? depends on number of strikes + setColor(strikeColors[strikes - 1]); + } else if (isRippling) {//randomize green hue for a moment + FOREACH_FACE(f) { + setColorOnFace(makeColorHSB(grassHue, 255, random(50) + 205), f); + //setColorOnFace(makeColorHSB(grassHue + random(20), 255, 255), f); + } + } else {//just be green + setColor(makeColorHSB(grassHue, 255, 255)); + } + + //resolve non-death animation timers + if (flashingTimer.isExpired()) { + isFlashing = false; + } + if (strikingTimer.isExpired()) { + isStriking = false; + } + if (ripplingTimer.isExpired()) { + isRippling = false; + } +} + +void deathDisplayLoop() { + long currentAnimationPosition = (millis() - timeOfDeath) % (DEATH_ANIMATION_INTERVAL * 2); + byte animationValue; + if (currentAnimationPosition < DEATH_ANIMATION_INTERVAL) { //we are in the down swing (255 >> 0) + // animationValue = map(currentAnimationPosition, 0, DEATH_ANIMATION_INTERVAL, 255, 0); + animationValue = 255 - ((255 * currentAnimationPosition) / DEATH_ANIMATION_INTERVAL); + } else {//we are in the up swing (0 >> 255) + // animationValue = map(currentAnimationPosition - DEATH_ANIMATION_INTERVAL, 0, DEATH_ANIMATION_INTERVAL, 0, 255); + animationValue = ((255 * (currentAnimationPosition - DEATH_ANIMATION_INTERVAL)) / DEATH_ANIMATION_INTERVAL); + } + + if (isSourceOfDeath) { + setColor(makeColorHSB(0, animationValue, 255)); + } else { + setColor(makeColorHSB(0, 255, animationValue)); + } +} + +void victoryDisplayLoop() { + if (isFlashing) {//fade from white to green based on flashingTimer + byte currentSaturation = 255 - map(flashingTimer.getRemaining(), 0, FLASHING_INTERVAL, 0, 255); + setColor(makeColorHSB(grassHue, currentSaturation, 255)); + } + + //resolve flashing animation timers + if (flashingTimer.isExpired()) { + isFlashing = false; + } +} + +///////////////// +//COMMUNICATION// +///////////////// + +byte getGameState(byte data) {//1st and 2nd bit + return (data >> 4); +} + +byte getGoStrikeSignal(byte data) { + return ((data >> 1) & 7); +} + +bool isGoStrikeInert (byte data) {//is this neighbor in an inert state? + if (data == INERT0 || data == INERT1 || data == INERT2) { + return true; + } else { + return false; + } +} + +byte getStrikes(byte data) {//returns a number of strikes + byte s = getGoStrikeSignal(data); + switch (s) { + case INERT0: + return 0; + break; + case INERT1: + return 1; + break; + case INERT2: + return 2; + break; + default: + return 0; + break; + } +} + +byte getLifeSignal(byte data) {//6th bit + return (data & 1); +} + +byte getGoVictorySignal(byte data) {//3rd and 4th bit + return ((data >> 2) & 3); +} diff --git a/libraries/Examples03/examples/Widgets/Widgets.ino b/libraries/Examples03/examples/Widgets/Widgets.ino new file mode 100644 index 00000000..9210ebb5 --- /dev/null +++ b/libraries/Examples03/examples/Widgets/Widgets.ino @@ -0,0 +1,478 @@ +/* + Widgets + by Move38, Inc. 2019 + Lead development by Dan King + original game by Dan King, Vanilla Liu, Justin Ha, Junege Hong, Kristina Atanasoski, Jonathan Bobrow + + Rules: https://github.com/Move38/Widgets/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +////GENERIC VARIABLES//// +enum widgets {DICE, SPINNER, COIN, TIMER}; +byte currentWidget = DICE; +enum signalTypes {INERT, GO, RESOLVE}; +byte pushSignal = INERT; +byte goSignal = INERT; + +Timer animTimer; +byte framesRemaining = 0; +byte outcomeColors[6] = {0, 21, 42, 85, 170, 212}; +byte currentOutcome = 1; + +bool bChange = false; + +////WIDGET VARIABLES//// +#define DICE_ROLL_INTERVAL 75 +#define COIN_FLIP_INTERVAL 150 + +#define SPINNER_INTERVAL_RESET 25 +#define SPINNER_ACTIVE_DIM 196 +#define SPINNER_FINAL_DIM 128 +word spinInterval = SPINNER_INTERVAL_RESET; +Timer spinnerFinalPulseTimer; +#define SPINNER_PULSE_DURATION 1000 + +enum timerModes {SETTING, TIMING, ALARM}; +byte timerMode = SETTING; +#define TIMER_SETTING_TICK 250 + +void setup() { + randomize(); + startWidget(); +} + +void loop() { + + // discard the change mode from a force sleep + if (hasWoken()) { + bChange = false; + if (currentWidget == DICE) { + diceFaceDisplay(currentOutcome); + } else if (currentWidget == SPINNER) { + spinnerFaceDisplay(currentOutcome, true); + } + } + + //listen for button clicks + if (buttonSingleClicked()) { + if (currentWidget == TIMER) {//we're in the timer + if (timerMode == SETTING) {//it's not currently going + if (currentOutcome == 5) { + currentOutcome = 1; + animTimer.set(TIMER_SETTING_TICK * (currentOutcome + 3)); + } else { + currentOutcome++; + animTimer.set(TIMER_SETTING_TICK * (currentOutcome + 3)); + } + } + } else { + startWidget(); + } + } + + if (buttonLongPressed()) { + bChange = true; + } + + if (buttonReleased() && bChange) { + changeWidget((currentWidget + 1) % 4); + bChange = false; + } + + //listen for signals + pushLoop(); + goLoop(); + + //do things + switch (currentWidget) { + case DICE: + diceLoop(); + break; + case SPINNER: + spinnerLoop(); + break; + case COIN: + coinLoop(); + break; + case TIMER: + timerLoop(); + break; + } + + //set up communication + byte sendData = (currentWidget << 4) + (pushSignal << 2) + (goSignal); + setValueSentOnAllFaces(sendData); + + //set it to all white if waiting for longpress release + if (bChange) { + setColor(WHITE); + } +} + +void changeWidget(byte targetWidget) { + currentWidget = targetWidget; + //currentWidget = (currentWidget + 1) % 4; + pushSignal = GO; + if (currentWidget == TIMER) { + currentOutcome = 1; + timerMode = SETTING; + animTimer.set(TIMER_SETTING_TICK * (currentOutcome + 3)); + } else { + startWidget(); + } +} + +void pushLoop() { + if (pushSignal == INERT) { + //look for neighbors trying to push me + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getPushSignal(neighborData) == GO) { + //this neighbor is pushing a widget + changeWidget(getCurrentWidget(neighborData)); + //currentWidget = getCurrentWidget(neighborData); + pushSignal = GO; + //if we're not becoming a TIMER, we gotta also roll/spin/flip + if (currentWidget != TIMER) { + startWidget(); + } else { + currentOutcome = 1; + animTimer.set(TIMER_SETTING_TICK * (currentOutcome + 3)); + } + } + } + } + } else if (pushSignal == GO) { + pushSignal = RESOLVE;//this is corrected within the face loop if it shouldn't happen + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getPushSignal(neighborData) == INERT) { + pushSignal = GO;//we should still be in GO, there are neighbors to inform + } + } + } + } else if (pushSignal == RESOLVE) { + pushSignal = INERT;//this is corrected within the face loop if it shouldn't happen + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getPushSignal(neighborData) == GO) { + pushSignal = RESOLVE;//we should still be in RESOLVE, there are neighbors to inform + } + } + } + } +} + +void goLoop() { + if (goSignal == INERT) { + //look for neighbors trying to push me + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getGoSignal(neighborData) == GO) { + //this neighbor is pushing a go signal + startWidget(); + } + } + } + } else if (goSignal == GO) { + goSignal = RESOLVE;//this is corrected within the face loop if it shouldn't happen + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getGoSignal(neighborData) == INERT) { + goSignal = GO;//we should still be in GO, there are neighbors to inform + } + } + } + } else if (goSignal == RESOLVE) { + goSignal = INERT;//this is corrected within the face loop if it shouldn't happen + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) {//neighbor! + byte neighborData = getLastValueReceivedOnFace(f); + if (getGoSignal(neighborData) == GO) { + goSignal = RESOLVE;//we should still be in RESOLVE, there are neighbors to inform + } + } + } + } +} + +void startWidget() { + switch (currentWidget) { + case DICE: + //totalAnimationTimer.set(DICE_ROLL_DURATION); + currentOutcome = random(5) + 1; + framesRemaining = 20 + random(5); + animTimer.set(DICE_ROLL_INTERVAL); + diceFaceDisplay(currentOutcome); + break; + case SPINNER: + framesRemaining = random(11) + 36; + spinInterval = SPINNER_INTERVAL_RESET; + animTimer.set(spinInterval); + break; + case COIN: + framesRemaining = random(3) + 22; + if (animTimer.isExpired()) {//reset the timer if it isn't currently going + animTimer.set(COIN_FLIP_INTERVAL); + if (currentOutcome == 0) { + currentOutcome = 1; + } else { + currentOutcome = 0; + } + } + break; + } + goSignal = GO;//happens regardless of type +} + +void diceLoop() { + if (animTimer.isExpired() && framesRemaining > 0) { + animTimer.set(DICE_ROLL_INTERVAL); + framesRemaining--; + currentOutcome += 5; + if (currentOutcome > 6) { + currentOutcome = currentOutcome % 6; + } + diceFaceDisplay(currentOutcome); + } +} + +void diceFaceDisplay(byte val) { + byte orientFace = random(5); + setColor(OFF); + switch (val) { + case 1: + setColorOnFace(RED, orientFace); + break; + case 2: + setColorOnFace(RED, orientFace); + setColorOnFace(RED, (orientFace + 3) % 6); + break; + case 3: + setColorOnFace(RED, orientFace); + setColorOnFace(RED, (orientFace + 2) % 6); + setColorOnFace(RED, (orientFace + 4) % 6); + break; + case 4: + setColor(RED); + setColorOnFace(OFF, orientFace); + setColorOnFace(OFF, (orientFace + 3) % 6); + break; + case 5: + setColor(RED); + setColorOnFace(OFF, orientFace); + break; + case 6: + setColor(RED); + break; + } +} + +void spinnerLoop() { + if (animTimer.isExpired() && framesRemaining > 0) {//actively spinning + framesRemaining--; + //determine how long the next frame is + if (framesRemaining < 24) {//we're in the slow down + spinInterval = (spinInterval * 23) / 20; + } + animTimer.set(spinInterval); + + currentOutcome = (currentOutcome + 1) % 6; + spinnerFaceDisplay(currentOutcome, true); + if (framesRemaining == 0) { //this is the last frame + spinnerFinalPulseTimer.set(SPINNER_PULSE_DURATION); + } + } else if (framesRemaining == 0) { //just hanging out + spinnerFaceDisplay(currentOutcome, false); + } +} + +void spinnerFaceDisplay(byte val, bool spinning) { + if (spinning) { + FOREACH_FACE(f) { + setColorOnFace(makeColorHSB(outcomeColors[f], 255, SPINNER_ACTIVE_DIM), f); + } + setColorOnFace(WHITE, val); + } else { + if (spinnerFinalPulseTimer.isExpired()) { + spinnerFinalPulseTimer.set(SPINNER_PULSE_DURATION); + } + byte saturation = sin8_C(map(spinnerFinalPulseTimer.getRemaining(), 0, SPINNER_PULSE_DURATION, 0, 255)); + setColorOnFace(makeColorHSB(outcomeColors[val], saturation, 255), val); + } +} + +void coinLoop() { + if (animTimer.isExpired() && framesRemaining > 0) { + framesRemaining--; + animTimer.set(COIN_FLIP_INTERVAL); + //change the outcome from 0 to 1 and back + if (currentOutcome == 0) { + currentOutcome = 1; + } else { + currentOutcome = 0; + } + } + + if (framesRemaining == 0) { + coinDisplay(true); + } else { + coinDisplay(false); + } +} + +void coinDisplay(bool finalFlip) { + Color faceColor; + if (currentOutcome == 0) { + faceColor = YELLOW; + } else { + faceColor = WHITE; + } + + byte animPosition = COIN_FLIP_INTERVAL - animTimer.getRemaining(); + byte leftStart = 0; + byte centerStart = COIN_FLIP_INTERVAL / 6; + byte rightStart = COIN_FLIP_INTERVAL / 3; + byte edgeDuration = (COIN_FLIP_INTERVAL / 3) * 2; + + setColor(OFF); + + if (animPosition >= leftStart && animPosition <= leftStart + edgeDuration) { + byte brightness = sin8_C(map(animPosition, leftStart, leftStart + edgeDuration, 0, 255)); + setColorOnFace(dim(faceColor, brightness), 0); + setColorOnFace(dim(faceColor, brightness), 1); + } + + if (finalFlip && animPosition >= leftStart + (edgeDuration / 2)) { + setColorOnFace(faceColor, 0); + setColorOnFace(faceColor, 1); + } + + if (animPosition >= centerStart && animPosition <= centerStart + edgeDuration) { + byte brightness = sin8_C(map(animPosition, centerStart, centerStart + edgeDuration, 0, 255)); + setColorOnFace(dim(faceColor, brightness), 2); + setColorOnFace(dim(faceColor, brightness), 5); + } + + if (finalFlip && animPosition >= centerStart + (edgeDuration / 2)) { + setColorOnFace(faceColor, 2); + setColorOnFace(faceColor, 5); + } + + if (animPosition >= rightStart && animPosition <= rightStart + edgeDuration) { + byte brightness = sin8_C(map(animPosition, rightStart, rightStart + edgeDuration, 0, 255)); + setColorOnFace(dim(faceColor, brightness), 3); + setColorOnFace(dim(faceColor, brightness), 4); + } + + if (finalFlip && animPosition >= rightStart + (edgeDuration / 2)) { + setColorOnFace(faceColor, 3); + setColorOnFace(faceColor, 4); + } + +} + +void timerLoop() { + + if (buttonDoubleClicked()) { + switch (timerMode) { + case SETTING: + animTimer.set((currentOutcome * 60000) - 1); + timerMode = TIMING; + break; + case TIMING: + timerMode = SETTING; + animTimer.set(0); + break; + case ALARM: + timerMode = SETTING; + animTimer.set(0); + break; + } + } + + if (animTimer.isExpired()) { + switch (timerMode) { + case SETTING: + animTimer.set(TIMER_SETTING_TICK * (currentOutcome + 3)); + break; + case TIMING: + timerMode = ALARM; + animTimer.set(100); + break; + case ALARM: + animTimer.set(100); + break; + } + } + + timerDisplay(); +} + +void timerDisplay() { + switch (timerMode) { + case SETTING: + //set color thing going + { + byte animFrame = ((TIMER_SETTING_TICK * (currentOutcome + 3)) - animTimer.getRemaining()) / TIMER_SETTING_TICK;//0-X + if (animFrame == 0) {//the off frame + setColor(OFF); + } else {//the rest of the frames + FOREACH_FACE(f) { + //should this face be on? + if (f < animFrame && f <= currentOutcome) { + setColorOnFace(makeColorHSB(outcomeColors[currentOutcome - 1], 255, 255), f); + } + } + } + } + + setColorOnFace(WHITE, 0); + break; + case TIMING: + //set overall face color + { + byte minutesRemaining = animTimer.getRemaining() / 60000;//0-4 + byte progressBrightness = map((minutesRemaining * 60000) - animTimer.getRemaining(), 0, 60000, 127, 255); + setColor(makeColorHSB(outcomeColors[minutesRemaining], 255, progressBrightness)); + + //set the little ticking color on a specific face + if (animTimer.getRemaining() % 1000 <= 500) { + setColorOnFace(WHITE, 5 - ((animTimer.getRemaining() / 1000) % 6)); + } + } + break; + case ALARM: + if (animTimer.getRemaining() <= 50) { + setColor(makeColorHSB(outcomeColors[0], 255, 128)); + } else { + setColor(makeColorHSB(outcomeColors[0], 255, 255)); + } + break; + } +} + +byte getCurrentWidget(byte data) { + return (data >> 4); +} + +byte getPushSignal(byte data) { + return ((data >> 2) & 3); +} + +byte getGoSignal(byte data) { + return (data & 3); +} diff --git a/libraries/Examples03/examples/ZenFlow/ZenFlow.ino b/libraries/Examples03/examples/ZenFlow/ZenFlow.ino new file mode 100644 index 00000000..00a5f04c --- /dev/null +++ b/libraries/Examples03/examples/ZenFlow/ZenFlow.ino @@ -0,0 +1,465 @@ +/* + ZenFlow + by Move38, Inc. 2019 + Lead development by Dan King + original game by Dan King, Jonathan Bobrow + + Rules: https://github.com/Move38/ZenFlow/blob/master/README.md + + -------------------- + Blinks by Move38 + Brought to life via Kickstarter 2018 + + @madewithblinks + www.move38.com + -------------------- +*/ + +//#include "Serial.h" +//ServicePortSerial Serial; + +enum modeStates {SPREAD, CONNECT}; +byte currentMode; + +enum commandStates {INERT, SEND_PERSIST, SEND_SPARKLE, RESOLVING}; +byte commandState;//this is the state sent to neighbors +byte internalState;//this state is internal, and does not get sent + +// Colors by hue +byte hues[7] = {0, 21, 42, 85, 110, 170, 210}; +byte currentHue; +byte sparkleOffset[6] = {0, 3, 5, 1, 4, 2}; + +#define SEND_DELAY 100 +#define SEND_DURATION 800 + +uint32_t timeOfSend = 0; +uint32_t timeOfPress = 0; // use this to show the button was pressed in connect mode + +Timer sendTimer; +Timer transitionTimer; + +byte sendData; + +bool bChangeMode = false; + +void setup() { + // put your setup code here, to run once: + //Serial.begin(); + + currentMode = SPREAD; + commandState = INERT; + internalState = INERT; + currentHue = 0; + + randomize(); +} + +void loop() { + + // discard the change mode from a force sleep + if (hasWoken()) { + bChangeMode = false; + } + + // BUTTON HANDLING + //if single clicked, move to SEND_PERSIST + if (buttonSingleClicked()) { + //Serial.println("Single Click Registered"); + if (currentMode == SPREAD) { + changeInternalState(SEND_PERSIST); + currentHue = nextHue(currentHue); + //Serial.print("hue: "); + //Serial.println(currentHue); + } + if (currentMode == CONNECT) { + timeOfPress = millis(); + } + } + + //if double clicked, move to SEND_SPARKLE + if (buttonDoubleClicked()) { + //Serial.println("Double Click Registered"); + + if (currentMode == SPREAD) { + changeInternalState(SEND_SPARKLE); + currentHue = millis() % COUNT_OF(hues); //generate a random color + //Serial.print("hue: "); + //Serial.println(currentHue); + } + if (currentMode == CONNECT) { + timeOfPress = millis(); + } + } + + //if long-pressed, move to CONNECT mode + if (buttonLongPressed()) { + bChangeMode = true; + } + + // if change mode + if (buttonReleased()) { + if (bChangeMode) { + switch (currentMode) { + case CONNECT: currentMode = SPREAD; break; + case SPREAD: currentMode = CONNECT; break; + } + // reset our states + changeInternalState(INERT); + commandState = INERT; + bChangeMode = false; + } + } + + + // decide which loop to run + if (currentMode == SPREAD) {//spread logic loops + switch (internalState) { + case INERT: + inertLoop(); + break; + case SEND_PERSIST: + sendPersistLoop(); + break; + case SEND_SPARKLE: + sendSparkleLoop(); + break; + case RESOLVING: + resolvingLoop(); + break; + } + } else if (currentMode == CONNECT) { + connectLoop(); + } + + //communicate full state + sendData = (currentMode << 5) + (commandState << 3) + (currentHue);//bit-shifted data to fit in 6 bits + setValueSentOnAllFaces(sendData); + + //do display work + if (currentMode == SPREAD) { + switch (internalState) { + case RESOLVING: + case INERT: + inertDisplay();//both inert and resolving share the same display logic + break; + case SEND_PERSIST: + sendPersistDisplay(); + break; + case SEND_SPARKLE: + sendSparkleDisplay(); + break; + } + } else if (currentMode == CONNECT) { + connectDisplay(); + } + + if (bChangeMode) { + setColor(WHITE); + } + +} + + +void inertLoop() { + + //now we evaluate neighbors. if our neighbor is in either send state, move to that send state + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (getMode(neighborData) == SPREAD) { + if (getCommandState(neighborData) == SEND_PERSIST) { + changeInternalState(SEND_PERSIST); + currentHue = getHue(neighborData);//you are going to take on the color of the commanding neighbor + break; // leave forloop + } + else if (getCommandState(neighborData) == SEND_SPARKLE) { + changeInternalState(SEND_SPARKLE); + currentHue = millis() % COUNT_OF(hues); //generate a random color + break; // leave forloop + } + } // end of mode in SPREAD + } // end of valid value + }// end of for each face +} + +void sendPersistLoop() { + //first, check if it's been long enough to send the command + if (sendTimer.isExpired()) { + commandState = internalState; + } + + if (transitionTimer.isExpired()) { + //now check neighbors. If they have all moved into SEND_PERSIST or RESOLVING, you can move to RESOLVING + //Only do this check if we are past the full display time + //if we've survived and are stil true, we transition to resolving + if (canResolve(SEND_PERSIST) && !isAlone()) { + changeInternalState(RESOLVING); + commandState = RESOLVING; + } + } +} + +bool canResolve(byte a) { + bool canResolve = true;//default to true, set to false in the face loop + FOREACH_FACE(f) { + byte neighborData = getLastValueReceivedOnFace(f);//we do this before checking for expired so we can use it to evaluate mode below + if (!isValueReceivedOnFaceExpired(f) && getMode(neighborData) == SPREAD) {//something is here, and in a compatible mode. We ignore the others + if (getCommandState(neighborData) != a && getCommandState(neighborData) != RESOLVING) {//it is neither of the acceptable states + canResolve = false; + } + } + }//end of face loop + return canResolve; +} + +void sendSparkleLoop() { + + //first, check if it's been long enough to send the command + if (sendTimer.isExpired()) { + commandState = internalState; + } + + //here we can transition from sparkle to persist spread + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { + byte neighborData = getLastValueReceivedOnFace(f); + if (getCommandState(neighborData) == SEND_PERSIST && getMode(neighborData) == SPREAD) { + changeInternalState(SEND_PERSIST); + currentHue = getHue(neighborData);//you are going to take on the color of the commanding neighbor + } + } + } + + //now check neighbors. If they have all moved into SEND_SPARKLE or RESOLVING, you can move to RESOLVING + //Only do this check if we are past the full display time + if (transitionTimer.isExpired()) { + //if we've survived and are stil true, we transition to resolving + if (canResolve(SEND_SPARKLE) && !isAlone()) { + changeInternalState(RESOLVING); + commandState = RESOLVING; + } + } +} + +void resolvingLoop() { + //check neighbors. If they have all moved into RESOLVING or INERT, you can move to INERT + bool canInert = true;//default to true, set to false in the face loop + FOREACH_FACE(f) { + byte neighborData = getLastValueReceivedOnFace(f);//we do this before checking for expired so we can use it to evaluate mode below + if (!isValueReceivedOnFaceExpired(f) && getMode(neighborData) == SPREAD) {//something is here, and in a compatible mode. We ignore the others + if (getCommandState(neighborData) != RESOLVING && getCommandState(neighborData) != INERT) {//it is neither of the acceptable states + canInert = false; + } + } + }//end of face loop + + //if we've survived and are stil true, we transition to resolving + if (canInert) { + changeInternalState(INERT); + commandState = INERT; + } +} + +void connectLoop() { + // nothing to do here +} + +/* + Data parsing +*/ +byte getMode(byte data) { + return (data >> 5); //the value in the first bit +} + +byte getCommandState (byte data) { + return ((data >> 3) & 3); //the value in the second and third bits +} + +byte getHue(byte data) { + return (data & 7); //the value in the fourth, fifth, and sixth bits +} +/* + End Data parsing +*/ + + + + +/* + Display Animations +*/ +// this code uses ~100 Bytes +void inertDisplay() { + + // setColor(makeColorHSB(hues[currentHue],255,255)); // much less interesting, but fits in memory + FOREACH_FACE(f) { + // minimum of 125, maximum of 255 + byte phaseShift = 60 * f; + byte amplitude = 55; + byte midline = 185; + byte rate = 4; + byte brightness = midline + (amplitude * sin8_C( (phaseShift + millis() / rate) % 255)) / 255; + byte saturation = 255; + + Color faceColor = makeColorHSB(hues[currentHue], 255, brightness); + setColorOnFace(faceColor, f); + } +} + +// this code uses ~200 Bytes +void sendPersistDisplay() { + // go full white and then fade to new color + uint32_t delta = millis() - timeOfSend; + + // show that we are charged up when alone + if (isAlone()) { + while (delta > SEND_DURATION * 3) { + delta -= SEND_DURATION * 3; + } + } + + if (delta > SEND_DURATION) { + delta = SEND_DURATION; + } + + FOREACH_FACE(f) { + // minimum of 125, maximum of 255 + byte phaseShift = 60 * f; + byte amplitude = 55; + byte midline = 185; + byte rate = 4; + byte brightness = midline + (amplitude * sin8_C ( (phaseShift + millis() / rate) % 255)) / 255; + byte saturation = map(delta, 0, SEND_DURATION, 0, 255); + + Color faceColor = makeColorHSB(hues[currentHue], saturation, brightness); + setColorOnFace(faceColor, f); + } +} + +// this code uses ~400 Bytes +void sendSparkleDisplay() { + // go full white and then fade to new color, pixel by pixel + uint32_t delta = millis() - timeOfSend; + + // show that we are charged up when alone + if (isAlone()) { + while (delta > SEND_DURATION * 3) { + delta -= SEND_DURATION * 3; + } + } + + if (delta > SEND_DURATION) { + delta = SEND_DURATION; + } + + byte offset = 50; + + FOREACH_FACE(f) { + + // if the face has started it's glow + uint16_t sparkleStart = sparkleOffset[f] * offset; + uint16_t sparkleEnd = sparkleStart + SEND_DURATION - (6 * offset); + + if ( delta > sparkleStart ) { + // minimum of 125, maximum of 255 + byte phaseShift = 60 * f; + byte amplitude = 55; + byte midline = 185; + byte rate = 4; + byte lowBri = midline + (amplitude * sin8_C( (phaseShift + millis() / rate) % 255)) / 255; + byte brightness; + byte saturation; + + if ( delta < sparkleEnd ) { + brightness = lowBri + 255 - map(delta, sparkleStart, sparkleStart + SEND_DURATION - (6 * offset), lowBri, 255); + saturation = map(delta, sparkleStart, sparkleStart + SEND_DURATION - (6 * offset), 0, 255); + } + else { + brightness = lowBri; + saturation = 255; + } + + Color faceColor = makeColorHSB(hues[currentHue], saturation, brightness); + setColorOnFace(faceColor, f); + } + } +} + +// this code uses ~300 Bytes +void connectDisplay() { + // go full white and then fade to new color, pixel by pixel + uint32_t delta = millis() - timeOfPress; + if ( delta > 300) { + delta = 300; + } + + if (isAlone()) { //so this is a lonely blink. we just set it to full white + // minimum of 125, maximum of 255 + byte amplitude = 30; + byte midline = 100; + byte rate = 4; + byte brightness = midline + (amplitude * sin8_C( (millis() / rate) % 255)) / 255; + + // if the button recently pressed, dip and then raise up + brightness = map(delta, 0, 300, 0, brightness); + + Color faceColor = makeColorHSB(0, 0, brightness); + setColor(faceColor); + + } else { + setColor(OFF);//later in the loop we'll add the colors + } + + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //something there + byte neighborData = getLastValueReceivedOnFace(f); + //now we figure out what is there and what to do with it + if (getMode(neighborData) == SPREAD) { //this neighbor is in spread mode. Just display the color they are on that face + byte brightness = map(delta, 0, 300, 0, 255); + Color faceColor = makeColorHSB(hues[getHue(neighborData)], 255, brightness); + setColorOnFace(faceColor, f); + } else if (getMode(neighborData) == CONNECT) { //this neighbor is in connect mode. Display a white connection + byte brightness = map(delta, 0, 300, 0, 255); + setColorOnFace(makeColorHSB(0, 0, brightness), f); + } + } + }//end of face loop +} + +/* + Data parsing +*/ +void changeInternalState(byte state) { + //this is here for the moment of transition. mostly housekeeping + switch (state) { + case INERT: + ////serial.println("I'm just hangin' tight."); + // nothing to do here + break; + case SEND_PERSIST: + //serial.println("show the next color and spread it"); + timeOfSend = millis(); + sendTimer.set(SEND_DELAY); + transitionTimer.set(SEND_DURATION); + break; + case SEND_SPARKLE: + //serial.println("a little sparkle for y'all"); + timeOfSend = millis(); + sendTimer.set(SEND_DELAY); + transitionTimer.set(SEND_DURATION); + break; + case RESOLVING: + //serial.println("I'm in my happy place :)"); + // nothing to do here + break; + } + + internalState = state; +} + + +byte nextHue(byte h) { + byte nextHue = (h + 1) % COUNT_OF(hues); + return nextHue; +} diff --git a/libraries/Examples03/library.properties b/libraries/Examples03/library.properties index df7bdece..ca588e93 100644 --- a/libraries/Examples03/library.properties +++ b/libraries/Examples03/library.properties @@ -2,9 +2,9 @@ name=03. Getting Playful version=1.0 author=Move38.com maintainer=Move38.com -sentence=See something blink +sentence=12 Games for Blinks paragraph= category=Other -url=https://github.com/bigjosh/Move38-Arduino-Platform +url=https://github.com/Move38/Blinks-SDK architectures=avr types=Arduino diff --git a/libraries/Examples05/examples/A-HexWorld_Adventure/A-HexWorld_Adventure.ino b/libraries/Examples05/examples/A-HexWorld_Adventure/A-HexWorld_Adventure.ino deleted file mode 100644 index 87be2aaf..00000000 --- a/libraries/Examples05/examples/A-HexWorld_Adventure/A-HexWorld_Adventure.ino +++ /dev/null @@ -1,639 +0,0 @@ -/* - * A tiny text adventure game running on (and integrated to) the Blinks hardware. - * - * Walk around, grab lights, try to reach enlightenment. Don't cheat and look at the source! - * - * Also nicely demonstrates... - * 1) how to fit a lot of static text into a program by storing it in flash. - * 2) how make an interactive text interface over the service port serial link. - * - * To play this game you'll need to connect a serial terminal to the Blinks service port. - * More info on how to do that here... - * https://github.com/bigjosh/Move38-Arduino-Platform/blob/master/Service%20Port.MD - */ - -#include "Serial.h" - -ServicePortSerial Serial; - -// Hack to get enum count: https://stackoverflow.com/a/2102673/3152071 - -// We have to rename SE as SEX because SE is used in io.h and clobbers us! Ahhh! - -enum DirectionsEnum { NE, E , SEX , SW , W , NW , UP , DOWN , DIRECTION_MAX = DOWN }; - -typedef struct { - const char *shortname; - const char *name; - const char key; - const int id; -} Direction; - -// There must be a less verbose way to do this, right? LMK! - -Direction directions[DIRECTION_MAX+1] = { - {"north-east" , "NE" , '1' , NE }, - {"East" , "E" , '2' , E }, - {"South-east" , "SE" , '3' , SEX }, - {"South-west" , "SW" , '4' , SW }, - {"West" , "W" , '5' , W }, - {"north-west" , "NW" , '6' , NW }, - {"Up" , "UP" , '7' , UP }, - {"Down" , "DOWN" , '8' , DOWN }, -}; - - -enum LocationKey { CENTER , NE_CITY , E_BAY , SE_BEACH , SW_DESERT , W_TEMPLE , NW_FOREST , SKY , HIGHER_PLANE , LOWER_PLANE , SKYSCRAPER, DEPTHS, MAX_LOCATION = DEPTHS , BLOCKED }; - -#define NO_PIXEL FACE_COUNT - -typedef struct { - - bool deadly; - - byte pixel; // If this place is a pixel, then the pixel number 0-5, otherwise NO_PIXEL - - const char *text; // REMEMBER this is in progmem! - - LocationKey next[DIRECTION_MAX+1]; - -} Location; - -// Oh this flashstring stuff is so ugly, but no way to put a flash string into a static initializer in Arduiono. :/ - -const char fs1[] PROGMEM = "You are standing on top of a weathered bronze plaque that reads \"Axis Mundi Survey Marker\". "; -const char fs2[] PROGMEM = "You are inside a huge shiny building building made of glass and steel."; -const char fs3[] PROGMEM = "You are in a pastoral estuary filled with creatures large and small. "; -const char fs4[] PROGMEM = "You are on a sandy beach. There is an insurmountable cultural barrier to the NW. "; -const char fs5[] PROGMEM = "You are in a desert. There is a large pile of ashes here."; -const char fs6[] PROGMEM = "You have stumbled into a sacred and/or profane place. An ornate starburst pattern mosaic is embedded in the floor beneath your feet. You can't shake the feeling that something profound happened (or will happen) here." ; -const char fs7[] PROGMEM = "You are in a lush and dense forest."; -const char fs8[] PROGMEM = "You feel yourself rising through space. The higher your loft, the larger becomes your field of vision. You are awestruck to find that your native world, all that you know and have ever known, exists solely on the surface of a small hexagon only 20mm on a side. Every place and every creature in your erstwhile universe therein, lays open to your view in miniature. As you mount even higher, lo, the secrets of the universe are bared before you."; -const char fs9[] PROGMEM = "You find yourself existing on a plane twice the size of the previous one."; -const char fs10[] PROGMEM = "You find yourself existing on a plane half the size of the previous one."; -const char fs11[] PROGMEM = "You take the elevator to the top floor, even though you have no idea what that means. Either this is madness or it is Hell. Unprepared for what you have just seen, your mind snaps and you awake. Was it all a dream?" ; -const char fs12[] PROGMEM = "You submerge into the brackish depths. You hear snapping and bubbling. Distracted by the sights and sounds, you forget to breathe and die unceremoniously in mud. Eons pass and the universe expands and then contracts and then expands again."; - -const char fs13[] PROGMEM = ""; - -// This is brittle, but false way to statically assign elements in C++ https://stackoverflow.com/questions/9048883/static-array-initialization-of-individual-elements-in-c?rq=1 - -Location locations[MAX_LOCATION+1] = { - - {/* CENTER */ false , NO_PIXEL, fs1 , { NE_CITY , E_BAY , SE_BEACH , SW_DESERT , W_TEMPLE, NW_FOREST , SKY , BLOCKED } }, - - {/* NE_CITY */ false , 5 , fs2 , {/*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ E_BAY, /*SW*/ CENTER, /*W*/ NW_FOREST , /*NW*/ BLOCKED , /*UP*/ SKYSCRAPER , /*DOWN*/ BLOCKED } }, - {/* E_BAY */ false , 0 , fs3 , {/*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ CENTER, /*NW*/ NE_CITY , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, - {/* SE_BEACH */ false , 1 , fs4 , {/*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ SW_DESERT, /*NW*/ BLOCKED , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, - {/* SW_DESERT */ false , 2 , fs5 , {/*NE*/ CENTER, /*E*/ SE_BEACH , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED , /*NW*/ W_TEMPLE , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, // ACID. Recognizes you. - {/* W_TEMPLE */ false , 3 , fs6 , {/*NE*/ NW_FOREST , /*E*/ CENTER, /*SE*/ SW_DESERT , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ SKY , /*DOWN*/ BLOCKED } }, - {/* NW_FOREST */ false , 4 , fs7 , {/*NE*/ BLOCKED , /*E*/ NE_CITY, /*SE*/ CENTER, /*SW*/ BLOCKED , /*W*/ BLOCKED , /*NW*/ BLOCKED , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, // COFFEE - - // no-returner - {/* SKY */ false , NO_PIXEL, fs8 , { /*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ HIGHER_PLANE , /*DOWN*/ LOWER_PLANE } }, - - // Nirvana - {/* HIGHER_PLANE */ false , NO_PIXEL, fs9 , { /*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ HIGHER_PLANE , /*DOWN*/ LOWER_PLANE} }, - {/* LOWER_PLANE */ false , NO_PIXEL, fs10, { /*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ HIGHER_PLANE , /*DOWN*/ LOWER_PLANE } }, - - // Mortality - {/* SKYSCRAPER */ true , 5 , fs11, { /*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, - {/* DEPTHS */ true , 0 , fs12, { /*NE*/ BLOCKED , /*E*/ BLOCKED , /*SE*/ BLOCKED , /*SW*/ BLOCKED , /*W*/ BLOCKED, /*NW*/ BLOCKED , /*UP*/ BLOCKED , /*DOWN*/ BLOCKED } }, - -} ; - - - -// It is pitch black. You are eaten by a Grue. -// Such language in a high-class establishment like this! -// “Either this is madness or it is Hell.” - -// There is something about this place - a palpable void of light-sustainability - that deeply affects you. The light(s) that you carry is inexplicably extinguished. - -// Suddenly, there is an enormous flash of light. The brightest and whitest light you have ever seen or that anyone has ever seen. It bores its way into you. It is transcendent and you can't help but feel like a barrier between this world and the next has been irreparably broken. What have you done? What have you done? - - -// Global state info -// (Wish we had closures here!) -// (...And anonymous functions!) - -Location *currentLocation; - -bool dead; - -struct Light { - const char *name; - Color color; - char key; - bool onFlag; - Location *where; -}; - -enum LightKey { LIGHTKEY_RED , LIGHTKEY_GREEN, LIGHTKEY_BLUE, LIGHTKEY_MAX=LIGHTKEY_BLUE}; - -Light lights[LIGHTKEY_MAX+1] = { - { "Red" , RED , 'R' , true , &locations[SE_BEACH] }, - { "Green", GREEN , 'G' , true , &locations[NE_CITY] }, - { "Blue" , BLUE , 'B' , true , &locations[NW_FOREST]}, -}; - - -// Note: Due to Arduino IDE bug, the first function definition in this -// file *must* always come after all the type defitions. Sorry... -// https://arduino.stackexchange.com/a/46522/7859 - - -void pause( uint16_t wait_ms ) { - - uint32_t doneTime = millis() + wait_ms; - while (millis()=40 && (c==' ' || c=='-')) { - Serial.println(); - col=0; - } - - pause(20); // Get that old modem cadence - - }; - - Serial.println(); - -} - -Light *lightInHand=NULL; // Light we are currently holding (NULL=none) - -// One love, one life -// Returns reason for death. - -// Valid commands should be all caps, input is converted to upper -// CR and LF are ignored - -char getCommand(void) { - char c; - - do { - c=Serial.readWait(); - - if (c>='a' && c<='z') { - c = (c-'a')+'A'; // Convert to uppercase - } - - } while (c=='\n' || c=='\r'); - - Serial.println(); // Feedback for command accpeted - - return(c); - -} - -// Save some typing - -#define p(x) Serial.print(x) -#define pl(x) Serial.println(x) - -#define pf(x) Serial.print(F(x)) -#define plf(x) Serial.println(F(x)) - -Light *pickAlight(void) { - - // Pick a light, any light - - for(int i=0; i<=LIGHTKEY_MAX;i++) { - p(lights[i].key); - p('-'); - pl(lights[i].name); - } - - plf("Pick a light, any light?"); - - char whichLight = getCommand(); - - for(int i=0; i<=LIGHTKEY_MAX;i++) { - - if ( whichLight == lights[i].key ) { - - return &lights[i]; - - } - - } - - return(NULL); - -} - -int onLightCount(void) { - - int o=0; - for(int i=0;ionFlag ) { // Is this light on? - o++; - } - - } - return o; -} - -bool enlightementConditionMet(void) { - - for(int i=0;ionFlag) || (l->where!=&locations[W_TEMPLE]) ) { - return false; - } - } - - return true; - -} - -// Returns true if death ensued. - -bool nextStep(void) { - - // Show all the lights where they are - - for(int i=0; iwhere->pixel == i ) { // If this color is in this location... - - if ( light->onFlag) { - - color |= lights[j].color; // Mix the color in - - } - - } - - } - - setFaceColor( i , color ); - } - - pl(); - terminalPrint( currentLocation->text ); - - // If we are in the center, check if any visible light(s) in the distance - if ( currentLocation == &locations[CENTER] ) { - - int onLights = onLightCount(); - - if (onLights>=1) { - pf( "You see " ); - if (onLights>1) { - pf( "colored lights" ); - } else { - pf( "a colored light" ); - } - plf( " in the distance." ); - } else { - - plf("It is pitch dark. You have been eaten by a Grue."); - - return true; - } - - } - pl(); - - if (currentLocation->deadly) { // Did we die on the last move? - - return(true); - - } - - if ( currentLocation==&locations[SW_DESERT] && lights[LIGHTKEY_RED].where==currentLocation && lights[LIGHTKEY_RED].onFlag) { - - plf("You die. Alone. Roasted like a pumpkin seed."); - return(true); - - } - - plf("M-Move"); - plf("T-Take/Toss light"); - plf("U-Use light"); - plf( "What do you want to do?"); - - switch (getCommand()) { - - case 'M': { - - for(int i=0; i<=DIRECTION_MAX;i++) { - Direction *d = &directions[i]; - p( d->key ); - p('-'); - pl(d->name); - } - - plf("Where to Sir? Ou desirez-vous aller?"); - - char directionCommand = getCommand(); - - for(int i=0; i<=DIRECTION_MAX;i++) { - - Direction *d = &directions[i]; - - if (d->key == directionCommand) { - - LocationKey next = currentLocation->next[i]; - - if ( next != BLOCKED ) { - - if (next == SKY) { - - if (!enlightementConditionMet()) { // Saint peter - - plf("As you start to rise, you hit your hit on the metphoric ceiling. You have died of ignorance."); - return(1); - - } - - for(int d=10; d>0; d--) { - - for( int b=0; b<32; b++ ) { - - setColor( dim( WHITE , b ) ); - pause(35-b); - - } - - } - - } - - // Ok, we are walking here! - - currentLocation = &locations[next]; - - // If we are holding a light then bring it with us - - if (lightInHand) { - - lightInHand->where=currentLocation; - - if (currentLocation == &locations[CENTER] ) { // Center is special because it has no LED - - /// Taking as lite light to the center turns it off - - if (lightInHand->onFlag) { - - lightInHand->onFlag =false; - - plf( "There is something about this place - a palpable void of light-sustainability - that deeply affects you. The light that you carry is inexplicably extinguished."); - - } - } - - } - - return false; - - } else { - - plf("Sorry, you can't get thar from hear."); - return false; - - } - } // For i in directions - - } - - plf("Oh, we are only the local service."); - - } - - break; - - case 'T': - plf("1-Take"); - plf("2-Toss"); - plf("Take or toss?"); - - switch (getCommand()) { - - case '1': { // Take - - if (lightInHand) { - - // Already carrying one light - - plf("Do I look like a light sherpa?"); - return false; - - } else { - - Light *whichLight = pickAlight(); - - if (whichLight) { - - if (whichLight->where == currentLocation) { - - lightInHand = whichLight; - - pf("You are now carring the "); - p( whichLight->name ); - plf( " light."); - return false; - - } else { - - pf("There is no "); - p( whichLight->name ); - plf( " light here."); - return false; - - } - - } else { - - plf( "You've been looking at lights too long."); - return false; - - } - } - } - break; - - case '2': { // Toss - - Light *whichLight = pickAlight(); - - if (whichLight) { - - if (lightInHand==whichLight) { - - lightInHand = NULL; - - pf("You dropped the "); - p( whichLight->name ); - plf( " light."); - return false; - - } else { - - pf("You are not even carring the "); - p( whichLight->name ); - plf( " light."); - return false; - - } - - } else { - - plf( "That's not a light, bud."); - return false; - - } - - } - - break; - - } - - break; - - case 'U': { - - plf("Which light do you want to use?"); - - Light *whichLight = pickAlight(); - - if (whichLight) { - - if (lightInHand==whichLight) { - - plf("O-On"); - plf("F-Off"); - - pf("What to do with the "); - p( whichLight->name ); - plf( " light?"); - - char d = getCommand(); - - if (d == 'O' ) { - - whichLight->onFlag=true; - pf("The "); - p( whichLight->name ); - plf( " light on, master."); - - } else if ( d=='F' ) { - - whichLight->onFlag=false; - p( whichLight->name ); - plf( " light offed."); - - } else { - - plf("You can't do that with this light, pervert."); - - } - - } else { - - pf("You are not even carring the "); - p( whichLight->name ); - plf( " light."); - return false; - - } - - } else { - - plf( "That's not a light, bud."); - return false; - - } - - } - break; - - default: - plf("I don't understand your accent."); - break; - - }; - - return false; - -} - -void oneLife(void) { - - plf("------"); - plf("Welcome to HEXWORLD, a Tiny Tile Adventure!"); - - //put everything back the way it should be on the 1st day - - currentLocation = &locations[CENTER]; // Always start at Meru - - lights[ LIGHTKEY_RED ].onFlag=true; - lights[ LIGHTKEY_RED ].where=&locations[SE_BEACH]; - - lights[ LIGHTKEY_GREEN ].onFlag=true; - lights[ LIGHTKEY_GREEN ].where=&locations[NE_CITY]; - - lights[ LIGHTKEY_BLUE ].onFlag=true; - lights[ LIGHTKEY_BLUE ].where=&locations[NW_FOREST]; - - lightInHand = NULL; - - - dead = false; - - while (!dead) { - - dead = nextStep(); - - }; - -} - - -// DWARF STUFF - -void setup() { - - Serial.begin(); - -} - - -void loop() { - - - while (1) { - - oneLife(); - - }; - -}; - diff --git a/libraries/blinkani/library.properties b/libraries/blinkani/library.properties deleted file mode 100644 index cd63347a..00000000 --- a/libraries/blinkani/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=Blink Animation Display Manager -version=1.0 -author=Move38.com -maintainer=Move38.com -sentence=High level animation model view for blinks game developement -paragraph= -category=Other -url=https://github.com/bigjosh/Move38-Arduino-Platform -architectures=avr -types=Arduino diff --git a/libraries/blinkani/src/blinkani.cpp b/libraries/blinkani/src/blinkani.cpp deleted file mode 100644 index e2aa047a..00000000 --- a/libraries/blinkani/src/blinkani.cpp +++ /dev/null @@ -1,479 +0,0 @@ -/* - * blinkani.cpp - * - * This defines a display manager for blinks tile to animate its pixels at a high level. - * - * Animations are different than setting color in that they are time sensitive actions. each - * animation has a duration (usually ms) and will either run for the duration or if the duration is 0, - * will continue to loop until another animation is called or setColor clears the display manager. - * - * Note that this library depends on the blinklib library for control of each pixel. The blinklib - * setColor functions are available when using the blinkani library, so the user must understand how - * the two interact. setColor will always stop and clear display manager animations - * - * Note that the display animations only occur when the loop() function returns, so it is important - * that sketches using this model return from loop() frequently. (i.e. don't use delay) - * - * ROADMAP - * 1. display manager simply forces latest command to be the display mode - * 2. queue manager for timeline like animation, adding display actions to a stack - * 1. push, pop, clear - * 2. setColor() always clears the stack - * - */ - -#include "blinklib.h" - -#include "chainfunction.h" - -// Tell blinkstate.h to save the IR functions just for us... - -#define BLINKANI_CANNARY - -#include "blinkani.h" - -// Here we simulate an interface in C -// It is ugly, but works and is time & space efficient - - -struct Effect_t { - - // Every effect has a nextStep() and an isComplete() function - - virtual void nextStep(); // Called once per loop to update the display. - - virtual bool isComplete(); // Called to check if the current effect is finished running - - Effect_t *prevEffect; // A pointer back to the previous effect that called this one - // NULL at top of chain -}; - -// Pointer to the linked list of active effects. HEAD is currently running effect. -// NULL= no active effects. - -static Effect_t *currentEffect=NULL; - -// Make a new effect the current one -// the previous running effect is pushed and will resume when this new effect completes. -// Use this when composing an effect out of lower level effects. - - static void addEffect( Effect_t *effect ) { - - // Save currently running effect - - effect->prevEffect = currentEffect; - - // Set the new effect as currently running one - - currentEffect = effect; - - } - - // Terminates any existing running effects. - // Use this to initially start an effect. - - static void clearEffects() { - - currentEffect = NULL; - - } - - /* - - Flash Effect - Show the specified color for the specified period of time. - -*/ - -struct FlashEffect_t : Effect_t { - - uint32_t endTime; - - void nextStep() { - // Nothing here, we know where are done when time runs out - } - - bool isComplete() { - uint32_t now = millis(); - return endTime <= now; - } - - void start( Color c , uint16_t duration_ms) { - uint32_t now = millis(); - setColor( c ); - endTime = now + duration_ms; - addEffect( this ); - } - -}; - -static FlashEffect_t flashEffect; - -// public API call - -void flash( Color c , uint16_t durration_ms ){ - - clearEffects(); - flashEffect.start( c , durration_ms ); - -} - - -/* - - Blink Effect - Biphasic Blink - -*/ - -struct BlinkEffect_t : Effect_t { - - // Since we immediately display the `on` phase, we only need to - // remember what to do in the off phase - - Color m_offColor; - uint16_t m_offDurration; - - bool m_completeFlag; - - void nextStep() { - - // We pushed a flash for the on state in start(), so we will not get - // here until that has completed and we are ready for the off state. - - flashEffect.start( m_offColor , m_offDurration ); - - m_completeFlag = true; // As soon as the 2nd flash that we started is finished, then we are too. - - } - - bool isComplete() { - return m_completeFlag; - } - - void start(Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration) { - - // Set us up to display the off phase when the on phase is complete - // Note that we add this *before* we start the flash so that we will get called - // when flash completes. - - m_offColor = offColor; - m_offDurration = offDurration; - m_completeFlag = false; - - addEffect( this ); - - // show the on phase now - - flashEffect.start( onColor , onDurration_ms ); - } - -}; - -// All effects have a statically defined instance like this to allocate the memory at compile time - -static BlinkEffect_t blinkEffect; - -void blink(Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration) { - - clearEffects(); - - blinkEffect.start( onColor, onDurration_ms, offColor , offDurration); - -} - -// black & instant off - -void blink( Color onColor , uint16_t onDurration_ms) { - blink( onColor , onDurration_ms , OFF , 0 ); -} - -/* - - Strobe Effect - A series of Blinks - -*/ - -struct StrobeEffect_t : Effect_t { - - Color m_onColor; - uint16_t m_onDurration; - - Color m_offColor; - uint16_t m_offDurration; - - uint16_t occurancesLeft; - - void nextStep() { - - // We pushed a flash for the on state in start(), so we will not get - // here until that has completed and we are ready for the off state. - - - if (occurancesLeft) { - blinkEffect.start( m_onColor , m_onDurration , m_offColor, m_offDurration ); - occurancesLeft--; - } - - } - - bool isComplete() { - return occurancesLeft==0; - } - - void start( uint16_t occurances, Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration) { - - // Set us up to display the off phase when the on phase is complete - // Note that we add this *before* we start the flash so that we will get called - // when flash completes. - - m_onColor = onColor; - m_onDurration = offDurration; - - m_offColor = offColor; - m_offDurration = offDurration; - - occurancesLeft = occurances; - - addEffect( this ); - - // Get the first blink started - nextStep(); - } - -}; - -// All effects have a statically defined instance like this to allocate the memory at compile time - -static StrobeEffect_t strobeEffect; - - -// blink specified number of times -void strobe( uint16_t occurances, Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration ) { - clearEffects(); - strobeEffect.start( occurances, onColor, onDurration_ms, offColor , offDurration ); -} - -// black off, 50% duty cycle. -void strobe( uint16_t occurances, Color onColor, uint16_t period_ms ) { - - uint16_t durration = period_ms/2; // 50% duty cycle - - strobe( occurances , onColor , durration , OFF , durration ); - -} - - -/* - - Rotate Effect - Rotate a color around the faces - -*/ - -struct RotateEffect_t : Effect_t { - - Color m_onColor; - - Color m_offColor; - - uint8_t faceStep; - - - uint32_t nextStepTime; - uint16_t stepDelayTime_ms; - - - void nextStep() { - - uint32_t now = millis(); - - if (now >= nextStepTime) { - - if (faceStep==FACE_COUNT) { // We are done, turn off last pixel - - setFaceColor( 5 , m_offColor ); - - } else { - - setFaceColor( faceStep , m_onColor ); - - // Clear previous pixel - - if (faceStep>0) { - setFaceColor( faceStep-1 , m_offColor ); - } - - } - - faceStep++; - - nextStepTime = now + stepDelayTime_ms; - - } - - } - - bool isComplete() { - - return faceStep > FACE_COUNT; - - } - - - void start( Color onColor, Color offColor , uint16_t stepTime_ms) { - - m_onColor = onColor; - m_offColor = offColor; - - stepDelayTime_ms = stepTime_ms; - - faceStep=0; // Start at the beginning - - nextStepTime =0; - nextStep(); // prime the pump - - addEffect( this ); - } - -}; - -// All effects have a statically defined instance like this to allocate the memory at compile time - -static RotateEffect_t rotateEffect; - -// rotate a dot around the faces -void rotate( Color onColor, Color offColor , uint16_t stepTime_ms ) { - clearEffects(); - rotateEffect.start(onColor, offColor , stepTime_ms ); -} - -// rotate a dot around the faces with a black background -void rotate( Color onColor, uint16_t stepTime_ms ) { - clearEffects(); - rotateEffect.start(onColor, OFF , stepTime_ms ); -} - - -/* - - Spin Effect - A series of rotations - -*/ - -struct SpinEffect_t : Effect_t { - - Color m_onColor; - Color m_offColor; - - uint16_t m_stepDelay; - - uint16_t occurancesLeft; - - void nextStep() { - - if (occurancesLeft) { - rotateEffect.start( m_onColor , m_offColor, m_stepDelay ); - occurancesLeft--; - } - - } - - bool isComplete() { - return occurancesLeft==0; - } - - void start( uint16_t occurances, Color onColor, Color offColor , uint16_t stepDelay ) { - - // Set us up to display the off phase when the on phase is complete - // Note that we add this *before* we start the flash so that we will get called - // when flash completes. - - m_onColor = onColor; - m_offColor = offColor; - - m_stepDelay = stepDelay; - - occurancesLeft = occurances; - - addEffect( this ); - - // Get the first rotate started - nextStep(); - } - -}; - -// All effects have a statically defined instance like this to allocate the memory at compile time - -static SpinEffect_t spinEffect; - -// rotate specified number of times -void spin( uint16_t occurances, Color onColor, Color offColor , uint16_t stepTime_ms ) { - clearEffects(); - spinEffect.start( occurances, onColor, offColor , stepTime_ms ); -} - -bool effectCompleted() { - - return currentEffect == NULL; - -} - -void blinkAniOnLoop(void) { - - // This is the loop that gets called after every loop - - // Tail any already completed effects... - // Requires that at least one effect always be running (currently the idleEffect at the top) - - while ( currentEffect && currentEffect->isComplete()) { - currentEffect = currentEffect->prevEffect; - } - - if (currentEffect) { - (currentEffect->nextStep)(); - } - -} - -// Make a record to add to the callback chain - -static struct chainfunction_struct blinkAniOnLoopChain = { - .callback = blinkAniOnLoop, - .next = NULL // This is a waste because it gets overwritten, but no way to make this un-initialized in C -}; - -// Something tricky here: I can not find a good place to automatically add -// our onLoop() hook at compile time, and we -// don't want to follow idiomatic Arduino ".begin()" pattern, so we -// hack it by adding here the first time anything that could use state -// stuff is called. This is an ugly hack. :/ - -// TODO: This is a good place for a GPIO register bit. Then we could inline the test to a single instruction., - -static void registerHook(void) { - addOnLoop( &blinkAniOnLoopChain ); -} - -// Manually add our hooks - -void blinkAniBegin(void) { - clearEffects(); - registerHook(); -} - - -// send the color you want to fade to, the duration of the fade -void fadeTo( Color newColor, uint16_t duration) { - -} - -/* -Color getFaceColor(byte face) { - // -} - -// -Color getColor() { - // if the color is the same on all faces return the color, otherwise return OFF??? - // TODO: Think this through -} -*/ \ No newline at end of file diff --git a/libraries/blinkani/src/blinkani.h b/libraries/blinkani/src/blinkani.h deleted file mode 100644 index b9bd9155..00000000 --- a/libraries/blinkani/src/blinkani.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * blinkani.h - * - * This defines a display manager for blinks tile to animate its pixels at a high level. - * - * Animations are different than setting color in that they are time sensitive actions. each - * animation has a duration (usually ms) and will either run for the duration or if the duration is 0, - * will continue to loop until another animation is called or setColor clears the display manager. - * - * Note that this library depends on the blinklib library for control of each pixel. The blinklib - * setColor functions are available when using the blinkani library, so the user must understand how - * the two interact. setColor will always stop and clear display manager animations - * - * Note that the display animations only occur when the loop() function returns, so it is important - * that sketches using this model return from loop() frequently. (i.e. don't use delay) - * - * ROADMAP - * 1. display manager simply forces latest command to be the display mode - * 2. queue manager for timeline like animation, adding display actions to a stack - * 1. push, pop, clear - * 2. setColor() always clears the stack - * - */ - -#ifndef BLINKANI_H_ -#define BLINKANI_H_ - -#ifndef BLINKLIB_H_ - #error You must #include blinklib.h before blinkani.h -#endif - -// Call to initialize the blinkani subsystem before starting any effects - -void blinkAniBegin(void); - - -// Show a color for specified time -void flash( Color c , uint16_t durration_ms ); - -// Show two colors in a row -void blink(Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration); - -// black & instant off - -void blink( Color onColor , uint16_t onDurration_ms); - - -// blink specified number of times -void strobe( uint16_t occurances, Color onColor, uint16_t onDurration_ms, Color offColor , uint16_t offDurration ); - -// black off, 50% duty cycle. -void strobe( uint16_t occurances, Color onColor, uint16_t period_ms ); - - -// rotate a dot around the faces -void rotate( Color onColor, Color offColor , uint16_t stepTime_ms ); - -// rotate a dot around the faces with a black background -void rotate( Color onColor, uint16_t stepTime_ms ); - -// rotate specified number of times -void spin( uint16_t occurances, Color onColor, Color offColor , uint16_t stepTime_ms ); - - -// send the color you want to fade to, the duration of the fade -void fadeTo( Color newColor, uint16_t duration); - -Color getColor(); - -Color getFaceColor(byte face); - -bool effectCompleted(); - - -#endif /* BLINKANI_H_ */ diff --git a/libraries/blinklib/library.properties b/libraries/blinklib/library.properties deleted file mode 100644 index 2cffd4c2..00000000 --- a/libraries/blinklib/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=Blink Developement Interface -version=1.0 -author=Move38.com -maintainer=Move38.com -sentence=High level functions for blinks game developement -paragraph= -category=Other -url=https://github.com/bigjosh/Move38-Arduino-Platform -architectures=avr -types=Arduino diff --git a/libraries/blinklib/src/blinklib.cpp b/libraries/blinklib/src/blinklib.cpp deleted file mode 100644 index e3f27256..00000000 --- a/libraries/blinklib/src/blinklib.cpp +++ /dev/null @@ -1,737 +0,0 @@ -/* - - This library presents an API for game developers on the Move38 Blinks platform. - More info at http://Move38.com - - This API sits on top of the hardware abstraction layer and presents a simplified representation - of the current state of the tile and its adjacent neighbors. - - It handles all the low level protocol to communicate state with neighbors, and - offers high level functions for setting the color and detecting button interactions. - -*/ - -// We need this little nugget to get UINT16_T as per... -// https://stackoverflow.com/a/3233069/3152071 -// ...and it must come before Arduino.h which also pulls in stdint.h - -#define __STDC_LIMIT_MACROS -#include - -#include -#include // NULL - -#include "ArduinoTypes.h" - -#include "blinklib.h" -#include "blinkstate.h" // Get the reference to beginBlinkState() -#include "chainfunction.h" - -#include "pixel.h" -#include "timer.h" -#include "button.h" -#include "utils.h" -#include "power.h" - -#include "ir.h" -#include "irdata.h" - -#include "run.h" - -#include -#define DO_ATOMICALLY ATOMIC_BLOCK(ATOMIC_FORCEON) // Non-HAL code always runs with interrupts on - -// IR CONSTANTS - -#define STATE_BROADCAST_SPACING_MS 50 // How often do we broadcast our state to neighboring tiles? - -#define STATE_ABSENSE_TIMEOUT_MS 250 // If we don't get any state received on a face for this long, we set their state to 0 - -// BUTTON CONSTANTS - -// Debounce button pressed this much -// Empirically determined. At 50ms, I can click twice fast enough -// that the second click it gets in the debounce. At 20ms, I do not -// any bounce even when I sllllooooowwwwly click. -#define BUTTON_DEBOUNCE_MS 20 - -// Delay for determining clicks -// So, a click or double click will not be registered until this timeout -// because we don't know yet if it is a single, double, or triple - -#define BUTTON_CLICK_TIMEOUT_MS 330 - -#define BUTTON_LONGPRESS_TIME_MS 2000 // How long you must hold button down to register a long press. - -#define BUTTON_SLEEP_TIMEOUT_SECONDS (10*60) // If no button press in this long then goto sleep - -// PIXEL FUNCTIONS - -// Remeber that thge underlying pixel_* setting functions are double buffered and so -// require a call to pixel_displayBuffer() to aactually show all the updates. We do this -// this call everytime loop() returns to ensure a coherent update, and also make sure that -// sure that the final result of any loop() interation will always hit the display for at least -// one frame to eliminate aliasing and tearing. - -void setFaceColor( byte face , Color newColor ) { - - pixelColor_t newPixelColor; - - // TODO: OMG, this is the most inefficient conversion from a unit16 back to (the same) unit16 ever! - // But to share a type between the core and blinklib level though pixel.h would require all blinklib - // users to get the whole pixel.h namespace. There has to be a good way around this. Maybe - // break out the pixelColor type into its own core .H file? seems wrong. Hmmm.... - - newPixelColor.r = GET_5BIT_R( newColor ); - newPixelColor.g = GET_5BIT_G( newColor ); - newPixelColor.b = GET_5BIT_B( newColor ); - - pixel_bufferedSetPixel( face , newPixelColor ); - #warning You are using setFaceColor(face, color), which is being deprecated. Please use setColorOnFace(color, face) in its place. -} - -void setColorOnFace( Color newColor , byte face ) { - - pixelColor_t newPixelColor; - - // TODO: OMG, this is the most inefficient conversion from a unit16 back to (the same) unit16 ever! - // But to share a type between the core and blinklib level though pixel.h would require all blinklib - // users to get the whole pixel.h namespace. There has to be a good way around this. Maybe - // break out the pixelColor type into its own core .H file? seems wrong. Hmmm.... - - newPixelColor.r = GET_5BIT_R( newColor ); - newPixelColor.g = GET_5BIT_G( newColor ); - newPixelColor.b = GET_5BIT_B( newColor ); - - pixel_bufferedSetPixel( face , newPixelColor ); - -} - -// Convenience function to set all pixels to the same color. - -void setColor( Color newColor ) { - - FOREACH_FACE(f) { - setFaceColor( f , newColor ); - } - -} - - -// This maps 0-255 values to 0-31 values with the special case that 0 (in 0-255) is the only value that maps to 0 (in 0-31) -// This leads to some slight non-linearity since there are not a uniform integral number of 1-255 values -// to map to each of the 1-31 values. - -byte map8bitTo5bit( byte b ) { - - if (b==0) return 0; - - // 0 gets a special case of `off`, so we divide the rest of the range in to - // 31 equaly spaced regions to assign the remaing 31 brightness levels. - - uint16_t normalizedB = b-1; // Offset to a value 0-254 that will be scaled to the remaining 31 on values - - //uint16_t scaledB = (normalizedB / 255) * 31); // This is what we want to say, but it will underflow in integer math - - byte scaledB = ( (uint16_t) normalizedB * 31U) / 255U; // Be very careful to stay in bounds! - - // scaledB is now a number 0-30 that is (almost) lenearly scaled down from the orginal b - - return ( scaledB )+1; // De-normalize back up to `on` values 1-31. - -} - -// Make a new color from RGB values. Each value can be 0-255. - -Color makeColorRGB( byte red, byte green, byte blue ) { - return MAKECOLOR_5BIT_RGB( map8bitTo5bit(red) , map8bitTo5bit(green) , map8bitTo5bit(blue) ); -} - -Color makeColorHSB( uint8_t hue, uint8_t saturation, uint8_t brightness ) { - - uint8_t r; - uint8_t g; - uint8_t b; - - if (saturation == 0) - { - // achromatic (grey) - r =g = b= brightness; - } - else - { - unsigned int scaledHue = (hue * 6); - unsigned int sector = scaledHue >> 8; // sector 0 to 5 around the color wheel - unsigned int offsetInSector = scaledHue - (sector << 8); // position within the sector - unsigned int p = (brightness * ( 255 - saturation )) >> 8; - unsigned int q = (brightness * ( 255 - ((saturation * offsetInSector) >> 8) )) >> 8; - unsigned int t = (brightness * ( 255 - ((saturation * ( 255 - offsetInSector )) >> 8) )) >> 8; - - switch( sector ) { - case 0: - r = brightness; - g = t; - b = p; - break; - case 1: - r = q; - g = brightness; - b = p; - break; - case 2: - r = p; - g = brightness; - b = t; - break; - case 3: - r = p; - g = q; - b = brightness; - break; - case 4: - r = t; - g = p; - b = brightness; - break; - default: // case 5: - r = brightness; - g = p; - b = q; - break; - } - } - - return( makeColorRGB( r , g , b ) ); -} - -// OMG, the Ardiuno rand() function is just a mod! We at least want a uniform distibution. - -// Here we implement the SimpleRNG pseudo-random number generator based on this code... -// https://www.johndcook.com/SimpleRNG.cpp - -#define GETNEXTRANDUINT_MAX UINT16_MAX - -static uint16_t GetNextRandUint(void) { - - // These values are not magical, just the default values Marsaglia used. - // Any unit should work. - - // We make them local static so that we only consume the storage if the random() - // functions are actually ever called. - - static uint32_t u = 521288629UL; - static uint32_t v = 362436069UL; - - v = 36969*(v & 65535) + (v >> 16); - u = 18000*(u & 65535) + (u >> 16); - - return (v << 16) + u; - -} - -// return a random number between 0 and limit inclusive. -// TODO: Use entropy from the button or decaying IR LEDs -// https://stackoverflow.com/a/2999130/3152071 - -uint16_t rand( uint16_t limit ) { - - uint16_t divisor = GETNEXTRANDUINT_MAX/(limit+1); - uint16_t retval; - - do { - retval = GetNextRandUint() / divisor; - } while (retval > limit); - - return retval; -} - -// Returns the number of millis since last call -// Handy for profiling. - -uint32_t timeDelta(void) { - static uint32_t lastcall=0; - uint32_t now = millis(); - uint32_t delta = now - lastcall; - lastcall = now; - return delta; -} - -// Read the unique serial number for this blink tile -// There are 9 bytes in all, so n can be 0-8 - - -byte getSerialNumberByte( byte n ) { - - if (n>8) return(0); - - return utils_serialno()->bytes[n]; - -} - - -/** IR Functions **/ - - -// // These all can be updated by callback, so must be volatile. - - -// Click Semantics -// =============== -// All clicks happen inside a click window, as defined by BUTTON_CLICK_TIMEOUT_MS -// The window resets each time the debounced button state goes down. -// Any subsequent down events before the window expires are part of the same click cycle. -// If a cycle ends with the button up, then the clicks are tallied. -// If the cycle ends with the button down, then we interpret this that the user wanted to -// abort the clicks, so we discard the count. - -static volatile bool buttonState=0; // Current debounced state - -static volatile bool buttonPressedFlag=0; // Has the button been pressed since the last time we checked it? -static volatile bool buttonReleasedFlag=0; // Has the button been lifted since the last time we checked it? - -static uint8_t buttonDebounceCountdown=0; // How long until we are done bouncing. Only touched in the callback - -static uint16_t clickWindowCountdown=0; // How long until we close the current click window. 0=done TODO: Make this 8bit by reducing scan rate. -static uint8_t clickPendingcount=0; // How many clicks so far int he current click window - -static uint16_t longPressCountdown=0; // How long until the current press becomes a long press - -static volatile bool singleClickedFlag=0; // single click since the last time we checked it? -static volatile bool doubleClickedFlag=0; // double click since the last time we checked it? -static volatile bool multiClickedFlag=0; // multi click since the last time we checked it? - -static volatile bool longPressFlag=0; // Has the button been long pressed since the last time we checked it? - -static volatile uint8_t maxCompletedClickCount=0; // Remember the most completed clicks to support the clickCount() function - -#define BUTTON_SLEEP_TIMEOUT_MS ( BUTTON_SLEEP_TIMEOUT_SECONDS * (unsigned long) MILLIS_PER_SECOND) - -static volatile unsigned long buttonSleepTimout=BUTTON_SLEEP_TIMEOUT_MS; - // we sleep when millis() >= buttonSleepTimeout - // Here we assume that millis() starts at 0 on power up - -// Called once per tick by the timer to check the button position -// and update the button state variables. - -// Note: this runs in Callback context in the timercallback - -static void updateButtonState(void) { - - - bool buttonPositon = button_down(); - - if ( buttonPositon == buttonState ) { - - if (buttonDebounceCountdown) { - - buttonDebounceCountdown--; - - } - - if (longPressCountdown) { - - longPressCountdown--; - - if (longPressCountdown==0) { - - if (buttonState) { - - longPressFlag = 1; - } - } - - // We can nestle thew click window countdown in here because a click will ALWAYS happen inside a long press... - - if (clickWindowCountdown) { - - clickWindowCountdown--; - - if (clickWindowCountdown==0) { // Click window just expired - - if (!buttonState) { // Button is up, so register clicks - - if (clickPendingcount==1) { - singleClickedFlag=1; - } else if (clickPendingcount==2) { - doubleClickedFlag=1; - } else { - multiClickedFlag=1; - } - - - if (clickPendingcount > maxCompletedClickCount ) { - maxCompletedClickCount=clickPendingcount; - } - - - - } - - clickPendingcount=0; // Start next cycle (aborts any pending clicks if button was still down - - - } - - } - - } - - - } else { // New button position - - if (!buttonDebounceCountdown) { // Done bouncing - - buttonState = buttonPositon; - - if (buttonPositon) { - buttonPressedFlag=1; - - if (clickPendingcount<255) { // Don't overflow - clickPendingcount++; - } - - clickWindowCountdown = TIMER_MS_TO_TICKS( BUTTON_CLICK_TIMEOUT_MS ); - longPressCountdown = TIMER_MS_TO_TICKS( BUTTON_LONGPRESS_TIME_MS ); - - buttonSleepTimout = millis() + BUTTON_SLEEP_TIMEOUT_MS; // Button pressed, so restart the sleep countdown - - } else { - buttonReleasedFlag=1; - } - } - - // Restart the countdown anytime the button position changes - - buttonDebounceCountdown = TIMER_MS_TO_TICKS( BUTTON_DEBOUNCE_MS ); - - } - - - -} - - -// Debounced view of button state - -bool buttonDown(void) { - - return buttonState; - -} - - -// This test does not need to be atomic. No race -// because we only clear here, and only set in ISR. - -// This compiles quite nicely... - -/* -;if ( flag ) { -552: 80 91 15 01 lds r24, 0x0115 ; 0x800115 -556: 81 11 cpse r24, r1 -;flag=false; -558: 10 92 15 01 sts 0x0115, r1 ; 0x800115 -55c: 08 95 ret -*/ - -bool testAndClearFlag( volatile bool &flag ) { - -if ( flag ) { - flag=false; - return true; -} - -return false; - -} - - -bool buttonPressed(void) { - return testAndClearFlag ( buttonPressedFlag ); -} - - -bool buttonReleased(void) { - // This test does not need to be atomic. No race - // because we only clear here, and only set in ISR - return testAndClearFlag ( buttonReleasedFlag ); -} - - -bool buttonSingleClicked(void) { - return testAndClearFlag( singleClickedFlag ); -} - - -bool buttonDoubleClicked(void) { - return testAndClearFlag( doubleClickedFlag); -} - -bool buttonMultiClicked(void) { - return testAndClearFlag( multiClickedFlag ); -} - -bool buttonLongPressed(void) { - return testAndClearFlag( longPressFlag ); -} - - -uint8_t buttonClickCount(void) { - - uint8_t t; - - // Race here - if a new (and higher count) click expired exactly between the next two lines, - // we would only read the previous highest count, and then discard the new highest when we set to 0. - // That's why we need ATOMICALLY here. - - DO_ATOMICALLY { - t=maxCompletedClickCount; - maxCompletedClickCount=0; - } - - return t; -} - -void button_callback_onChange(void) { - // Empty since we do not need to do anything in the interrupt with the above timer sampling scheme. - // Nice because bounces can come fast and furious. -} - - -// TODO: Need this? - -volatile uint8_t verticalRetraceFlag=0; // Turns to 1 when we are about to start a new refresh cycle at pixel zero -// Once this turns to 1, you have about 2ms to load new values into the raw array -// to have them displayed in the next frame. -// Only matters if you want to have consistent frames and avoid visual tearing -// which might not even matter for this application at 80hz -// TODO: Is this empirically necessary? - - -// Will overflow after about 62 days... -// https://www.google.com/search?q=(2%5E31)*2.5ms&rlz=1C1CYCW_enUS687US687&oq=(2%5E31)*2.5ms - -static volatile uint32_t millisCounter=0; // How many milliseconds since startup? - -// Overflows after about 60 days -// Note that resolution is limited by timer tick rate - -// TODO: Clear out millis to zero on wake - -// This snapshot makes sure that we always see the same value for millis() in a given iteration of loop() -// This "freeze-time" view makes it harder to have race conditions when millis() changes while you are looking at it. -// The value of millis_snapshot gets reset to 0 after each loop() iteration. - -static uint32_t millis_snapshot=0; - -unsigned long millis(void) { - - if (millis_snapshot) { // Did we already compute this for this pass of loop()? - return millis_snapshot; - } - - uint32_t tempMillis; - - // millisCounter is 4 bytes long and is updated in the background - // so we need an atomic block to make sure it does not change while we are reading it - - DO_ATOMICALLY { - tempMillis=millisCounter; - } - - millis_snapshot = tempMillis; - - return( tempMillis ); -} - -// Note we directlyt access millis() here, which is really bad style. -// The timer should capture millis() in a closure, but no good way to -// do that in C++ that is not verbose and inefficient, so here we are. - -bool Timer::isExpired() { - return millis() >= m_expireTime; -} - -void Timer::set( uint32_t ms ) { - m_expireTime= millis()+ms; -} - -uint32_t Timer::getRemaining() { - uint32_t timeRemaining; - if( millis() >= m_expireTime) { - timeRemaining = 0; - }else { - timeRemaining = m_expireTime - millis(); - } - return timeRemaining; -} - -/* - -// Delay for `ms` milliseconds - -void delay( unsigned long ms ) { - - unsigned long endtime = millis() + ms; - - while (millis() < endtime); - -} -*/ - -// TODO: This is accurate and correct, but so inefficient. -// We can do better. - -// TODO: chance timer to 500us so increment is faster. -// TODO: Change time to uint24 _t - -// Supported!!! https://gcc.gnu.org/wiki/avr-gcc#types - -// __uint24 timer24; - -static uint16_t cyclesCounter=0; // Accumulate cycles to keep millisCounter accurate - -#define BLINKCORE_UINT16_MAX (0xffff) // I can not get stdint.h to work even with #define __STDC_LIMIT_MACROS, so have to resort to this hack. - - -#if TIMER_CYCLES_PER_TICK > BLINKCORE_UINT16_MAX - #error Overflow on cyclesCounter -#endif - -// Note this runs in callback context - -static void updateMillis(void) { - - cyclesCounter+=TIMER_CYCLES_PER_TICK; // Used for timekeeping. Nice to increment here since this is the least time consuming phase - - while (cyclesCounter >= CYCLES_PER_MS ) { // While covers cases where TIMER_CYCLES_PER_TICK >= CYCLES_PER_MILLISECOND * 2 - - millisCounter++; - cyclesCounter-=CYCLES_PER_MS ; - - } - - // Note that we might have some cycles left. They will accumulate in cycles counter and eventually get folded into a full milli to - // avoid errors building up. - -} - -// Have we woken since last time we checked? - -static volatile uint8_t wokeFlag=0; - -// Returns 1 if we have slept and woken since last time we checked -// Best to check as last test at the end of loop() so you can -// avoid intermediate display upon waking. - -uint8_t hasWoken(void) { - - if (wokeFlag) { - wokeFlag=0; - return 1; - } - - return 0; - -} - -// Turn off everything and goto to sleep - -void sleep(void) { - - pixel_disable(); // Turn off pixels so battery drain - ir_disable(); // TODO: Wake on pixel - button_ISR_on(); // Enable the button interrupt so it can wake us - - power_sleep(); // Go into low power sleep. Only a button change interrupt can wake us - - button_ISR_off(); // Set everything back to thew way it was before we slept - ir_enable(); - pixel_enable(); - - wokeFlag = 1; - -} - -// Time to sleep? (No button presses recently?) - -void checkSleepTimeout(void) { - - if ( millis() >= buttonSleepTimout) { - sleep(); - } - -} - -// This is called by about every 512us with interrupts on. - -void timer_512us_callback_sei(void) { - updateMillis(); - updateButtonState(); - checkSleepTimeout(); -} - -// This is called by about every 256us with interrupts on. - -void timer_256us_callback_sei(void) { - updateIRComs(); -} - - -static chainfunction_struct *onLoopChain = NULL; - -// Call all the functions on the chain (if any)... - -static void callOnLoopChain(void ) { - - chainfunction_struct *c = onLoopChain; - - while (c) { - - - c->callback(); - - c= c->next; - - } - -} - -// This is the entry point where the blinkcore platform will pass control to -// us after initial power-up is complete - -// We make this weak so that a game can override and take over before we initialize all the hier level stuff - -void __attribute__ ((weak)) run(void) { - - // Let blinkstate sink its hooks in - blinkStateBegin(); - - setup(); - - while (1) { - - loop(); - - pixel_displayBufferedPixels(); // show all display updates that happened in last loop() - // Also currently blocks until new frame actually starts - - callOnLoopChain(); - - millis_snapshot=0; // Clear out so we get an updated value next pass - - // TODO: Sleep here - - } - -} - -// Add a function to be called after each pass though loop() -// `cons` onto the linked list of functions - -void addOnLoop( chainfunction_struct *chainfunction ) { - - chainfunction->next = onLoopChain; - onLoopChain = chainfunction; - -} diff --git a/libraries/blinklib/src/blinklib.h b/libraries/blinklib/src/blinklib.h deleted file mode 100644 index 432cefa1..00000000 --- a/libraries/blinklib/src/blinklib.h +++ /dev/null @@ -1,352 +0,0 @@ -/* - * blink.h - * - * This defines a high-level interface to the blinks tile hardware. - * - */ - -#ifndef BLINKLIB_H_ -#define BLINKLIB_H_ - -//#include "blinkcore.h" - -#include "ArduinoTypes.h" - -#include "chainfunction.h" - -#include -#include - - -/* - - This set of functions let you test for changes in the environment. - -*/ - -// Did the state on any face change since last called? -// Get the neighbor states with getNeighborState() - -// TODO: State view not implemented yet. You can use irIsReadyOnFace() instead. - -//bool neighborChanged(); - -// Was the button pressed or lifted since the last time we checked? -// Note that these register the change the instant the button state changes -// without any delay, so good for latency sensitive cases. -// It is debounced, so the button must have been in the previous state a minimum -// debounce time before a new detection will occur. - -bool buttonPressed(void); -bool buttonReleased(void); - -// Was the button single, double , or multi clicked since we last checked? -// Note that there is a delay after the button is first pressed -// before a click is registered because we have to wait to -// see if another button press is coming. -// A multiclick is 3 or more clicks - -// Remember that these click events fire a short time after the button is lifted on the final click -// If the button is held down too long on the last click, then click interaction is aborted. - -bool buttonSingleClicked(); - -bool buttonDoubleClicked(); - -bool buttonMultiClicked(); - - -// The number of clicks in the longest consecutive valid click cycle since the last time called. -byte buttonClickCount(void); - -// Remember that a long press fires while the button is still down -bool buttonLongPressed(void); - -/* - - This set of functions lets you read the current state of the environment. - -*/ - -// Returns true if the button currently pressed down -// (Debounced) - -bool buttonDown(); - - - -/* - IR communications functions -*/ - - -// Send data on a single face. -// Data is 6-bits wide, top bits are ignored. - -void irSendData( uint8_t face , uint8_t data ); - -// Simultaneously send data on all faces that have a `1` in bitmask -// Data is 6-bits wide, top bits are ignored. -void irSendDataBitmask(uint8_t data, uint8_t bitmask); - -// Broadcast data on all faces. -// Data is 6-bits wide, top bits are ignored. - -void irBroadcastData( uint8_t data ); - -// Is there a received data ready to be read on the indicated face? Returns 0 if none. - -bool irIsReadyOnFace( uint8_t face ); - -// Read the most recently received data. Value 0-127. Blocks if no data ready. - -uint8_t irGetData( uint8_t led ); - - -/* - - This set of functions lets you control the colors on the face RGB LEDs - -*/ - -// Set our state to newState. This state is repeatedly broadcast to any -// neighboring tiles. -// Note that setting our state to 0 make us stop broadcasting and effectively -// disappear from the view of neighboring tiles. - -// TODO: State view not implemented yet. You can use irBroadcastData() instead. - -// void setState( byte newState ); - -// Color type holds 4 bits for each R,G,B. Top bit is currently unused. - -// TODO: Do we need 5 bits of resolution for each color? -// TODO: Use top bit(s) for something useful like automatic -// blink or twinkle or something like that. - -// Argh, these macros are so ugly... but so ideomatic arduino. Maybe use a class with bitfields like -// we did in pixel.cpp just so we can sleep at night? - -typedef uint16_t Color; - -// Number of brightness levels in each channel of a color -#define BRIGHTNESS_LEVELS_5BIT 32 -#define MAX_BRIGHTNESS_5BIT (BRIGHTNESS_LEVELS_5BIT-1) - -#define GET_5BIT_R(color) ((color>>10)&31) -#define GET_5BIT_G(color) ((color>> 5)&31) -#define GET_5BIT_B(color) ((color )&31) - -// R,G,B are all in the domain 0-31 -// Here we expose the internal color representation, but it is worth it -// to get the performance and size benefits of static compilation -// Shame no way to do this right in C/C++ - -#define MAKECOLOR_5BIT_RGB(r,g,b) ((r&31)<<10|(g&31)<<5|(b&31)) - -#define RED MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT, 0 ,0) -#define ORANGE MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT/2 ,0) -#define YELLOW MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT ,0) -#define GREEN MAKECOLOR_5BIT_RGB( 0 ,MAX_BRIGHTNESS_5BIT ,0) -#define CYAN MAKECOLOR_5BIT_RGB( 0 ,MAX_BRIGHTNESS_5BIT ,MAX_BRIGHTNESS_5BIT) -#define BLUE MAKECOLOR_5BIT_RGB( 0 , 0 ,MAX_BRIGHTNESS_5BIT) -#define MAGENTA MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT, 0 ,MAX_BRIGHTNESS_5BIT) - -#define WHITE MAKECOLOR_5BIT_RGB(MAX_BRIGHTNESS_5BIT,MAX_BRIGHTNESS_5BIT ,MAX_BRIGHTNESS_5BIT) - -#define OFF MAKECOLOR_5BIT_RGB( 0 , 0 , 0) - -// This maps 0-255 values to 0-31 values with the special case that 0 (in 0-255) is the only value that maps to 0 (in 0-31) -// This leads to some slight non-linearity since there are not a uniform integral number of 1-255 values -// to map to each of the 1-31 values. - -// Make a new color from RGB values. Each value can be 0-255. - -Color makeColorRGB( byte red, byte green, byte blue ); - -#define MAX_BRIGHTNESS (255) - -// Dim the specified color. Brightness is 0-255 (0=off, 255=don't dim at all-keep original color) -// Inlined to allow static simplification at compile time - -inline Color dim( Color color, byte brightness) { - return MAKECOLOR_5BIT_RGB( - (GET_5BIT_R(color)*brightness)/255, - (GET_5BIT_G(color)*brightness)/255, - (GET_5BIT_B(color)*brightness)/255 - ); -} - -// Make a new color in the HSB colorspace. All values are 0-255. - -Color makeColorHSB( byte hue, byte saturation, byte brightness ); - -// Change the tile to the specified color -// NOTE: all color changes are double buffered -// and the display is updated when loop() returns - -void setColor( Color newColor); - -// Set the pixel on the specified face (0-5) to the specified color -// NOTE: all color changes are double buffered -// and the display is updated when loop() returns - -void setFaceColor( byte face, Color newColor ); - -void setColorOnFace( Color newColor , byte face ); - -/* - - Timing functions - -*/ - -// Number of running milliseconds since power up. -// -// Important notes: -// 1) does not increment while sleeping -// 2) is only updated between loop() interations -// 3) is not monotonic, so always use greater than -// and less than rather than equals for comparisons -// 4) overflows after about 50 days -// 5) is only accurate to about +/-10% - -unsigned long millis(void); - -#define NEVER ( (uint32_t)-1 ) // UINT32_MAX would be correct here, but generates a Symbol Not Found. - - - -class Timer { - - private: - - uint32_t m_expireTime; // When this timer will expire - - public: - - Timer() : m_expireTime(0) {}; // Timers come into this world pre-expired. - - bool isExpired(); - - void set( uint32_t ms ); - - uint32_t getRemaining(); -}; - - -/* - - Utility functions - -*/ - - -// Return a random number between 0 and limit inclusive. - -uint16_t rand( uint16_t limit ); - -// Read the unique serial number for this blink tile -// There are 9 bytes in all, so n can be 0-8 - - -byte getSerialNumberByte( byte n ); - - -/* - - Button functions - -*/ - - -// Debounced view of button state - -bool buttonDown(void); - -// Returns true if the button has been pressed since -// the last time it was called. - -bool buttonPressed(void); - - -/* - - IR communications functions - -*/ - -// Send data on a single face. Data is 6-bits wide (0-63 value). - -void irSendDataBitmask( uint8_t face , uint8_t data ); - -// Broadcast data on all faces. Data is 6-bits wide (0-63 value). - -void irBroadcastData( uint8_t data ); - -// Is there a received data ready to be read on the indicated face? Returns 0 if none. - -bool irIsReadyOnFace( uint8_t face ); - -// Read the most recently received data. Blocks if no data ready. Data is 6-bits wide (0-63 value). - -uint8_t irGetData( uint8_t face ); - - -/* Power functions */ - -// The blink will automatically sleep if the button has not been pressed in -// more than 10 minutes. The sleep is preemptive - the tile stops in the middle of whatever it -// happens to be doing. - -// The tile wakes from sleep when the button is pressed. Upon waking, it picks up from exactly -// where it left off. It is up to the programmer to check to see if the blink has slept and then -// woken and react accordingly if desired. - - -// Returns 1 if we have slept and woken since last time we checked -// Best to check as last test at the end of loop() so you can -// avoid intermediate display upon waking. - -uint8_t hasWoken(void); - -/* - - These hook functions are filled in by the sketch - -*/ - - -// Called when this sketch is first loaded and then -// every time the tile wakes from sleep - -void setup(void); - -// Called repeatedly just after the display pixels -// on the tile face are updated - -void loop(); - -// Add a function to be called after each pass though loop() - -void addOnLoop( chainfunction_struct *chainfunction ); - - -/* - - Some syntactic sugar to make our progrmas look not so ugly. - -*/ - - -#define FACE_COUNT 6 - -// 'Cause C ain't got iterators and all those FOR loops are too ugly. -#define FOREACH_FACE(x) for(int x = 0; x < FACE_COUNT ; ++ x) // Pretend this is a real language with iterators - -// Get the number of elements in an array. -#define COUNT_OF(x) ((sizeof(x)/sizeof(x[0]))) - - - -#endif /* BLINKLIB_H_ */ diff --git a/libraries/blinklib/src/chainfunction.h b/libraries/blinklib/src/chainfunction.h deleted file mode 100644 index cbca9441..00000000 --- a/libraries/blinklib/src/chainfunction.h +++ /dev/null @@ -1,26 +0,0 @@ - -// Defines a template that is used to add chainable function calls to a handler -// This should be statically allocated in the module that is adding the chain -// Otherwise the handler would either need to... -// -// (1) allocate a fixed number of slots that could run out, or -// (2) dynamically allocate space each time a new function is added, but better to see how much space used at compile time. - -#ifndef CHAINFUNCTION_H_ -#define CHAINFUNCTION_H_ - -#include - -typedef void *voidvoidfunction_ptr(void); - -typedef struct chainfunction_struct { - - void (*callback)(void); - - struct chainfunction_struct *next; // Sorry for this ugly syntax, this is how we do it in old skool C! https://stackoverflow.com/questions/18582205/linkedlist-struct-typedef-in-c - -} chainfunction_t ; - -#endif /* CHAINFUNCTION_H_ */ - - \ No newline at end of file diff --git a/libraries/blinklib/src/img/0-bit-timing.png b/libraries/blinklib/src/img/0-bit-timing.png deleted file mode 100644 index 576f1650..00000000 Binary files a/libraries/blinklib/src/img/0-bit-timing.png and /dev/null differ diff --git a/libraries/blinklib/src/img/1-bit-timing.png b/libraries/blinklib/src/img/1-bit-timing.png deleted file mode 100644 index b216fcee..00000000 Binary files a/libraries/blinklib/src/img/1-bit-timing.png and /dev/null differ diff --git a/libraries/blinklib/src/irdata.cpp b/libraries/blinklib/src/irdata.cpp deleted file mode 100644 index 803d673b..00000000 --- a/libraries/blinklib/src/irdata.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - - Talk to the 6 IR LEDs that are using for communication with adjacent tiles - - - THEORY OF OPERATION - =================== - - The IR LEDs detect pulses of light. - - A bit is represented by the space between two pulses of light. A short space is a '1' and a long space is a '0'. - - A transmission is sent as a series of 8 bits. - - We always send a preamble of "10" before the data bits. This allows the receiver to detect the - incoming byte. This sequence is special because (1) the only way a '1' can end up in the top bit - of the buffer is if we have received enough consecutive valid bits, and (2) the following '0' - guards against the case where we send an opening '1' bit but it just happens to come right after the - receiver had spontaneously fired. In this case, we might see these two fires (the spontaneous one - and the intentional start of the preamble one) as a valid '1' bit. The extra '0' ensure that only the second - intentional '1' bit will have a '0' after it and the phantom '1' will be pushed off the top of the buffer. - - There are 6 data bits after the preamble. MSB sent first. - - There must be an idle gap after the end of each transmission. - - Internally, we use an 8-bit buffer to store incoming bits. We check to see if the top two - bit of the buffer match the '10' preamble sequence to know if we have received a good transmission. - - If we ever go too long between pulses, we reset the incoming buffer to '0' to avoid - detecting a phantom message. - - TODO: When noise causes a face to wake us from sleep too much, we can turn off the mask bit for a while. - - TODO: MORE TO COME HERE - NEED PICTURES. - - -*/ - -#include "blinklib.h" - -#include "ir.h" -#include "utils.h" -#include "timer.h" // get US_TO_CYCLES() - -#include "irdata.h" - -#include // Must come after F_CPU definition - -#include // Gets us _BV() - -// A bit cycle is one 2x timer tick, currently 256us - -#define IR_WINDOW_US 256 // How long is each timing window? Based on timer programming and clock speed. - -#define IR_CLOCK_SPREAD_PCT 10 // Max clock spread between TX and RX clocks in percent - -#define TX_PULSE_OVERHEAD_US 25 // How long does ir_tx_pulse() take? - -// Time between consecutive bit flashes -// Must be long enough that receiver can detect 2 distinct consecutive flashes even at maximum interrupt latency - - //#define IR_SPACE_TIME_US (IR_WINDOW_US + (( ((unsigned long) IR_WINDOW_US * IR_CLOCK_SPREAD_PCT) ) / 100UL ) - TX_PULSE_OVERHEAD ) // Used for sending flashes. Must be longer than one IR timer tick including if this clock is slow and RX is fast. - -#define IR_SPACE_TIME_US (300) // Used for sending flashes. - // Must be longer than one IR timer tick including if this clock is slow and RX is fast - // Must be shorter than two IR timer ticks including if the sending pulse is delayed by maximum interrupt latency. - -#define TICKS_PER_SECOND (F_CPU) - -#define IR_SPACE_TIME_TICKS US_TO_CYCLES( IR_SPACE_TIME_US ) - -/* - -// from http://www.microchip.com/forums/m587239.aspx - -static uint8_t oddParity(uint8_t p) { - p = p ^ (p >> 4 | p << 4); - p = p ^ (p >> 2); - p = p ^ (p >> 1); - return p & 1; -} - -*/ - - -// You really want sizeof( ir_rx_state_t) to be a power of 2. It makes the emitted pointer calculations much smaller and faster. -// TODO: Do we need all these volatiles? Probably not... - - typedef struct { - - // These internal variables are only updated in ISR, so don't need to be volatile. - - // I think access to the first element is slightly faster, so most frequently used should go here - - uint8_t volatile windowsSinceLastFlash; // How many times windows since last trigger? Reset to 0 when we see a trigger - - uint8_t inputBuffer; // Buffer for RX in progress. Data bits step up until high bit set. - // High bit will always be set for real data because the start bit is 1 - - - // Visible to outside world - volatile uint8_t inValue; // Last successfully decoded RX value. 1 in high bit means valid and unread. 0= empty. - - - uint8_t dummy; // TODO: parity bit? for now just keep struct a power of 2 - - // This struct should be even power of 2 long. - - } ir_rx_state_t; - - - // We keep these in together in a a struct to get faster access via - // Data Indirect with Displacement opcodes - - static volatile ir_rx_state_t ir_rx_states[IRLED_COUNT]; - -// Called once per timer tick -// Check all LEDs, decode any changes - - // Note this runs in callback context in timercallback. - - volatile uint8_t specialWindowCounter=0; - - // Temp storage to stash the IR bits while interrupts are off. - // We will later process and decode once interrupts are back on. - - volatile uint8_t most_recent_ir_test; - - void timer_256us_callback_cli(void) { - - // Interrupts are off, so get it done as quickly as possible - most_recent_ir_test = ir_test_and_charge_cli(); - - } - - void updateIRComs(void) { - - // Grab which IR LEDs triggered in the last time window - - uint8_t bits = most_recent_ir_test; - - // Loop though and process each IR LED (which corresponds to one bit in `bits`) - // Start at IR5 and work out way down to IR0. - // Going down is faster than up because we can test bitwalker == 0 for free - - - uint8_t bitwalker = _BV( IRLED_COUNT -1 ); - ir_rx_state_t volatile *ptr = ir_rx_states + IRLED_COUNT -1; - -/* - #warning only checking IR0! - uint8_t bitwalker = _BV( 0 ); - ir_rx_state_t volatile *ptr = ir_rx_states; -*/ - // Loop though each of the IR LED and see if anything happened on each... - - do { - - uint8_t bit = bits & bitwalker; - - if (bit) { // This LED triggered in the last time window - - uint8_t thisWindowsSinceLastFlash = ptr->windowsSinceLastFlash; - - ptr->windowsSinceLastFlash = 0; // We just got a flash, so start counting over. - - if (thisWindowsSinceLastFlash<=3) { // We got a valid bit - - uint8_t inputBuffer = ptr->inputBuffer; // Compiler should do this optimization for us, but it don't - - inputBuffer <<= 1; // Make room for new bit (fills with a 0) - - if (thisWindowsSinceLastFlash<=1) { // Saw a 1 bit - - inputBuffer |= 0b00000001; // Save newly received 1 bit - - } - - // Here we look for a 1 followed by a 0 in the top two bits. We need this - // because there can potentially be a leading 1 in the bit stream if the - // first pulse of the 0 start bit happens to come right after an ambient - // trigger - this could look like a 1. This can only happen at the first pulse - // because after that we are pulsing often enough that there will never be - // an ambient trigger. - // So we use the pattern '10' as a start because if there is a leading '1' - // then there will be '11' at the beginning and the 1st '1' will not have a '0' - // after it. - // TODO: Explain this better with pictures. - - - if ( (inputBuffer & 0b11000000) == 0b10000000 ) { - - // TODO: check for overrun in lastValue and either flag error or increase buffer size - - ptr->inValue = inputBuffer; // Save the received byte (clobbers old if not read yet) - - inputBuffer =0; // Clear out the input buffer to look for next start bit - - - } - - ptr->inputBuffer = inputBuffer; - - } else { - - // Received an invalid bit (too long between last two detected flashes) - - ptr->inputBuffer = 0; // Start looking for start bit again. - - } - - } else { - - ptr->windowsSinceLastFlash++; // Keep count of how many windows since last flash - - // Note there here were do not check for overflow on windowsSinceLastFlash. - // We assume that an LED will spontaneously fire form ambient light in fewer - // than 255 time windows - - } - - - ptr--; - bitwalker >>=1; - - - } while (bitwalker); - -} - -// Is there a received data ready to be read on this face? - -bool irIsReadyOnFace( uint8_t led ) { - return( ir_rx_states[led].inValue != 0 ); -} - -// Read the most recently received data. Blocks if no data ready - -uint8_t irGetData( uint8_t led ) { - - ir_rx_state_t volatile *ptr = ir_rx_states + led; // This turns out to generate much more efficient code than array access. ptr saves 25 bytes. :/ Even so, the emitted ptr dereference code is awful. - - while (! ptr->inValue ); // Wait for high be to be set to indicate value waiting. - - // No need to atomic here since these accesses are lockstep, so the data can not be updated until we clear the ready bit - - uint8_t d = ptr->inValue; - - ptr->inValue=0; // Clear to indicate we read the value. Doesn't need to be atomic. - - return d & 0b00111111; // Don't show our internal preamble bits - -} - -// Simultaneously send data on all faces that have a `1` in bitmask - -void irSendDataBitmask(uint8_t data, uint8_t bitmask) { - - uint8_t bitwalker = 0b00100000; - - // Start things up, send initial pulse and start bit (1) - ir_tx_start( IR_SPACE_TIME_TICKS , bitmask , 1 ); - - ir_tx_sendpulse( 3 ); // Guard 0 bit to ensure real start bit is detected and not extraneous leading pulse. - - do { - - if (data & bitwalker) { - ir_tx_sendpulse( 1 ) ; - } else { - ir_tx_sendpulse( 3 ) ; - } - - bitwalker>>=1; - - } while (bitwalker); - - // TODO: Send a stop bit or some parity bit for error checking? Necessary? - - ir_tx_end(); - -} - -// Send data on specified face -// I put destination (face) first to mirror the stdio.h functions like fprintf(). - -void irSendData( uint8_t face , uint8_t data ) { - irSendDataBitmask( data , 1 << face ); -} - - -// Send data on all faces - -void irBroadcastData( uint8_t data ) { - - irSendDataBitmask( data , IR_ALL_BITS ); - -} diff --git a/libraries/blinklib/src/irdata.h b/libraries/blinklib/src/irdata.h deleted file mode 100644 index 5fbb83d0..00000000 --- a/libraries/blinklib/src/irdata.h +++ /dev/null @@ -1,6 +0,0 @@ - - // Called from timer on every click - - void updateIRComs(void); - - \ No newline at end of file diff --git a/libraries/blinklib/src/irdata.md b/libraries/blinklib/src/irdata.md deleted file mode 100644 index 07294075..00000000 --- a/libraries/blinklib/src/irdata.md +++ /dev/null @@ -1,79 +0,0 @@ -# Constraints - -We detect incoming light by charging the IR LEDs up and then waiting for them to discharge below the digital `1` threshold. The more light falling on the LED, the more quickly it discharges. See `core/ir.md` for details. - -Empirically the IR LEDs take longer than 1ms to discharge in ambient light, but on the order of a few us when directly illuminated by an IR LED on a nearby tile. - -To cause the receiver to discharge, the sender transmits a short flash. These are on the order of a few us long. - -# THEORY OF OPERATION - -A bit is represented by the space between two pulses of light. A short space is a '1' and a long space is a '0'. - -The long space must be shorter than 1ms to ensure that ambient light can not generate valid bits. Anytime we see a space longer than a long space we know the trigger was cause by ambient light rather than an actual transmsion, so we can ignore the bit and escape out of any reception in progress. - -A transmission is sent as a series of 8 bits. - -## Timing - -Since blinks use the cheap internal RC clock on the ATMEGA, we must be able to tolerate time bases that may be off by up to 10% between sender and receiver. - -We do this by carefully picking timing windows to send and detect flashes. - -The amount of time between consecutive flashes represents the bits as follows... - -| Bit | Time | -|-|-| -| 0 | 300us | -| 1 | 900us | - -The receiver periodically samples the IR LEDs to see if a flash (or ambient discharge) has occurred since the last sample. If the LED has been discharged then it is recharged at this time. - -The sampling takes place every 256us. After each sample, we keep track of how many samples it has been since the last time we saw a discharge on each LED. - -If it has been 4 or more samples, that means that is has been more than 1024us since the last discharge. Since the longest time that can happen between two flashes is 990us (900us + 10% clock difference), this means that the most recent trigger is not a valid bit. - -Because the transmit and receive clocks are asynchronous and possibly up to 10% off, there are several ways a transmitted bit can appear to the sampling receiver... - -| Samples between triggers | Transmitted bit | -|-|-| -| 0 | 1 | -| 1 | 1 | -| 2 | 0 | -| 3 | 0 | -| 4+ | Invalid | - -If it has been 0 or 1 samples since the last trigger, this corresponds to a valid transmitted `1` bit. - -![](img/1-bit-timing.png) - - -If it has been 2 or 3 samples since the last trigger, this corresponds to a valid transmitted `0` bit. - - -![](img/0-bit-timing.png) - - -## Preamble - -We always send a preamble of "110" before the data bits. This allows the receiver to detect the -incoming byte. This sequence is special. - -Because we always clear the buffer to zeros whenever we see an invalid bit, the only way a '1' can end up in the top bit of the buffer is if we have received 8 consecutive valid bits. This saves us needing a separate bit counter - the leading `1` gets shifted up the buffer naturally as each new valid it is received. - - -The following '0' guards against the case where we send an opening '1' bit but it just happens to come right after the LED had spontaneously triggered. In this case, we might see these two fires (the spontaneous one and the intentional start of the preamble one) as a valid '1' bit. The extra '0' ensures that only the second intentional '1' bit will have a '0' after it and the phantom '1' will be pushed off the top of the buffer. - -## Data - -There are 6 data bits after the preamble. MSB sent first. - -There should be an idle gap after the end of each transmission for synchronization. If we sent a continuous string of valid bits, it is possible that the "10" preamble pattern could appear in the data and prevent the receiver from syncing to the correct start of each byte. Having a gap between bytes ensure that no matter when the receiver starts receiving, it will only ever see valid full bytes. - -# Implementation - -Internally, we use an 8-bit buffer to store incoming bits. This is space efficient and allows us to accumulate newly received bits at a cost of only a left shift followed an OR. - -To check to see if a full valid byte has been received, we look at the top two bits of the buffer match the '10' preamble sequence to know if we have received a good transmission. This costs an AND followed by a compare. - -If we ever go too long between pulses, we reset the incoming buffer to '0' to avoid detecting a phantom message. diff --git a/libraries/blinklib/utils/pixelNormalizer.cpp b/libraries/blinklib/utils/pixelNormalizer.cpp deleted file mode 100644 index 6ec46300..00000000 --- a/libraries/blinklib/utils/pixelNormalizer.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - Happy New Years - 2018 - - Firecracker - - Rules: - If button pressed... - explode with firecracker like flashing of lights - when done, pick one of my neighbors to send the spark to - if receive spark, explode like firecracker - remember who I received it from - when done, pick one of my other neighbors to send the spark to - if no other neighbors available, firecracker is extinguished - -*/ - -#include "blinklib.h" -#include "blinkstate - -// Our local state. We also reuse these values as messages to send to neighbors - -enum state_t { - READY , // We will explode if we see an INFECT message on any face. - EXPLODING , // Running the explosion pattern. soruceFace is the face we got INFECTED from. - COOLDOWN, // Just a delay for visual effect. soruceFace is still the face we got INFECTED from. - INFECT // Trying to send an INFECT message to any neighbor besides sourceFace. -}; - -static state_t state = READY; - -#define NO_FACE FACE_COUNT // Use FACE_COUNT as a match-none special value for sourceFace - // We use this when the explosion was manually started so no - // face is the source. - - -static int sourceFace=NO_FACE; // The face we got the spark from. - // Only valid in EXPLODING, COOLDOWN, and INFECT - // NO_FACE if we did not get the spark from anywhere (was cause by button press) - -static int targetFace=NO_FACE; // The face we are sending the spark to. - // Only valid in INFECT - -static uint32_t nextStateTime; // Time we switch to next state. Valid in EXPLODING, COOLDOWN, and INFECT. - -// How long we show the effect once we get a spark -static const uint16_t explosionDurration_ms = 400; - -// How long after the effect ends before we send the spark out to a neighbor -static const uint16_t cooldownDurration_ms = 100; - -// How long we wait for a neighbor to ack our spark before giving up -static const uint16_t infectTimeout_ms = 200; - -// The color we show on an occupied face in READY state -static Color readyFaceColor; - -#include "Serial.h" - -void setup() { - // put your setup code here, to run once: - readyFaceColor = makeColorHSB( 25, 255, 128); // A nice smoldering orange -} - -// Returns a face that (1) has not yet expired, and (2) is not `exclude` -// Returns NO_FACE is no faces meet the criteria - -static byte pickSparkTarget( uint32_t now, byte exclude ) { - - // First tally up all the potential target faces - - byte potentialTargetList[FACE_COUNT]; - byte potentialTargetCount=0; - - FOREACH_FACE(f) { - - if ( () && (f!=exclude) ) { - - - potentialTargetList[ potentialTargetCount ] = f; - potentialTargetCount++; - } - - } - - if ( potentialTargetCount==0) { - // No place to send - return NO_FACE; - } - - // Pick which of the potential nodes is the winner... - - byte targetIndex = rand( potentialTargetCount-1 ); - - return potentialTargetList[ targetIndex ]; - -} - - -void loop() { - - // put your main code here, to run repeatedly: - uint32_t now = millis(); - - bool detonateFlag = false; // Set this to true to cause detonation - - // First get some situational awareness - - FOREACH_FACE(f) { - - if (irIsReadyOnFace(f)) { - - // Got something, so we know there is someone out there - neighboorExpireTime[f] = now + expireDurration_ms; - - // Clear to send on this face immediately to ping-pong messages at max speed without collisions - neighboorSendTime[f] = 0; - - byte receivedMessage = irGetData(f); - - if (receivedMessage==INFECT) { // We just got a spark! - - detonateFlag=true; - sourceFace=f; - - } else if (receivedMessage == EXPLODING && state==INFECT && f==targetFace) { - - // They got our INFECT message! - state=READY; - - } - } - } - - // if button pressed, firecracker - if (buttonSingleClicked()) { - detonateFlag=true; - sourceFace = NO_FACE; // Manually initiated, so no source - } - - - if (detonateFlag) { - state=EXPLODING; - nextStateTime=now+explosionDurration_ms; - } - - - // In all states, we show orange on any face that has a neighbor - // (may be covered by effect if we are EXPLODING) - - FOREACH_FACE(f) { - - if (neighboorExpireTime[f]>now) { - setFaceColor(f, readyFaceColor ); - } else { - setFaceColor(f,OFF); - } - - } - - // Note that we do not explicitly check for READY since in this state - // we don't really do anything (the original background that we already drew shows) - - if (state == EXPLODING ) { - - // While exploding we show the fireworks! - - if (now < nextStateTime ) { // Still exploding - - // Blink a random face white - setFaceColor( rand( FACE_COUNT -1 ) , WHITE ); - - } else { - - // Show's over folks! - state=COOLDOWN; - nextStateTime=now+cooldownDurration_ms; - - } - } - - // Note that we concatenate our if's here so that we do the right thing - // after a state change. Assumes state if's are listed in chronological order - - if (state == COOLDOWN ) { - - // We don't show anything in cool down - - if (now >= nextStateTime ) { // Done cooling down, so time to infect! - - targetFace = pickSparkTarget( now , sourceFace ); - - if ( targetFace != NO_FACE ) { - // We found a target - state=INFECT; - nextStateTime=now+cooldownDurration_ms; - } else { - // No suitable targets, fizzle out - state=READY; - } - - } - } - - if (state==INFECT) { - - if (now < nextStateTime ) { - - // Show the spark flying away - setFaceColor( targetFace , BLUE ); - - } else { - - // Give up trying to infect. The target is not acking us - state = READY; - - } - - } - - // Send out messages to our neighbors - - FOREACH_FACE(f) { - - if ( neighboorSendTime[f] <= now ) { // Time to send on this face? - - if (state==INFECT && f != targetFace ) { - - // This is a bit of a hack. We can't send our current state because then - // we would infect everyone. So we send READY because it is benign. - irSendData( f , READY ); - - } else { // In any state but INFECT - - irSendData( f , state ); - - } - - // Here we set a timeout to keep periodically probing on this face, but - // if there is a neighbor, they will send back to us as soon as they get what we - // just transmitted, which will make us immediately send again. So the only case - // when this probe timeout will happen is if there is no neighbor there. - - neighboorSendTime[f] = now + sendprobeDurration_ms; - - } - } - -} - - diff --git a/libraries/blinkstate/library.properties b/libraries/blinkstate/library.properties deleted file mode 100644 index 6aa8f263..00000000 --- a/libraries/blinkstate/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=Blink Stateful Interface -version=1.0 -author=Move38.com -maintainer=Move38.com -sentence=High level state model view for blinks game developement -paragraph= -category=Other -url=https://github.com/bigjosh/Move38-Arduino-Platform -architectures=avr -types=Arduino diff --git a/libraries/blinkstate/src/blinkstate.cpp b/libraries/blinkstate/src/blinkstate.cpp deleted file mode 100644 index fe3dd3ce..00000000 --- a/libraries/blinkstate/src/blinkstate.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * - * This library presents a higher level abstraction of state and communications and sits on top of the blinklib IR functions. - * - * In this view, each tile has a "state" that is represented by a number between 1 and 127. - * This state value is continuously broadcast on all of its faces. - * Each tile also remembers the most recently received state value from he neighbor on each of its faces. - * - * This library takes over the blinklib IR read functions since it needs to consume the events they produce. - * - * Note that the beacon transmissions only occur when the loop() function returns, so it is important - * that sketches using this model return from loop() frequently. This is modeled on the idomatic Arduino - * way of handling periodic callbacks... - * https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/main.cpp#L47 - * - */ - -#include -#include -#include -#include //rand() - -#define DEBUG_MODE - -#include - -#include "blinklib.h" - -#include "chainfunction.h" - -// Tell blinkstate.h to save the IR functions just for us... - -#define BLINKSTATE_CANNARY - -#include "blinkstate.h" - - -// ---- Keep track of neighbor IR states - -// TODO: The compiler hates these arrays. Maybe use a per-face struct so it can do indirect offsets? -// TODO: These struct even better if they are padded to a power of 2 like https://stackoverflow.com/questions/1239855/pad-a-c-structure-to-a-power-of-two - -// Last received value on this face, or 0 if no neighbor ever seen since startup - -static byte inValue[FACE_COUNT]; - -// Value we send out on this face - -static byte outValue[FACE_COUNT]; - -// Last time we saw a message on this face so -// we know if there is a neighbor there. -// Inits to 0 so all faces are initially expired. https://www.geeksforgeeks.org/g-fact-53/ -static uint32_t expireTime[FACE_COUNT]; - -// Assume no neighbor if we don't see a message on a face for this long -// TODO: Allow user to tweak this? -static const uint16_t expireDurration_ms = 100; - -// Next time we will send on this face. -// Reset to 0 anytime we get a message so we end up token passing across the link -static uint32_t neighboorSendTime[FACE_COUNT]; // inits to 0 on startup, so we will immediately send a probe on all faces. https://www.geeksforgeeks.org/g-fact-53/ - -// How often do we send a probe message on a face where we haven't seen anything in a while? -// TODO: Allow user to tweak this? -static const uint16_t sendprobeDurration_ms = 200; - - - -// check and see if any states recently updated.... - -static void updateIRFaces(uint32_t now) { - - FOREACH_FACE(f) { - - // Check for anything new coming in... - - if (irIsReadyOnFace(f)) { - - // Got something, so we know there is someone out there - expireTime[f] = now + expireDurration_ms; - - // Clear to send on this face immediately to ping-pong messages at max speed without collisions - neighboorSendTime[f] = 0; - - byte receivedMessage = irGetData(f); - - inValue[f] = receivedMessage; - - } - - // Send out if it is time.... - - if ( neighboorSendTime[f] <= now ) { // Time to send on this face? - - irSendData( f , outValue[f] ); - - // Here we set a timeout to keep periodically probing on this face, but - // if there is a neighbor, they will send back to us as soon as they get what we - // just transmitted, which will make us immediately send again. So the only case - // when this probe timeout will happen is if there is no neighbor there. - - neighboorSendTime[f] = now + sendprobeDurration_ms; - } - - } - -} - -// Called one per loop() to check for new data and repeat broadcast if it is time -// Note that if this is not called frequently then neighbors can appear to still be there -// even if they have been gone longer than the time out, and the refresh broadcasts will not -// go out often enough. - -// TODO: All these calls to millis() and subsequent calculations are expensive. Cache stuff and reuse. -// TODO: now will come as an arg soon with freeze-time branch - -void blinkStateOnLoop(void) { - - uint32_t now = millis(); - - updateIRFaces(now); - - -} - -// Make a record to add to the callback chain - -static struct chainfunction_struct blinkStateOnLoopChain = { - .callback = blinkStateOnLoop, - .next = NULL // This is a waste because it gets overwritten, but no way to make this un-initialized in C -}; - -// Something tricky here: I can not find a good place to automatically add -// our onLoop() hook at compile time, and we -// don't want to follow idiomatic Arduino ".begin()" pattern, so we -// hack it by adding here the first time anything that could use state -// stuff is called. This is an ugly hack. :/ - -// TODO: This is a good place for a GPIO register bit. Then we could inline the test to a single instruction., - -static uint8_t hookRegisteredFlag=0; // Did we already register? - -static void registerHook(void) { - if (!hookRegisteredFlag) { - addOnLoop( &blinkStateOnLoopChain ); - hookRegisteredFlag=1; - } -} - -// Manually add our hooks - -void blinkStateBegin(void) { - registerHook(); -} - - -// Returns the last received state on the indicated face -// Remember that getNeighborState() starts at 0 on powerup. -// so returns 0 if no neighbor ever seen on this face since power-up -// so best to only use after checking if face is not expired first. -// Note the a face expiring has no effect on the getNeighborState() - -byte getLastValueReceivedOnFace( byte face ) { - - return inValue[ face ]; - -} - -// Did the neighborState value on this face change since the -// last time we checked? -// Remember that getNeighborState starts at 0 on powerup. -// Note the a face expiring has no effect on the getNeighborState() - -byte didValueOnFaceChange( byte face ) { - static byte prevState[FACE_COUNT]; - - byte curState = getLastValueReceivedOnFace(face); - - if ( curState == prevState[face] ) { - return false; - } - prevState[face] = curState; - - return true; - -} - -// 0 if no messages recently received on the indicated face -// (currently configured to 100ms timeout in `expireDurration_ms` ) - -static byte isValueReceivedOnFaceExpired( byte face , uint32_t now ) { - - return expireTime[face] < now; - -} - - -// 0 if no messages recently received on the indicated face -// (currently configured to 100ms timeout in `expireDurration_ms` ) - -byte isValueReceivedOnFaceExpired( byte face ) { - - uint32_t now=millis(); - - return isValueReceivedOnFaceExpired( face , now ); - -} - -// Returns false if their has been a neighbor seen recently on any face, true otherwise. - -bool isAlone() { - - FOREACH_FACE(f) { - - if( !isValueReceivedOnFaceExpired(f) ) { - return false; - } - - } - return true; - -} - - -// Set our broadcasted state on all faces to newState. -// This state is repeatedly broadcast to any neighboring tiles. - -// By default we power up in state 0. - -void setValueSentOnAllFaces( byte newState ) { - - FOREACH_FACE(f) { - - outValue[f] = newState; - - } - -} - -// Set our broadcasted state on indicated face to newState. -// This state is repeatedly broadcast to the partner tile on the indicated face. - -// By default we power up in state 0. - -void setValueSentOnFace( byte newState , byte face ) { - - outValue[face] = newState; - -} - diff --git a/libraries/blinkstate/src/blinkstate.h b/libraries/blinkstate/src/blinkstate.h deleted file mode 100644 index 43f04f0e..00000000 --- a/libraries/blinkstate/src/blinkstate.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * blinkstate.h - * - * This defines a statefull view of the blinks tile interactions with neighbors. - * - * In this view, each tile has a "state" that is represented by a number between 1 and 127. - * This state value is continuously broadcast on all of its faces. - * Each tile also remembers the most recently received state value from he neighbor on each of its faces. - * - * Note that this library depends on the blinklib library for communications with neighbors. The blinklib - * IR read functions are not available when using the blinkstate library. - * - * Note that the beacon transmissions only occur when the loop() function returns, so it is important - * that sketches using this model return from loop() frequently. - * - */ - -#ifndef BLINKSTATE_H_ -#define BLINKSTATE_H_ - -#ifndef BLINKLIB_H_ - #error You must #include blinklib.h before blinkstate.h -#endif - - -// Manually add our hooks. -// Must be called before using any other blinkstate functions -// TODO: Now that blinkstate is the primary game-level API, maybe make this the default? - -void blinkStateBegin(void); - - -// Returns the last received state on the indicated face -// returns 0 if no neighbor ever seen on this face since power-up -// so best to only use after checking if face is not expired first. - -byte getLastValueReceivedOnFace( byte face ); - - -// Did the neighborState value on this face change since the -// last time we checked? - -// Note the a face expiring has no effect on the last value - -byte didValueOnFaceChange( byte face ); - -// false if messages have been recently received on the indicated face -// (currently configured to 100ms timeout in `expireDurration_ms` ) - -byte isValueReceivedOnFaceExpired( byte face ); - -// Returns false if their has been a neighbor seen recently on any face, returns true otherwise. -bool isAlone(); - -// Set value that will be continuously broadcast on all face. -// By default we power up with all faces sending the value 0. - -void setValueSentOnAllFaces( byte newState ); - - -// Set value that will be continuously broadcast on indicated face. - -// By default we power up with all faces sending the value 0. - -void setValueSentOnFace( byte value , byte face ); - - -#ifndef BLINKSTATE_CANNARY - - // We need to hide the direct IR functions or else they might consume IR events that we need to read - // This is kind of hackish, but can you think of a better way? - - // It gets worse, Arduino will not less us use an #error inside the #define, so we must resort to this ugly __force_error() shenanegans - // At least the user gets to see the error string.... :/ - - #define irIsReadyOnFace(face) __force_error("The irIsReadyOnFace() function is not available while using the blinkstate library") - - #define irGetData(led) __force_error("The irGetData() function is not available while using the blinkstate library") - - #define irGetErrorBits(face) __force_error("The irGetErrorBits() function is not available while using the blinkstate library") - -#endif - - - -#endif /* BLINKSTATE_H_ */ diff --git a/linkscripts/avr5.xn b/linkscripts/avr5.xn new file mode 100644 index 00000000..dd355e94 --- /dev/null +++ b/linkscripts/avr5.xn @@ -0,0 +1,368 @@ +/* Script for -n: mix text and data on same page */ +/* Copyright (C) 2014-2015 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr") +OUTPUT_ARCH(avr:5) + +/* I have no idea where these DATA length numbers come from, but they are clearly wrong */ +/* I overwrite them with the correct values in the MEMORY declarations */ + +__TEXT_REGION_LENGTH__ = DEFINED(__TEXT_REGION_LENGTH__) ? __TEXT_REGION_LENGTH__ : 0x1600; +__DATA_REGION_LENGTH__ = DEFINED(__DATA_REGION_LENGTH__) ? __DATA_REGION_LENGTH__ : 0xffa0; +__EEPROM_REGION_LENGTH__ = DEFINED(__EEPROM_REGION_LENGTH__) ? __EEPROM_REGION_LENGTH__ : 64K; +__FUSE_REGION_LENGTH__ = DEFINED(__FUSE_REGION_LENGTH__) ? __FUSE_REGION_LENGTH__ : 1K; +__LOCK_REGION_LENGTH__ = DEFINED(__LOCK_REGION_LENGTH__) ? __LOCK_REGION_LENGTH__ : 1K; +__SIGNATURE_REGION_LENGTH__ = DEFINED(__SIGNATURE_REGION_LENGTH__) ? __SIGNATURE_REGION_LENGTH__ : 1K; +__USER_SIGNATURE_REGION_LENGTH__ = DEFINED(__USER_SIGNATURE_REGION_LENGTH__) ? __USER_SIGNATURE_REGION_LENGTH__ : 1K; + +/* When changing the size of the text section, you must also update */ +/* `blink.upload.maximum_size` in boards.txt for the Arduinio IDE to */ +/* Show the correct `Sketch uses x bytes...` after compilization. */ + +/* The TEXT length reflects the maximum size of the built-in game in the flash */ +/* This is defined by the bootloader code in the BlinkBIOS project. */ +/* Remember that there is the built-in game at the base of flash, then the */ +/* current active game, then the BIOS at the top of flash. */ + +/* The DATA length comes right frm the datasheets. While SRAM nominally starts at */ +/* 0x60, the first 160 bytes are IO registers so not really allocatable. To be clean */ +/* I start the data region where usable RAM really starts. The LENGTH if 0x0400 comes */ +/* straight from the datasheets (1,024 dec bytes). */ + +/* When changing the size of the text section, you must also update */ +/* `blink.upload.maximum_size` in boards.txt for the Arduinio IDE to */ +/* show the correct `Sketch uses x bytes...` after compilization. */ + +/* When changing the size of the data section, you must also update */ +/* `blink.upload.maximum_data_size` in boards.txt for the Arduinio IDE to */ +/* show the correct `Global variables use...` after compilization. */ + + +MEMORY +{ + text (rx) : ORIGIN = 0, LENGTH = 0x1700 + data (rw!x) : ORIGIN = 0x800100, LENGTH = 0x400 + eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = __EEPROM_REGION_LENGTH__ + fuse (rw!x) : ORIGIN = 0x820000, LENGTH = __FUSE_REGION_LENGTH__ + lock (rw!x) : ORIGIN = 0x830000, LENGTH = __LOCK_REGION_LENGTH__ + signature (rw!x) : ORIGIN = 0x840000, LENGTH = __SIGNATURE_REGION_LENGTH__ + user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = __USER_SIGNATURE_REGION_LENGTH__ +} + +ENTRY(__vectors) + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.text : + { + *(.rel.text) + *(.rel.text.*) + *(.rel.gnu.linkonce.t*) + } + .rela.text : + { + *(.rela.text) + *(.rela.text.*) + *(.rela.gnu.linkonce.t*) + } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.rodata : + { + *(.rel.rodata) + *(.rel.rodata.*) + *(.rel.gnu.linkonce.r*) + } + .rela.rodata : + { + *(.rela.rodata) + *(.rela.rodata.*) + *(.rela.gnu.linkonce.r*) + } + .rel.data : + { + *(.rel.data) + *(.rel.data.*) + *(.rel.gnu.linkonce.d*) + } + .rela.data : + { + *(.rela.data) + *(.rela.data.*) + *(.rela.gnu.linkonce.d*) + } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + /* Internal text space or external memory. */ + .text : + { + *(.vectors) + KEEP(*(.vectors)) + /* For data that needs to reside in the lower 64k of progmem. */ + *(.progmem.gcc*) + /* PR 13812: Placing the trampolines here gives a better chance + that they will be in range of the code that uses them. */ + . = ALIGN(2); + __trampolines_start = . ; + /* The jump trampolines for the 16-bit limited relocs will reside here. */ + *(.trampolines) + *(.trampolines*) + __trampolines_end = . ; + /* avr-libc expects these data to reside in lower 64K. */ + *libprintf_flt.a:*(.progmem.data) + *libc.a:*(.progmem.data) + *(.progmem*) + . = ALIGN(2); + /* For future tablejump instruction arrays for 3 byte pc devices. + We don't relax jump/call instructions within these sections. */ + *(.jumptables) + *(.jumptables*) + /* For code that needs to reside in the lower 128k progmem. */ + *(.lowtext) + *(.lowtext*) + __ctors_start = . ; + *(.ctors) + __ctors_end = . ; + __dtors_start = . ; + *(.dtors) + __dtors_end = . ; + KEEP(SORT(*)(.ctors)) + KEEP(SORT(*)(.dtors)) + /* From this point on, we don't bother about wether the insns are + below or above the 16 bits boundary. */ + *(.init0) /* Start here after reset. */ + KEEP (*(.init0)) + *(.init1) + KEEP (*(.init1)) + *(.init2) /* Clear __zero_reg__, set up stack pointer. */ + KEEP (*(.init2)) + *(.init3) + KEEP (*(.init3)) + *(.init4) /* Initialize data and BSS. */ + KEEP (*(.init4)) + *(.init5) + KEEP (*(.init5)) + *(.init6) /* C++ constructors. */ + KEEP (*(.init6)) + *(.init7) + KEEP (*(.init7)) + *(.init8) + KEEP (*(.init8)) + *(.init9) /* Call main(). */ + KEEP (*(.init9)) + *(.text) + . = ALIGN(2); + *(.text.*) + . = ALIGN(2); + *(.fini9) /* _exit() starts here. */ + KEEP (*(.fini9)) + *(.fini8) + KEEP (*(.fini8)) + *(.fini7) + KEEP (*(.fini7)) + *(.fini6) /* C++ destructors. */ + KEEP (*(.fini6)) + *(.fini5) + KEEP (*(.fini5)) + *(.fini4) + KEEP (*(.fini4)) + *(.fini3) + KEEP (*(.fini3)) + *(.fini2) + KEEP (*(.fini2)) + *(.fini1) + KEEP (*(.fini1)) + *(.fini0) /* Infinite loop after program termination. */ + KEEP (*(.fini0)) + _etext = . ; + + } > text + + + /* These are our link to the boot loader */ + /* We can jump to these to call the appropriate function */ + + boot_reset = 0x3800; + boot_vector1 = 0x3804; + boot_vector2 = 0x3808; + boot_vector3 = 0x380c; + boot_vector4 = 0x3810; + boot_vector5 = 0x3814; + boot_vector6 = 0x3818; + boot_vector7 = 0x381c; + boot_vector8 = 0x3820; + boot_vector9 = 0x3824; + boot_vector10 = 0x3828; + boot_vector11 = 0x382c; + boot_vector12 = 0x3830; + boot_vector13 = 0x3834; + boot_vector14 = 0x3838; + boot_vector15 = 0x383c; + + + /* The NOLOAD makes it not show in in the HEX file */ + + .bss 0x0800100 (NOLOAD) : AT (ADDR (.bss)) + { + + /* Here are the data blocks that are shared with the BlinkBIOS. They *must* be at */ /* fixed addreses at the base of SRAM, so they must come first here. They also */ + /* must *not* get zeroed out, so the bss_start *must* come after them. */ + + *(.ipcram1) + KEEP( *(.ipcram1) ) + *(.ipcram2) + KEEP( *(.ipcram2) ) + *(.ipcram3) + KEEP( *(.ipcram3) ) + *(.ipcram4) + KEEP( *(.ipcram4) ) + *(.ipcram5) + KEEP( *(.ipcram5) ) + + /* + This __bss_start is used by the startup code to clear the bss to 0's + We put it below the ipcram so those do NOT get cleared. We are luckly + this happens to work since we can not change the base of `data` . + + We also can not aparently make a new memory region called something sensical like + `ram` becuase it seems like several tools have the name `data` hard coded. + Something reports the error `data not found` if we compile in Arduino IDE + with the ram region called `ram`. + */ + + PROVIDE (__bss_start = .) ; + *(.bss) + *(.bss*) + *(COMMON) + PROVIDE (__bss_end = .) ; + } > data + + /* + + We have to rename .data to .datax becuase the .data is hardcoded in the "special devices" + file in gcc so it gets fixed at 0x800100, but we want it to float so it can come behind the bss + + */ + + .datax : + { + PROVIDE (__data_start = .) ; + *(.data) + *(.data*) + *(.gnu.linkonce.d*) + *(.rodata) /* We need to include .rodata here if gcc is used */ + *(.rodata*) /* with -fdata-sections. */ + *(.gnu.linkonce.r*) + . = ALIGN(2); + _edata = . ; + PROVIDE (__data_end = .) ; + } > data AT> text + + __data_load_start = LOADADDR(.datax); + __data_load_end = __data_load_start + SIZEOF(.datax); + + /* Global data not cleared after reset. */ + .noinit ADDR(.datax) + SIZEOF (.datax) : AT (ADDR (.noinit)) + { + PROVIDE (__noinit_start = .) ; + *(.noinit*) + PROVIDE (__noinit_end = .) ; + _end = . ; + PROVIDE (__heap_start = .) ; + } > data + + .stackwatcher (NOLOAD) : + + { + /* This comes at the very end of the data section, so will be */ + /* the first thing clobbered by a blown stack. We can put a special value */ + /* here and if it gets clobbered then we know we blew the stack. */ + *.(.stackwatcher*) + + } > data + + + .eeprom : + { + /* See .data above... */ + KEEP(*(.eeprom*)) + __eeprom_end = . ; + } > eeprom + .fuse : + { + KEEP(*(.fuse)) + KEEP(*(.lfuse)) + KEEP(*(.hfuse)) + KEEP(*(.efuse)) + } > fuse + .lock : + { + KEEP(*(.lock*)) + } > lock + .signature : + { + KEEP(*(.signature*)) + } > signature + .user_signatures : + { + KEEP(*(.user_signatures*)) + } > user_signatures + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } +} diff --git a/linkscripts/readme.MD b/linkscripts/readme.MD new file mode 100644 index 00000000..665f0e95 --- /dev/null +++ b/linkscripts/readme.MD @@ -0,0 +1,51 @@ +We use a shared data block for communication between the bootloader code and the foreground game. This lets us load that stuff once rather than having to include it in every game image. + +The shared memory block lives at a fixed location at the bottom of RAM. + +We need a custom link script so we can force that block to this known location. + +The linker scripts here were copied from... + +C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\lib\ldscripts + +...which seems to be hardcoded into `gcc`. + +We add a new group of sections called `ipcram1`-`ipcram9` which hold the shared memory block. + +Note also that we mark these sections as `KEEP`, but they still seem to get garbage collected if not referenced, so we should make sure to reference them in the foreground code or else the foreground will put variables here and overwrite the bootloader's info. + +We had to make a new `.dataX` segment because the origin of the normal `.data` segment seems to be hardcoded into the "specific device" file in gcc... + +https://electronics.stackexchange.com/questions/408115/how-does-avr-gcc-linker-know-to-put-the-datasection-at-0x800100-rather-than + +It is ok, becuase the new script is much cleaner than the stock one, and also moves the `bss` segment down so that both the `ipcram` memory and the normal static variables can all be initialized to `0` in one big block. Note that we only want to zero out the `ipcram` in the BIOS startup, so the linker script for user programs will put the `bss` start above here. + +Another hard won discovery is that if you put anything into .bss, it seems to output the whole thing to the HEX file. We fix this by adding `(NOLOAD)` to the segment. Phew. + +To get Arduino use this custom script, we edit the `platform.txt` to have... + +``` +recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" -Wl,-verbose -Wl,--script="{{build.core}/linkerscripts/avr5.xn" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -lm +``` + +But it is better to pre-compile this BIOS code into a HEX file and then have the Arduino code blindly flash it using AVRDUDE during the download. To compile in AS7, we have to add this to the `Toolchain->Linker->Misc->Linker flags`... + +`-Wl,--script="../../../src/linkscripts/avr5.xn"` + +The `-Wl,` means "pass the next arg to the linker and the `--script=` [specifies the linker script](ftp://ftp.gnu.org/pub/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_2.html#SEC3). This sucks that we have to hard code the `avr5` in there but I think the mapping from MCU to those `avrX` code is hardcoded into gcc. + + +To put a variable into this new section, we use... + +``` +byte __attribute__((section ("ipcram1"))) counter=0; +``` + +More info: +https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html + +### Stack Overflow Detector + +We put the input section `.stackwatcher` after all the other stuff in the `data` memory section. We can then have the code put a special value here and periodically check that value to see if it changed. Since the stack grows down from the top of RAM, it will hit this first and so if anything here changes then we know that the stack got too big and overwrote it. + + \ No newline at end of file diff --git a/platform.txt b/platform.txt index a7682dd6..d100fe11 100644 --- a/platform.txt +++ b/platform.txt @@ -1,12 +1,12 @@ -# Move38 AVR Core and platform. +# Arduino AVR Core and platform. # ------------------------------ # # For more info: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification name=Move38 -version=1.0.3 +version=1.1.1 # AVR compile variables # --------------------- @@ -20,17 +20,33 @@ compiler.warning_flags.all=-Wall -Wextra # Default "compiler.path" is correct, change only if you want to override the initial value compiler.path={runtime.tools.avr-gcc.path}/bin/ compiler.c.cmd=avr-gcc -compiler.c.flags=-c -g -Os {compiler.warning_flags} -std=gnu11 -ffunction-sections -fdata-sections -MMD -compiler.c.elf.flags={compiler.warning_flags} -Os -Wl,--gc-sections +compiler.c.flags=-c -g -Os {compiler.warning_flags} -std=gnu11 -ffunction-sections -fdata-sections -MMD -flto -fno-fat-lto-objects +compiler.c.elf.flags={compiler.warning_flags} -Os -g -flto -mrelax -fuse-linker-plugin -Wl,--gc-sections compiler.c.elf.cmd=avr-gcc -compiler.S.flags=-c -g -x assembler-with-cpp + +compiler.S.flags=-c -g -x assembler-with-cpp -flto -MMD + compiler.cpp.cmd=avr-g++ -compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -compiler.ar.cmd=avr-ar +compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto + +compiler.ar.cmd=avr-gcc-ar compiler.ar.flags=rcs + compiler.objcopy.cmd=avr-objcopy compiler.objcopy.eep.flags=-O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 -compiler.elf2hex.flags=-O ihex -R .eeprom + +# We need to explicitly remove .BSS from the hex file since the linker inexplicably includes .bss in .text +# when we include .ipcram in .bss. + +# This 0x1700 is is the physical start address of the `built-in` program image area. This is +# defined in the blinkbios as `FLASH_BUILTIN_IMAGE_START_ADDRESS`. +# If you change this, then you must also update the LENGTH of the TEXT +# area in the MEMORY section of linkscripts/avr5.xn to match. +# Finally you must also update `blink.upload.maximum_size` in boards.txt +# so that the Arduino IDE reports the correct values for program size after a compile. +# Also remember that AVR flash memory is 16 bits wide so this number +# is 1/2 what you expect it to be if you are thinking in bytes. +compiler.elf2hex.flags=--change-addresses 0x1700 -O ihex -R .eepromm compiler.elf2hex.cmd=avr-objcopy compiler.ldflags= compiler.size.cmd=avr-size @@ -65,15 +81,18 @@ archive_file_path={build.path}/{archive_file} recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}" ## Combine gc-sections, archives, and objects -recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -lm +#recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -Wl,--script={runtime.platform.path}/linkscripts/avr5.xn -Wl,-nostartfiles -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -lm +#recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -Wl,--print-gc-sections -Wl,--script={runtime.platform.path}/linkscripts/avr5.xn -Wl,-Map,{build.path}/out.map -nostartfiles -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -lm +# Ok, I know that hard-coded path to the startup.S.o is ugly, but it took me a full day to figure out that the startup file can not be inside the archive and now I just need it to works so don't judge me. +recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -Wl,--script={runtime.platform.path}/linkscripts/avr5.xn -nostartfiles -o "{build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -lm ## Create output files (.eep and .hex) recipe.objcopy.eep.pattern="{compiler.path}{compiler.objcopy.cmd}" {compiler.objcopy.eep.flags} {compiler.objcopy.eep.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.eep" recipe.objcopy.hex.pattern="{compiler.path}{compiler.elf2hex.cmd}" {compiler.elf2hex.flags} {compiler.elf2hex.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.hex" -## Save compiled hex +## Save hex recipe.output.tmp_file={build.project_name}.hex -recipe.output.save_file={build.project_name}_{build.mcu}_{build.f_cpu}.hex +recipe.output.save_file={build.project_name}.{build.variant}.hex ## Compute size recipe.size.pattern="{compiler.path}{compiler.size.cmd}" -A "{build.path}/{build.project_name}.elf" @@ -88,42 +107,56 @@ recipe.preproc.includes="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} preproc.macros.flags=-w -x c++ -E -CC recipe.preproc.macros="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} {preproc.macros.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{preprocessed_file_path}" - # AVR Uploader/Programmers tools # ------------------------------ tools.avrdude.path={runtime.tools.avrdude.path} tools.avrdude.cmd.path={path}/bin/avrdude -tools.avrdude.config.path={runtime.platform.path}/avrdude.conf +tools.avrdude.config.path={path}/etc/avrdude.conf -# arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed} - -tools.avrdude.program.params.verbose=-v -tools.avrdude.program.params.quiet=-q -q -tools.avrdude.program.params.noverify=-V -tools.avrdude.program.pattern="{cmd.path}" "-C{config.path}" {program.verbose} {program.verify} -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{build.path}/{build.project_name}.hex:i" +tools.avrdude.network_cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA tools.avrdude.upload.params.verbose=-v tools.avrdude.upload.params.quiet=-q -q - -# tools.avrdude.upload.verify is needed for backwards compatibility with AVRDUDE 6.3.0 and IDE 1.6.8 or older, IDE 1.6.9 or newer overrides this value +# tools.avrdude.upload.verify is needed for backwards compatibility with IDE 1.6.8 or older, IDE 1.6.9 or newer overrides this value tools.avrdude.upload.verify= tools.avrdude.upload.params.noverify=-V +# We replace the upload pattern with the program pattern because for now blinks code comes via programmer, +# so putting this here lets us push the convenient "Upload" button rather than having to do the "Via Programmer" menu pick. +# The -B 5 speeds up the clock to 200KHz which is the fastest we can go with the currently programmed 1MHz MCU clock at startup +#Note that this used to be set to 250KHz but I found two different blinks that would not program at this speed + +# The second -U:flash programs the BlinkBIOS image into the bootlader are of the flash (check out the BlinkBIOS project for more info) +# The -U:efuse programs the extended fuse to enable BOOTRST. This makes the chip jump to the bootloader area (with BlinkBIOS) on reset rather than 0x0000 +# The -u means "don't change the fuses back to what they were when we started". Silly, yes. +# The -cusbtiny is hardcoded in there becuase the {protocol} does not seem to work with the upload event +tools.avrdude.upload.pattern="{cmd.path}" "-C{config.path}" -B 5 {upload.verbose} {upload.verify} -p{build.mcu} -cusbtiny "-Uflash:w:{build.path}/{build.project_name}.hex:i" "-Uflash:w:{runtime.platform.path}/bootloaders/BlinkBIOS.hex:i" "-Uefuse:w:0xf8:m" -u +#tools.avrdude.upload.pattern="{cmd.path}" "-C{config.path}" {upload.verbose} {upload.verify} -p{build.mcu} -c{upload.protocol} "-P{serial.port}" -b{upload.speed} -D "-Uflash:w:{build.path}/{build.project_name}.hex:i" "-Uflash:w:{build.core}/bootloader/XXXbin/blinkboot.hex:i" + +tools.avrdude.program.params.verbose=-v +tools.avrdude.program.params.quiet=-q -q +# tools.avrdude.program.verify is needed for backwards compatibility with IDE 1.6.8 or older, IDE 1.6.9 or newer overrides this value +tools.avrdude.program.verify= +tools.avrdude.program.params.noverify=-V -tools.avrdude.upload.pattern="{cmd.path}" "-C{config.path}" {program.verbose} {program.verify} -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{build.path}/{build.project_name}.hex:i" +# This just makes the "Upload via programmer" menu option work and do the same thing as the Upload button +# The -B 5 speeds up the clock to 200KHz which is the fastest we can go with the currently programmed 1MHz MCU clock at startup +#Note that this used to be set to 250KHz but I found two different blinks that would not program at this speed +tools.avrdude.program.pattern="{cmd.path}" "-C{config.path}" -B 5 {program.verbose} {program.verify} -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{build.path}/{build.project_name}.hex:i" "-Uflash:w:{runtime.platform.path}/bootloaders/BlinkBIOS.hex:i" -u tools.avrdude.erase.params.verbose=-v tools.avrdude.erase.params.quiet=-q -q -tools.avrdude.erase.pattern="{cmd.path}" "-C{config.path}" -v -p{build.mcu} -c{protocol} {program.extra_params} -e -Ulock:w:{bootloader.unlock_bits}:m -Uefuse:w:{bootloader.extended_fuses}:m -Uhfuse:w:{bootloader.high_fuses}:m -Ulfuse:w:{bootloader.low_fuses}:m +tools.avrdude.erase.pattern="{cmd.path}" "-C{config.path}" {erase.verbose} -p{build.mcu} -c{protocol} {program.extra_params} -e -Ulock:w:{bootloader.unlock_bits}:m -Uefuse:w:{bootloader.extended_fuses}:m -Uhfuse:w:{bootloader.high_fuses}:m -Ulfuse:w:{bootloader.low_fuses}:m -# there is no bootloader for blinks -#tools.avrdude.bootloader.params.verbose=-v -#tools.avrdude.bootloader.params.quiet=-q -q -#tools.avrdude.bootloader.pattern="{cmd.path}" "-C{config.path}" -v -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{runtime.platform.path}/bootloaders/{bootloader.file}:i" -Ulock:w:{bootloader.lock_bits}:m +tools.avrdude.bootloader.params.verbose=-v +tools.avrdude.bootloader.params.quiet=-q -q +tools.avrdude.bootloader.pattern="{cmd.path}" "-C{config.path}" {bootloader.verbose} -p{build.mcu} -c{protocol} {program.extra_params} "-Uflash:w:{runtime.platform.path}/bootloaders/BlinkBIOS.hex:i" "-Uefuse:w:0xf8:m" -u tools.avrdude_remote.upload.pattern=/usr/bin/run-avrdude /tmp/sketch.hex {upload.verbose} -p{build.mcu} +tools.avrdude.upload.network_pattern="{network_cmd}" -address {serial.port} -port {upload.network.port} -sketch "{build.path}/{build.project_name}.hex" -upload {upload.network.endpoint_upload} -sync {upload.network.endpoint_sync} -reset {upload.network.endpoint_reset} -sync_exp {upload.network.sync_return} + # USB Default Flags # Default blank usb manufacturer will be filled in at compile time # - from numeric vendor ID, set to Unknown otherwise diff --git a/programmers.txt b/programmers.txt new file mode 100644 index 00000000..e1a7cae2 --- /dev/null +++ b/programmers.txt @@ -0,0 +1,40 @@ +avrisp.name=Blinks AVR ISP +avrisp.communication=serial +avrisp.protocol=stk500v1 +avrisp.program.protocol=stk500v1 +avrisp.program.tool=avrdude +avrisp.program.extra_params=-P{serial.port} + +avrispmkii.name=Blinks AVRISP mkII +avrispmkii.communication=usb +avrispmkii.protocol=stk500v2 +avrispmkii.program.protocol=stk500v2 +avrispmkii.program.tool=avrdude +avrispmkii.program.extra_params=-Pusb + +usbtinyisp.name=Blinks USBtinyISP +usbtinyisp.protocol=usbtiny +usbtinyisp.program.tool=avrdude +usbtinyisp.program.extra_params= + +arduinoisp.name=Blinks ArduinoISP +arduinoisp.protocol=arduinoisp +arduinoisp.program.tool=avrdude +arduinoisp.program.extra_params= + + +usbasp.name=Blinks USBasp +usbasp.communication=usb +usbasp.protocol=usbasp +usbasp.program.protocol=usbasp +usbasp.program.tool=avrdude +usbasp.program.extra_params=-Pusb + +arduinoasisp.name=Blinks Arduino as ISP +arduinoasisp.communication=serial +arduinoasisp.protocol=arduino +arduinoasisp.speed=19200 +arduinoasisp.program.protocol=arduino +arduinoasisp.program.speed=19200 +arduinoasisp.program.tool=avrdude +arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed} diff --git a/variants/legacy/hardware.h b/variants/legacy/hardware.h deleted file mode 100644 index 0fba0ff9..00000000 --- a/variants/legacy/hardware.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - * hardware.h - * - * Defines the location of all the hardware - * This header matches all boards that do NOT have the pretty logo on the front. - * Less than 100 boards like this, so someday hopefully we can retire this header. - * - * - * Created: 7/23/2017 9:50:54 PM - */ - - -#ifndef HARDWARE_H_ -#define HARDWARE_H_ - -#include -#include -#include "utils.h" - - -/*** PIXELS ***/ - -// Note that we depend on PIXEL_COUNT from blinks.h - -// Common Anodes - We drive these 1 to select Pixel. - -#define PIXEL0_PORT PORTB -#define PIXEL0_DDR DDRB -#define PIXEL0_BIT 6 - -#define PIXEL1_PORT PORTD -#define PIXEL1_DDR DDRD -#define PIXEL1_BIT 0 - -#define PIXEL2_PORT PORTB -#define PIXEL2_DDR DDRB -#define PIXEL2_BIT 7 - -#define PIXEL3_PORT PORTD -#define PIXEL3_DDR DDRD -#define PIXEL3_BIT 1 - -#define PIXEL4_PORT PORTD -#define PIXEL4_DDR DDRD -#define PIXEL4_BIT 4 - -#define PIXEL5_PORT PORTD -#define PIXEL5_DDR DDRD -#define PIXEL5_BIT 2 - -// RGB Sinks - We drive these cathodes low to light the selected color (note that BLUE has a charge pump on it) -//This will eventually be driven by timers - -#define LED_R_PORT PORTD -#define LED_R_DDR DDRD -#define LED_R_BIT 6 - -#define LED_G_PORT PORTD -#define LED_G_DDR DDRD -#define LED_G_BIT 5 - -#define LED_B_PORT PORTD -#define LED_B_DDR DDRD -#define LED_B_BIT 3 - -// This pin is used to sink the blue charge pump -// We drive this HIGH to turn off blue, otherwise blue led could -// come on if the battery voltage is high enough to overcome the forward drop on the -// blue LED + Schottky - -#define BLUE_SINK_PORT PORTE -#define BLUE_SINK_DDR DDRE -#define BLUE_SINK_BIT 3 - -/*** IR ***/ - -// IR transceivers -// There are 6 IR LEDs - one for each face - -#define IR_CATHODE_PORT PORTC -#define IR_CATHODE_DDR DDRC -#define IR_CATHODE_PIN PINC - -#define IR_ANODE_PORT PORTB -#define IR_ANODE_DDR DDRB -#define IR_ANODE_PIN PINB - - -// CONSTRAINTS: -// Note that all IR anodes must be on same port, as do all IR cathodes. -// Cathodes must have pin change interrupt -// -// Each anode must be on the same bit as the corresponding anode. (this could be relaxed with extra code) - -// All of the 6 GPIO bits used by IR pins. Also assumes these are the same bits in the pin change mask register. - -#define IR_BITS (_BV( 0 )|_BV( 1 )|_BV( 2 )|_BV( 3 )|_BV( 4 )|_BV( 5 )) - -// IR pin change interrupts are unused so far, but we will want them for waking from sleep soon... -// TODO: Wake on IR change. - -// We want a pin change interrupt on the CATHODES since these will get charged up -// and then exposure will make them go low. -// PORTC is connected to the cathodes, and they are on PCINT0-PCINT5 -// which is controlled by PCIE0 - -/* - PCICR - Bit 1 � PCIE1:?Pin Change Interrupt Enable 1 - When the PCIE1 bit is set and the I-bit in the Status Register (SREG) is set, pin change interrupt 1 is - enabled. Any change on any enabled PCINT[14:8] pin will cause an interrupt. The corresponding interrupt - of Pin Change Interrupt Request is executed from the PCI1 Interrupt Vector. PCINT[14:8] pins are - enabled individually by the PCMSK1 Register. -*/ - -/* - PCMSK1 - Bits 0, 1, 2, 3, 4, 5, 6 � PCINT8, PCINT9, PCINT10, PCINT11, PCINT12, PCINT13, PCINT14:?Pin - Change Enable Mask - Each PCINT[15:8]-bit selects whether pin change interrupt is enabled on the corresponding I/O pin. If - PCINT[15:8] is set and the PCIE1 bit in PCICR is set, pin change interrupt is enabled on the - corresponding I/O pin. If PCINT[15:8] is cleared, pin change interrupt on the corresponding I/O pin is - disabled. -*/ - - -#define IR_PCI PCIE1 -#define IR_ISR PCINT1_vect -#define IR_MASK PCMSK1 // Each bit here corresponds to 1 pin -#define IR_PCINT IR_BITS - -/*** Button ***/ - -#define BUTTON_PORT PORTD -#define BUTTON_PIN PIND -#define BUTTON_BIT 7 - -#define BUTTON_PCI PCIE2 -#define BUTTON_ISR PCINT2_vect -#define BUTTON_MASK PCMSK2 -#define BUTTON_PCINT PCINT23 - -#define BUTTON_DOWN() (!TBI(BUTTON_PIN,BUTTON_BIT)) // PCINT23 - pulled low when button pressed - - -/*** SERCIVE PORT ***/ - -// Do not define SP_PRESENT - - // There is no good way to have a board variant only include compatible libraries in Arduino IDE, so - // we are stuck defining these even though they are wrong. If we do not, then ARDUINO still compiles - // the Serial class even if we do not include it and that causes errors when it pulls in the sp.x files. - - - // Service port hardware - -// Pins as digital IO - -#define SP_A_PORT PORTE -#define SP_A_DDR DDRE -#define SP_A_BIT 2 - -#define SP_R_PORT PORTD -#define SP_R_DDR DDRD -#define SP_R_BIT 0 - -#define SP_T_PORT PORTD -#define SP_T_DDR DDRD -#define SP_T_BIT 1 - -// Serial port hardware on service port - -#define SP_SERIAL_CTRL_REG UCSR0A -#define SP_SERIAL_DATA_REG UDR0 -#define SP_SERIAL_READY_BIT RXC0 - - -#endif /* HARDWARE_H_ */ \ No newline at end of file diff --git a/variants/standard/dummy.txt b/variants/standard/dummy.txt new file mode 100644 index 00000000..dc18ac6a --- /dev/null +++ b/variants/standard/dummy.txt @@ -0,0 +1 @@ +This file is only here so git will make this directory. \ No newline at end of file diff --git a/variants/standard/hardware.h b/variants/standard/hardware.h deleted file mode 100644 index 99d58927..00000000 --- a/variants/standard/hardware.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * hardware.h - * - * Defines the location of all the hardware - * This header matches all boards that *DO* have the pretty logo on the front (newer than 1/1/17). - * Less than 100 boards older than this, hopefully we can retire the legacy header and move this to core - * - */ - - -#ifndef HARDWARE_H_ -#define HARDWARE_H_ - -#include -#include -//#include "utils.h" - -/*** PIXELS ***/ - -// Note that we depend on PIXEL_COUNT from blinks.h - -// Common Anodes - We drive these 1 to select Pixel. - -#define PIXEL0_PORT PORTB -#define PIXEL0_DDR DDRB -#define PIXEL0_BIT 6 - -#define PIXEL1_PORT PORTE -#define PIXEL1_DDR DDRE -#define PIXEL1_BIT 0 - -#define PIXEL2_PORT PORTB -#define PIXEL2_DDR DDRB -#define PIXEL2_BIT 7 - -#define PIXEL3_PORT PORTE -#define PIXEL3_DDR DDRE -#define PIXEL3_BIT 1 - -#define PIXEL4_PORT PORTD -#define PIXEL4_DDR DDRD -#define PIXEL4_BIT 4 - -#define PIXEL5_PORT PORTD -#define PIXEL5_DDR DDRD -#define PIXEL5_BIT 2 - -// RGB Sinks - We drive these cathodes low to light the selected color (note that BLUE has a charge pump on it) -//This will eventually be driven by timers - -#define LED_R_PORT PORTD -#define LED_R_DDR DDRD -#define LED_R_BIT 6 - -#define LED_G_PORT PORTD -#define LED_G_DDR DDRD -#define LED_G_BIT 5 - -#define LED_B_PORT PORTD -#define LED_B_DDR DDRD -#define LED_B_BIT 3 - -// This pin is used to sink the blue charge pump -// We drive this HIGH to turn off blue, otherwise blue led could -// come on if the battery voltage is high enough to overcome the forward drop on the -// blue LED + Schottky - -#define BLUE_SINK_PORT PORTE -#define BLUE_SINK_DDR DDRE -#define BLUE_SINK_BIT 3 - -/*** IR ***/ - -// IR transceivers -// There are 6 IR LEDs - one for each face - -#define IR_CATHODE_PORT PORTC -#define IR_CATHODE_DDR DDRC -#define IR_CATHODE_PIN PINC - -#define IR_ANODE_PORT PORTB -#define IR_ANODE_DDR DDRB -#define IR_ANODE_PIN PINB - - -// CONSTRAINTS: -// Note that all IR anodes must be on same port, as do all IR cathodes. -// Cathodes must have pin change interrupt -// -// Each anode must be on the same bit as the corresponding anode. (this could be relaxed with extra code) - -// All of the 6 GPIO bits used by IR pins. Also assumes these are the same bits in the pin change mask register. - -#define IR_BITS (_BV( 0 )|_BV( 1 )|_BV( 2 )|_BV( 3 )|_BV( 4 )|_BV( 5 )) - -// IR pin change interrupts are unused so far, but we will want them for waking from sleep soon... -// TODO: Wake on IR change. - -// We want a pin change interrupt on the CATHODES since these will get charged up -// and then exposure will make them go low. -// PORTC is connected to the cathodes, and they are on PCINT0-PCINT5 -// which is controlled by PCIE0 - -/* - PCICR - Bit 1 � PCIE1:?Pin Change Interrupt Enable 1 - When the PCIE1 bit is set and the I-bit in the Status Register (SREG) is set, pin change interrupt 1 is - enabled. Any change on any enabled PCINT[14:8] pin will cause an interrupt. The corresponding interrupt - of Pin Change Interrupt Request is executed from the PCI1 Interrupt Vector. PCINT[14:8] pins are - enabled individually by the PCMSK1 Register. -*/ - -/* - PCMSK1 - Bits 0, 1, 2, 3, 4, 5, 6 � PCINT8, PCINT9, PCINT10, PCINT11, PCINT12, PCINT13, PCINT14:?Pin - Change Enable Mask - Each PCINT[15:8]-bit selects whether pin change interrupt is enabled on the corresponding I/O pin. If - PCINT[15:8] is set and the PCIE1 bit in PCICR is set, pin change interrupt is enabled on the - corresponding I/O pin. If PCINT[15:8] is cleared, pin change interrupt on the corresponding I/O pin is - disabled. -*/ - - -#define IR_PCI PCIE1 -#define IR_ISR PCINT1_vect -#define IR_MASK PCMSK1 // Each bit here corresponds to 1 pin -#define IR_PCINT IR_BITS - -/*** Button ***/ - -#define BUTTON_PORT PORTD -#define BUTTON_PIN PIND -#define BUTTON_BIT 7 - -#define BUTTON_PCI PCIE2 -#define BUTTON_ISR PCINT2_vect -#define BUTTON_MASK PCMSK2 -#define BUTTON_PCINT PCINT23 - -#define BUTTON_DOWN() (!TBI(BUTTON_PIN,BUTTON_BIT)) // PCINT23 - pulled low when button pressed - - - -/*** SERVICE PORT ***/ - -#define SP_PRESENT // Indicate that we do have a service port on this board - -// Service port hardware - -// Pins as digital IO - -#define SP_A_PORT PORTE -#define SP_A_DDR DDRE -#define SP_A_BIT 2 - -#define SP_R_PORT PORTD -#define SP_R_DDR DDRD -#define SP_R_BIT 0 - -#define SP_T_PORT PORTD -#define SP_T_DDR DDRD -#define SP_T_BIT 1 - -// Serial port hardware on service port - -#define SP_SERIAL_CTRL_REG UCSR0A -#define SP_SERIAL_DATA_REG UDR0 -#define SP_SERIAL_READY_BIT RXC0 - -#endif /* HARDWARE_H_ */ \ No newline at end of file