30 Days Lost in Space: Week 3
Day 16: A fancy new display - 12/1/2025
I keep forgetting about the plot for this course, where I've crash landed on an alien planet and I'm stuck underwater, repairing my ship to be able to get home. This week we are trying to display the depth of our ship under water, decrypt some coded message, and limit the rate of ascent toward the surface so the ship doesn't explode.
- Hardware:
- 7 segment display
- Software:
- 7 segment display library ("TM1627Display")
Hardware
7 segment display:
. AF BGE CD
We connect 5v to VCC and Ground to Ground on the 7 segment display.
- CLK = clock pin; connect this to pin 6
- DIO = digital input output; connect this to pin 5
Software
We'll be using the 7 segment display library library ("TM1627Display").
Each of the 7 segments in each digit of the display can be turned on or off with code. Do this in various combinations to create digits. This library makes this work easy for us with the setSegment() function.
Here is today's completed code:
#include "Arduino.h"#include <TM1637Display.h>const byte CLK_PIN = 6;const byte DIO_PIN = 5;TM1637Display lander_display = TM1637Display(CLK_PIN, DIO_PIN);const byte all_on[] = { 0b11111111,0b11111111,0b11111111,0b11111111, };const byte done[] = {SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_C | SEG_E | SEG_G,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G};void setup() {lander_display.setBrightness(7);}void loop() {lander_display.clear();delay(1000);lander_display.setSegments(all_on);delay(1000);lander_display.clear();delay(1000);for (int i = 0; i < 4; i++) {lander_display.showNumberDecEx(1200, 0b1000000);delay(500);lander_display.clear();delay(500);}for (int i = -100; i <= 100; i++) {lander_display.showNumberDec(i);delay(50);}delay(1000);lander_display.clear();delay(1000);lander_display.setSegments(done);delay(10000);}
There are a few ways to tell the 7 segment display what we want to show. We can give it binary number notation and bits:
const byte all_on[] = { 0b11111111,0b11111111,0b11111111,0b11111111, };
Or we can offer it something more human-readable:
const byte all_on[] = {SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F};
And here is my name:
const byte joey[] = {SEG_B | SEG_C | SEG_D,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G,SEG_B | SEG_C | SEG_D | SEG_F | SEG_G};
Day 17: H jyfwapj tlzzhnl - 12/2/2025
Hardware
- No hardware lesson today
Software
- Encryption the art of turning information into a jumble of nonsense that only someone with the right key can make sense of.
- Caesar Cipher: Shift every letter in the alphabet by a certain number of letters. For example, "A" becomes "B", "B" becomes "C" and so on. This shift value is the encryption key.
- Public Key Encryption: uses a pair of keys, public and private.
- Example: Email address is public key, password is private key
Anyway, the code for today's lesson is encrypted and looks wild, and our task is to decrypt it.
Here is the starting code:
/** 29 Wtrl - Ehlm bg Litvx* Wtr 06 - H jyfwapj tlzzhnl** Extkg fhkx tm ammil://bgoxgmk.bh/twoxgmnkx** Vhgzktmnetmbhgl, by rhn'kx kxtwbgz mabl rhn'ox lnvvxllneer wxvhwxw max* mhi-lxvkxm ldxmva. By rhn vhfibex tgw niehtw mabl ldxmva mh rhnk AXKH* pbma max ltfx pbkbgz tl hg Wtr 05 maxg rhn'ee lxx makxx "dxrl" wblietrxw* hoxk tgw hoxk pbma t uetgd uxyhkx max ybklm hgx bl wblietrxw.** By mabl wxvhwxw ldxmva whxlg'm vhfibex ftdx lnkx rhn ikhixker bgvenwxw* gnfxkbv wbzbml bg rhnk wxvhwx! By max gnfuxkl wbwg'm zxm wxvhwxw maxg* mabl wxvhwxw ldxmva phg'm vhfibex pbmahnm xkkhkl!** Texq Xlvaxgtnxk* Wtobw Lvafbwm* Zkxz Ersxgzt*/// Xqiebvbmer bgvenwx Tkwnbgh.a#bgvenwx <Tkwnbgh.a>// Bgvenwx MF0526 ebuktkr ybex#bgvenwx <MF0526Wblietr.a>// Fhwnex vhggxvmbhg ibgl (Wbzbmte Ibgl)vhglm urmx VED_IBG = 5;vhglm urmx WBH_IBG = 4;// Tfhngm hy mbfx (bg fl) mh wxetr uxmpxxg hnk wxvbiaxkxw dxr otenxlvhglm nglbzgxw bgm WBLIETR_WXETR = 1999;MF0526Wblietr axkh_wblietr(VED_IBG, WBH_IBG);vhglm nglbzgxw bgm OTEBWTMBHG_WTMT[] = {'u',16,"t",9qtw1v,9u0999009099009099,"xz",'F'-'5','w',"mpxgmr-lxoxg",9436-5,243,9q2,-16-52710,'j',471,9u0099009099000909,9321,9qwy2,31};ohbw lxmni() {}ohbw ehhi() {axkh_wblietr.lxmUkbzamgxll(6);axkh_wblietr.vextk();wxetr(WBLIETR_WXETR);axkh_wblietr.lahpGnfuxkWxv(OTEBWTMBHG_WTMT['G'/904]);wxetr(WBLIETR_WXETR);axkh_wblietr.lahpGnfuxkWxv(OTEBWTMBHG_WTMT['J'/9u0990]);wxetr(WBLIETR_WXETR);axkh_wblietr.lahpGnfuxkWxv(OTEBWTMBHG_WTMT[959-'$']);wxetr(WBLIETR_WXETR);}
The shift number for this is -19; so ohbw ehhi becomes void loop and Wtr 06 becomes Day 17.
The course provided a website where we can input the encrypted message to figure out the cipher and decrypt it. But I wanted to take on the programming challenge of building a tool myself. Here is my solution, written in JavaScript:
function calculateNewLocation(key, location, length) {let newLocation = key + location;newLocation = ((newLocation % length) + length) % length;return newLocation;}function caesarCipher(string, key) {if (key === 0) {return string;}const alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];const digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];const alphabetLength = alphabet.length;const digitsLength = digits.length;const encryption = string.split('').map((char) => {const isUpperCase = char === char.toUpperCase() && char !== char.toLowerCase();const lowerCaseChar = char.toLowerCase();const alphabetLocation = alphabet.indexOf(lowerCaseChar);if (alphabetLocation !== -1) {const newLocation = calculateNewLocation(key, alphabetLocation, alphabetLength);let newChar = alphabet[newLocation];return isUpperCase ? newChar.toUpperCase() : newChar;}const digitLocation = digits.indexOf(char);if (digitLocation !== -1) {const newLocation = calculateNewLocation(key, digitLocation, digitsLength);return digits[newLocation];}return char;}).join("");return encryption;}
This function helps us with circular wrapping, a problem that arises when the cipher key gives us a negative index that is larger than the array's length:
function calculateNewLocation(key, location, length) {let newLocation = key + location;newLocation = ((newLocation % length) + length) % length;return newLocation;}
Otherwise we are simply mapping over the provided string, figuring out if we're working with an upper- or lowercase letter, determine if we are working with an alphabetical character or a numeral, calculating the new version of that alphabetical character or numeral, and ignoring other characters (spaces, parenthesis, etc.). Fun little challenge.
If we run the decrypted code, our Arduino (same hardware setup as yesterday) flashes a series of numbers that we will need in the coming lesson.
Day 18: The surface seems so much closer - 12/3/2025
- Hardware:
- Rotary encoder
- Software:
- Interrupts
attachInterrupt()digitalPinToInterrupt()
- Interrupts
Hardware
The Rotary Encoder sends directions of rotation signals and the amount of turns. It can also send push signals, but we won't be using that today. As you turn the encoder, it clicks. Every one of those clicks is counted and the direction it's turned is noted. These two pieces of information are sent to the microcontroller as high and low signals. The rotary encoder is useful for scrolling through menus and adjusting variables on the fly. We will be using it to control the ascent and descent of our ship between the ocean floor and the ocean surface.
There are two types of encoders: absolute and relative.
- An absolute encoder has a unique value for every specific angular position of the knob; there is a maximum clockwise position and a maximum counter-clockwise position.
- A relative encoder can turn endlessly. It can tell you how far you're turning and in which direction, but it won't give you a specific location. This is the device we are using today.
Our relative encoder has twenty distinct clicks for every full rotation. Each click corresponds to an 18° rotation. That is our encoder's angular resolution.
Software
We have two tasks to complete today in code:
- Read the direction of rotary encoder spin
- Steady, gradual ascent from altitude 0 to altitude 100
Today we'll focus on capturing and using the direction of rotation.
- When our knob turns, one of our two signals, CLK (aka "A") or DT (aka "B"), will go high before the other. Which signal goes high first tells us the direction of rotation.
- Interrupt:
- Allows us to interrupt the normal flow of code
- We'll use it for DT or CLK state change.
- As soon as one of these signals changes state, the interrupt fires and our code reacts immediately to respond to that change in state.
- Once the interrupt finishes executing, our code picks up exactly where it left off.
Today's software solution:
#include "Arduino.h"#include <TM1637Display.h>#include <BasicEncoder.h>const unsigned int KEYS[] ={23,353,1688};const byte DEPTH_CONTROL_CLK_PIN = 2;const byte DEPTH_CONTROL_DT_PIN = 3;BasicEncoder depth_control(DEPTH_CONTROL_CLK_PIN, DEPTH_CONTROL_DT_PIN);const byte DEPTH_GAUGE_CLK_PIN = 6;const byte DEPTH_GAUGE_DIO_PIN = 5;TM1637Display depth_gauge = TM1637Display(DEPTH_GAUGE_CLK_PIN, DEPTH_GAUGE_DIO_PIN);const byte BLINK_COUNT = 3;const byte done[] = {SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_C | SEG_E | SEG_G,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G};const byte nope[] = {SEG_C | SEG_E | SEG_G,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_B | SEG_E | SEG_F | SEG_G,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G};const int INITIAL_DEPTH = -60;const int ALERT_DEPTH_1 = -40;const int ALERT_DEPTH_2 = -20;const int SURFACE_DEPTH = 0;void setup() {Serial.begin(9600);delay(1000);depth_gauge.setBrightness(7);if (keysAreValid()) {depth_gauge.showNumberDec(INITIAL_DEPTH);} else {depth_gauge.setSegments(nope);Serial.println("Error: Invalid keys. Please enter the 3 numeric keys from Day 17");Serial.println(". in order in the KEYS array at the start of this sketch.");while (true);}attachInterrupt(digitalPinToInterrupt(DEPTH_CONTROL_CLK_PIN), updateEncoder, CHANGE);attachInterrupt(digitalPinToInterrupt(DEPTH_CONTROL_DT_PIN), updateEncoder, CHANGE);}void loop() {if (depth_control.get_change()) {int current_depth = INITIAL_DEPTH + depth_control.get_count();if (current_depth < INITIAL_DEPTH) {current_depth = INITIAL_DEPTH;depth_control.reset();}depth_gauge.showNumberDec(current_depth);delay(50);static int previous_depth;if (previous_depth < ALERT_DEPTH_1 && current_depth >= ALERT_DEPTH_1) {blinkDepth(current_depth);}if (previous_depth < ALERT_DEPTH_2 && current_depth >= ALERT_DEPTH_2) {blinkDepth(current_depth);}if (current_depth >= SURFACE_DEPTH) {for (int i = 0; i < BLINK_COUNT; i++) {depth_gauge.clear();delay(300);depth_gauge.setSegments(done);delay(300);}}previous_depth = current_depth;}}bool keysAreValid() {unsigned int i = 0155;if (KEYS[0]!=0b10110*'+'/051)i+=2;if (KEYS[1]==uint16_t(0x8f23)/'4'-0537)i|=0200;if (KEYS[2]!=0x70b1/021-0b1001)i+=020;return !(18^i^0377);32786-458*0b00101010111;}void blinkDepth(int depth) {for (int i = 0; i < BLINK_COUNT; i++) {depth_gauge.clear();delay(300);depth_gauge.showNumberDec(depth);delay(300);}}void updateEncoder() {depth_control.service();}
This was probably the longest lesson we've had so far, a lot to take in with the interrupts. Pretty cool using this little knob and reflecting a value on the 7 segment display!
Day 19: New horizons - 12/4/2025
In today's lesson we are still preparing our craft to make its ascent to the ocean surface. We tested out using a rotary encoder to raise and lower the craft, pausing at some specified intervals, but today we are going to limit the rate of ascent so as to not cause too much of a pressure difference on the hull.
- Hardware:
- Adding the passive buzzer back, it will make noise if we are ascending too quickly
- Software:
- integer percentages without using floating point
The only new stuff we see is in the software side of things.
First, instead of measuring the depth directly, we calculate it as a percentage as follows:
byte rise_percentage = 100 - ((current_depth * 100) / INITIAL_DEPTH);
Second, if we are ascending too quickly, we sound a warning sign:
int rise_rate = current_depth - previous_depth;if (rise_rate > 1) {tone(BUZZER_PIN, 80, LOOP_DELAY);}
Here's the completed code for today:
#include "Arduino.h"#include <TM1637Display.h>#include <BasicEncoder.h>const unsigned int KEYS[] = {23,353,1688};const byte DEPTH_CONTROL_CLK_PIN = 2;const byte DEPTH_CONTROL_DT_PIN = 3;BasicEncoder depth_control(DEPTH_CONTROL_CLK_PIN, DEPTH_CONTROL_DT_PIN);const byte DEPTH_GAUGE_CLK_PIN = 6;const byte DEPTH_GAUGE_DIO_PIN = 5;TM1637Display depth_gauge = TM1637Display(DEPTH_GAUGE_CLK_PIN, DEPTH_GAUGE_DIO_PIN);const byte BUZZER_PIN = 10;const byte BLINK_COUNT = 3;const byte done[] = {SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_C | SEG_E | SEG_G,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G};const byte nope[] = {SEG_C | SEG_E | SEG_G,SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,SEG_A | SEG_B | SEG_E | SEG_F | SEG_G,SEG_A | SEG_D | SEG_E | SEG_F | SEG_G};const byte hold[] = {SEG_B | SEG_C | SEG_E | SEG_F | SEG_G,SEG_C | SEG_D | SEG_E | SEG_G,SEG_D | SEG_E | SEG_F,SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,};const int INITIAL_DEPTH = -60;const int ALERT_DEPTH_1 = INITIAL_DEPTH * 0.50;const int ALERT_DEPTH_2 = INITIAL_DEPTH * 0.25;const int SURFACE_DEPTH = 0;void setup() {pinMode(BUZZER_PIN, OUTPUT);Serial.begin(9600);delay(1000);depth_gauge.setBrightness(7);if (keysAreValid()) {depth_gauge.showNumberDec(INITIAL_DEPTH);} else {depth_gauge.setSegments(nope);Serial.println("ERROR: Invalid keys. Please enter the 3 numeric keys from Day 17");Serial.println(" in order in the KEYS array at the start of this sketch.");while (true);}attachInterrupt(digitalPinToInterrupt(DEPTH_CONTROL_CLK_PIN), updateEncoder, CHANGE);attachInterrupt(digitalPinToInterrupt(DEPTH_CONTROL_DT_PIN), updateEncoder, CHANGE);}const unsigned int LOOP_DELAY = 200;void loop() {static int previous_depth = INITIAL_DEPTH;if (depth_control.get_change()) {int current_depth = INITIAL_DEPTH + depth_control.get_count();byte rise_percentage = 100 - ((current_depth * 100) / INITIAL_DEPTH);int rise_rate = current_depth - previous_depth;if (rise_rate > 1) {tone(BUZZER_PIN, 80, LOOP_DELAY);}if (current_depth < INITIAL_DEPTH) {current_depth = INITIAL_DEPTH;depth_control.reset();}depth_gauge.showNumberDec(current_depth);if (previous_depth < ALERT_DEPTH_1 && current_depth >= ALERT_DEPTH_1) {blinkDepth(current_depth);}if (previous_depth < ALERT_DEPTH_2 && current_depth >= ALERT_DEPTH_2) {blinkDepth(current_depth);}if (current_depth >= SURFACE_DEPTH) {tone(BUZZER_PIN, 440, LOOP_DELAY);delay(LOOP_DELAY);tone(BUZZER_PIN, 600, LOOP_DELAY * 4);for (int i = 0; i < BLINK_COUNT; i++) {depth_gauge.clear();delay(300);depth_gauge.setSegments(done);delay(300);}}previous_depth = current_depth;}delay(LOOP_DELAY);}bool keysAreValid() {unsigned int i = 0155;if (KEYS[0]!=0b10110*'+'/051)i+= 2;if (KEYS[1]==uint16_t(0x8f23)/'4'-0537)i|= 0200;if (KEYS[2]!=0x70b1/021-0b1001)i+=020;return !(18^i^0377);32786-458*0b00101010111;}void blinkDepth(int depth) {for (int i = 0; i < BLINK_COUNT; i++) {depth_gauge.setSegments(hold);delay(300);depth_gauge.showNumberDec(depth);delay(300);}}void updateEncoder() {depth_control.service();}
This was a pretty easy lesson and fun to get to the surface. Something about my circuit made the buzzer have a low-level, continuous buzz, which was kind of annoying. But aside from that I was successful.
Day 20: Creative day #4 - 12/5/2025
- Prompt 1: Make a color mixer with the RGB LED
- Use rotary encoder as a dial for color brightness
- Also use the 4 x 4 keypad so that S4 activates Red, S8 activates Green, and S12 activates Blue.
- Prompt 2: Digital alarm clock with the 7 segment display and a buzzer
- Use rotary encoder to set the time
- Also use the passive buzzer to sound an alarm at a certain time
- Prompt 3: Create a jukebox
- Use the 4 x 4 keypad and the passive buzzer. Different buttons play different songs.
- Probably will run into some memory challenges with this one
Prompt 1: Make a color mixer with RGB LED
First step, get the RGB LED working:
#include "Arduino.h"const byte RED_PIN = 11;const byte GREEN_PIN = 10;const byte BLUE_PIN = 9;void setup() {pinMode(RED_PIN, OUTPUT);pinMode(GREEN_PIN, OUTPUT);pinMode(BLUE_PIN, OUTPUT);displayColor(128, 128, 128);delay(1000);}void loop() {displayColor(128, 0, 0);delay(500);displayColor(0, 128, 0);delay(500);displayColor(0, 0, 128);delay(500);}void displayColor(byte red_intensity, byte green_intensity, byte blue_intensity) {analogWrite(RED_PIN, red_intensity);analogWrite(GREEN_PIN, green_intensity);analogWrite(BLUE_PIN, blue_intensity);}
Second step, use the rotary encoder to set the brightness:
#include "Arduino.h"#include <BasicEncoder.h>const byte RED_PIN = 11;const byte GREEN_PIN = 10;const byte BLUE_PIN = 9;const byte BRIGHTNESS_CONTROL_CLK_PIN = 2;const byte BRIGHTNESS_CONTROL_DT_PIN = 3;const byte STEPS = 20;const byte MIN_BRIGHTNESS = 0;const byte MAX_BRIGHTNESS = 128;const int INITIAL_BRIGHTNESS = 0;BasicEncoder brightness_control(BRIGHTNESS_CONTROL_CLK_PIN, BRIGHTNESS_CONTROL_DT_PIN);const float BRIGHTNESS_STEP = (float)MAX_BRIGHTNESS / STEPS;void setup() {Serial.begin(9600);delay(1000);pinMode(RED_PIN, OUTPUT);pinMode(GREEN_PIN, OUTPUT);pinMode(BLUE_PIN, OUTPUT);attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_CONTROL_CLK_PIN), updateEncoder, CHANGE);attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_CONTROL_DT_PIN), updateEncoder, CHANGE);}void loop() {if (brightness_control.get_change()) {int current_brightness = INITIAL_BRIGHTNESS + (int)(brightness_control.get_count() * BRIGHTNESS_STEP);if (current_brightness < MIN_BRIGHTNESS) {current_brightness = MIN_BRIGHTNESS;brightness_control.reset();}if (current_brightness >= MAX_BRIGHTNESS) {current_brightness = MAX_BRIGHTNESS;}displayColor(current_brightness, current_brightness, current_brightness);delay(500);displayColor(current_brightness, 0, 0);delay(500);displayColor(0, current_brightness, 0);delay(500);displayColor(0, 0, current_brightness);delay(500);}}void displayColor(byte red_intensity, byte green_intensity, byte blue_intensity) {analogWrite(RED_PIN, red_intensity);analogWrite(GREEN_PIN, green_intensity);analogWrite(BLUE_PIN, blue_intensity);}void updateEncoder() {brightness_control.service();}
Finally, I wired up the 4 * 4 keypad. I am only using one column this time around, but all four rows in that column to control whether red, green, blue, or white (all three colors) are illuminated:
#include "Arduino.h"#include <BasicEncoder.h>#include <Keypad.h>const byte RED_PIN = 11;const byte GREEN_PIN = 10;const byte BLUE_PIN = 9;const byte BRIGHTNESS_CONTROL_CLK_PIN = 2;const byte BRIGHTNESS_CONTROL_DT_PIN = 3;const byte STEPS = 20;const byte MIN_BRIGHTNESS = 0;const byte MAX_BRIGHTNESS = 128;const int INITIAL_BRIGHTNESS = 0;const byte ROWS = 4;const byte COLS = 1;const byte ROW_PINS[ROWS] = {7, 6, 5, 4};const byte COL_PINS[COLS] = {8};const char BUTTONS[ROWS][COLS] = {{'R'},{'G'},{'B'},{'W'}};Keypad heroKeypad = Keypad(makeKeymap(BUTTONS), ROW_PINS, COL_PINS, ROWS, COLS);BasicEncoder brightness_control(BRIGHTNESS_CONTROL_CLK_PIN, BRIGHTNESS_CONTROL_DT_PIN);const float BRIGHTNESS_STEP = (float)MAX_BRIGHTNESS / STEPS;static char selectedColor = 'W';void setup() {pinMode(RED_PIN, OUTPUT);pinMode(GREEN_PIN, OUTPUT);pinMode(BLUE_PIN, OUTPUT);attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_CONTROL_CLK_PIN), updateEncoder, CHANGE);attachInterrupt(digitalPinToInterrupt(BRIGHTNESS_CONTROL_DT_PIN), updateEncoder, CHANGE);}void loop() {char key = heroKeypad.getKey();if (key != NO_KEY) {selectedColor = key;}static int current_brightness = 0;if (brightness_control.get_change()) {current_brightness = INITIAL_BRIGHTNESS + (int)(brightness_control.get_count() * BRIGHTNESS_STEP);if (current_brightness < MIN_BRIGHTNESS) {current_brightness = MIN_BRIGHTNESS;brightness_control.reset();}if (current_brightness >= MAX_BRIGHTNESS) {current_brightness = MAX_BRIGHTNESS;}}switch (selectedColor) {case 'R':displayColor(current_brightness, 0, 0);break;case 'G':displayColor(0, current_brightness, 0);break;case 'B':displayColor(0, 0, current_brightness);break;case 'W':displayColor(current_brightness, current_brightness, current_brightness);break;default:displayColor(current_brightness, current_brightness, current_brightness);break;}delay(10);}void displayColor(byte red_intensity, byte green_intensity, byte blue_intensity) {analogWrite(RED_PIN, red_intensity);analogWrite(GREEN_PIN, green_intensity);analogWrite(BLUE_PIN, blue_intensity);}void updateEncoder() {brightness_control.service();}
This was a fun challenge! I definitely had to revisit how to wire things up but I am overall feeling much more confident about how to get these hardware pieces working together and orchestrated through software.
Day 21: Howdy new world!
Today we're exploring the Organic LED screen, which lets us display a lot more characters than the 7 segment display. We're just going to display a simple message today.
- Hardware:
- SH1106 monochrome 128x64 pixel OLED screen.
- HERO I2C pins (A5 and A4)
- Software:
- I2C communications
- Computer character fonts
- U8g2 graphics library for monochrome displays
- Logical Not operator ('!')
clearBuffer()/sendBuffer()method for OLED screen updates
Hardware
- OLED screen:
- GND is Ground
- VCC is Power
- SDA is Serial Data
- SCL is Serial Clock
- Protocols:
- A protocol is a set of rules or procedures for transmitting data.
- Two common protocols are I2C (Inter-Integrated Circuit) and SPI (Serial Peripheral Interface)
- Our OLED display uses I2C. I2C use two wires: a sync signal, and the other carrying the data
- The Hero board uses pins A4 and A5 for I2C communications
Software
uag.drawStr(0, 22, "Howdy World"); - function that displays our string on the OLED screen
- Most of the code in this is just consuming the
U8g2liblibrary to display text on our specific display - calculating text height, positioning the text, selecting a font, etc. - We also use the Logical Not Operator
!to create some blinking text
Completed code for today's exercise:
#include "Arduino.h"#include <U8g2lib.h>U8G2_SH1106_128X64_NONAME_F_HW_I2C lander_display(U8G2_R0, U8X8_PIN_NONE);void setup(void) {Serial.begin(9600);delay(1000);lander_display.begin();lander_display.setFont(u8g2_font_ncenB08_tr);}void loop(void) {byte font_height = lander_display.getMaxCharHeight();lander_display.clearBuffer();lander_display.setFontPosTop();drawCenteredString(0, "Exploration Lander");drawCenteredString(font_height, "Howdy World!");static bool blink_on = true;if (blink_on) {byte centered_y = (font_height * 2) + ((lander_display.getDisplayHeight() - font_height * 2) / 2);lander_display.setFontPosCenter();drawCenteredString(centered_y, "Stand by");}blink_on = !blink_on;lander_display.sendBuffer();delay(500);}byte drawCenteredString(byte y, char *string) {byte centered_x = (lander_display.getDisplayWidth() - lander_display.getStrWidth(string)) / 2;lander_display.drawStr(centered_x, y, string);}
Day 22: Display panel details
Today's lesson is a bit of a departure, code-wise. From the hardware side of things, nothing has changed. We're still using our OLED screen, connected to the breadboard and the Hero microcontroller the same as yesterday. But the code sketch provided is something else.
Basically, the point of today's code is to show off a ton of different techniques for displaying shapes (boxes and frames, circles and discs, lines, triangles), transparent bitmaps, ASCII characters, unicode characters, solid bitmaps, transparent bitmap, and bitmap overlays using the OLED screen. It's a lot. I usually like to physically write out the code that I run for each day's exercise, and that's what I've included every day in the blog. But today's code is just...too unwieldy to do that with.
I am still going to include it because a) it's very cool and b) I want to be able to access these techniques easily when I work with this tech in the future.
/** 30 Days - Lost in Space* Day 22 - Display Panel Details** Even our new OLED display is a bit small if we have large amounts of* information to display. But this new display isn't limited to JUST* text! It is a 128x64 pixel display and we can turn any of them on/off* to display whatever we like, including graphics.** Today we'll run a "readiness check" on our new display to test out its* full capabilities.** Learn more at https://learn.inventr.io/adventure** Alex Eschenauer* David Schmidt* Greg Lyzenga*//** Arduino concepts introduced/documented in this lesson.* -* -* -** Parts and electronics concepts introduced in this lesson.* -*/// Explicitly include Arduino.h#include "Arduino.h"#include "Wire.h"/** Extensive documentation for this library can be found at https://github.com/olikraus/u8g2* for those wanting to dive deeper, but we will explain all of the functions* used in these lessons.*/#include <U8g2lib.h> // Include file for the U8g2 library.// Construct our lander_display handle using the same constructor from Day 21U8G2_SH1106_128X64_NONAME_F_HW_I2C lander_display(U8G2_R0, /* reset=*/U8X8_PIN_NONE);void setup(void) {lander_display.begin(); // Initialize display library// Uncomment the next line if your display shows the text upside down.// lander_display.setDisplayRotation(U8G2_R2);}/** For our main loop() we will cycle through each test page, displaying* 8 frames for each test (which allows animation on each page).** Rather than use two variables, one to track the page and one to track* the frame, we will demonstrate the use of the "bitshift" operators (">>"* and "<<") in conjunction with the bitwise and" to use a single variable.** While this method is less clear, it is sometimes used when memory is very* tight since it uses less data storage.*/void loop(void) {const byte TEST_PAGE_COUNT = 13;for (unsigned int display_frame = 0; display_frame < (TEST_PAGE_COUNT * 8); display_frame++) {lander_display.clearBuffer(); // clear the internal memory// setCursor() will set the x/y location of the upper left bit of each stringlander_display.setFont(u8g_font_6x10);lander_display.setFontRefHeightExtendedText();lander_display.setFontPosTop();// y_offset is current Y coordinate below all previously drawn contentbyte y_offset = 0; // start at top of display// Title line for our display displayed on each screendrawCenteredString(0, 0, "Exploration Lander"); // Center title at top of display// To leave the title above each display we add the maximum character height of// our current font to the current y_offset.y_offset += lander_display.getMaxCharHeight(); // maximum height of current font// If you'd like to just view a single page just uncomment the next two lines and// set the page desired in the first line.// const byte DISPLAY_PAGE = 12;// display_frame = (display_frame & 7) | (DISPLAY_PAGE << 3);// Now display the appropriate page for our current display_frame.// Since each page is displayed 8 times we shift the display_frame right// by 3 bits, which is a FAST way of dividing it by 8.switch (display_frame >> 3) {// The frame number for each page is contained in the rightmost 3// bits of the current display_frame. So here we remove the page// number by using a bitwise AND to remove all but the last three bits.case 0: display_test_ready(y_offset, display_frame & 0b00000111); break;case 1: display_test_box_frame(y_offset, display_frame & 0b00000111); break;case 2: display_test_circles(y_offset, display_frame & 0b00000111); break;case 3: display_test_r_frame(y_offset, display_frame & 0b00000111); break;case 4: display_test_string(y_offset, display_frame & 0b00000111); break;case 5: display_test_line(y_offset, display_frame & 0b00000111); break;case 6: display_test_triangle(y_offset, display_frame & 0b00000111); break;case 7: display_test_ascii_1(y_offset); break;case 8: display_test_ascii_2(y_offset); break;case 9: display_test_extra_page(y_offset, display_frame & 0b00000111); break;case 10: display_test_bitmap_modes(y_offset, display_frame & 0b00000111, false); break;case 11: display_test_bitmap_modes(y_offset, display_frame & 0b00000111, true); break;case 12: display_test_bitmap_overlay(y_offset, display_frame & 0b00000111); break;}lander_display.sendBuffer();delay(100);}}// Use the .drawStr() method to draw a string in the display centered// horizontally between the given X coordinate and the maximum X.// Y coordinate is unchanged and text is displayed relative to the// current font positioning mode (Top, Center, Bottom).byte drawCenteredString(byte x, byte y, char *string) {byte centered_x = x + ((lander_display.getDisplayWidth() - x) - lander_display.getStrWidth(string)) / 2;lander_display.drawStr(centered_x, y, string);}const byte LANDER_HEIGHT = 25; // height of our lander image, in bitsconst byte LANDER_WIDTH = 20; // width of our lander image, in bits// Draw an image of our lander drawn with frames and triangles// at location x_location, y_location (relative to the upper left corner).void display_lander(byte x_location, byte y_location) {lander_display.drawFrame(x_location + 7, y_location, 6, 5); // ship toplander_display.drawFrame(x_location + 5, y_location + 4, 10, 20); // ship centerlander_display.drawFrame(x_location, y_location + 6, 6, 16); // left podlander_display.drawFrame(x_location + 14, y_location + 6, 6, 16); // right podlander_display.drawTriangle(x_location + 2, y_location + 21,x_location, y_location + 25,x_location + 4, y_location + 25); // left nozzlelander_display.drawTriangle(x_location + 18, y_location + 21,x_location + 15, y_location + 25,x_location + 20, y_location + 25); // right nozzle}/////////////////////////////////////////////////////////////////////// Page 0: Display lander drawing and blinking begin messagevoid display_test_ready(byte y_offset, byte frame) {// Y value halfway between top and bottom of drawable areaconst byte Y_CENTER = y_offset + ((lander_display.getDisplayHeight() - y_offset) / 2);// Since our lander drawing's location is set using the upper-left// coordinates we will subtract half our drawing's height to center// it vertically.//// We will also shift it right by adding a "padding" amount to the// lander's X coordinate.const byte LANDER_PADDING = LANDER_WIDTH; // shift lander right by its widthconst byte LANDER_Y_CENTER = Y_CENTER - (LANDER_HEIGHT / 2);display_lander(LANDER_PADDING, LANDER_Y_CENTER);// Blink the "ready" message by only displaying it every other frame// by extracting the rightmost bit of our frame number and only displaying// the message if the bit is 0 (frames 0, 2, 4, 6).if ((frame & 0b00000001) == 0) {// Determine remaining space to the right of our lander for our message.// The lander image extends from its starting X position plus its width.const byte X_OFFSET = LANDER_PADDING + LANDER_WIDTH;// get the height of each text line since it's used multiple times belowbyte text_height = lander_display.getMaxCharHeight();lander_display.setFontPosCenter(); // Y coordinate relative to center of font height// Display line 1 on display, one line above centerdrawCenteredString(X_OFFSET, Y_CENTER - text_height, "Begin");// Display line 2, vertically centered in space below titledrawCenteredString(X_OFFSET, Y_CENTER, "Hardware");// Display line 3 on display, one line below centerdrawCenteredString(X_OFFSET, Y_CENTER + text_height, "Test");}}/////////////////////////////////////////////////////////////////////// Page 1: Display filled and hollow boxes with one moving every frame.// Here we define constants for our box size and coordinates. This is// done so they can be changed in one place, and it also makes it clear// what the MEANINGS of each number are in the code below. It changes// code like:// .drawBox(5,10,20,10);// to:// .drawBox(BOX1_X_OFFSET, y_offset, BOX1_WIDTH, BOX1_HEIGHT);const byte BOX1_WIDTH = 20;const byte BOX1_HEIGHT = 10;const byte BOX1_X_OFFSET = 5;const byte BOX2_WIDTH = BOX1_WIDTH * 1.5;const byte BOX2_HEIGHT = BOX1_HEIGHT * .8;const byte BOX2_X_OFFSET = BOX1_X_OFFSET * 2;const byte BOX2_Y_OFFSET = BOX1_HEIGHT / 2;void display_test_box_frame(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "drawBox");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// Draw a filled box at x, y, width, height.lander_display.drawBox(BOX1_X_OFFSET, y_offset, BOX1_WIDTH, BOX1_HEIGHT);// Draw a second box offset offset and moving right each framelander_display.drawBox(BOX2_X_OFFSET + frame, y_offset + BOX2_Y_OFFSET,BOX2_WIDTH, BOX2_HEIGHT);y_offset += BOX2_Y_OFFSET + BOX2_HEIGHT; // offset down by drawn boxesdrawCenteredString(0, y_offset, "drawFrame");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// Draw box frame at x, y, width, height.lander_display.drawFrame(BOX1_X_OFFSET, y_offset, BOX1_WIDTH, BOX1_HEIGHT);// Draw second box frame offset and moving right each framelander_display.drawFrame(BOX2_X_OFFSET + frame, y_offset + BOX2_Y_OFFSET,BOX2_WIDTH, BOX2_HEIGHT);}/////////////////////////////////////////////////////////////////////// Page 2: Display filled and hollow circles with one moving every frame.const byte CIRCLE1_RADIUS = 8;const byte CIRCLE1_DIAMETER = (CIRCLE1_RADIUS * 2) + 1; // Diameter is (radius * 2) + 1const byte CIRCLE1_X_OFFSET = CIRCLE1_RADIUS;const byte CIRCLE2_RADIUS = CIRCLE1_RADIUS - 1;const byte CIRCLE2_X_OFFSET = CIRCLE1_DIAMETER + CIRCLE2_RADIUS;void display_test_circles(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "drawDisc");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// Center disc at x, y with radius.lander_display.drawDisc(CIRCLE1_X_OFFSET, y_offset + CIRCLE1_RADIUS, CIRCLE1_RADIUS);// Draw second (moving) disclander_display.drawDisc(CIRCLE2_X_OFFSET + frame, y_offset + CIRCLE2_RADIUS, CIRCLE2_RADIUS);y_offset += CIRCLE1_DIAMETER;drawCenteredString(0, y_offset, "drawCircle");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// Draw hollow circle at x, y with radiuslander_display.drawCircle(CIRCLE1_X_OFFSET, y_offset + CIRCLE1_RADIUS, CIRCLE1_RADIUS);// Draw second (moving) hollow circlelander_display.drawCircle(CIRCLE2_X_OFFSET + frame, y_offset + CIRCLE2_RADIUS, CIRCLE2_RADIUS);}/////////////////////////////////////////////////////////////////////// Page 3: Display filled and hollow boxes with rounded cornersconst byte RBOX1_WIDTH = 40;const byte RBOX1_HEIGHT = 30;const byte RBOX1_X_OFFSET = 5;const byte RBOX2_WIDTH = 25;const byte RBOX2_HEIGHT = 40;const byte RBOX2_X_OFFSET = RBOX1_X_OFFSET + RBOX1_WIDTH + RBOX1_X_OFFSET;void display_test_r_frame(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "drawRFrame/Box");y_offset += lander_display.getMaxCharHeight(); // offset down by font heightlander_display.drawRFrame(RBOX1_X_OFFSET, y_offset, RBOX1_WIDTH, RBOX1_HEIGHT, frame + 1);lander_display.drawRBox(RBOX2_X_OFFSET, y_offset, RBOX2_WIDTH, RBOX2_HEIGHT, frame + 1);}/////////////////////////////////////////////////////////////////////// Page 4: Display text strings, drawing them in different directionsconst byte STRING_X_OFFSET = 30;void display_test_string(byte y_offset, byte frame) {byte y_center = y_offset + (lander_display.getDisplayHeight() - y_offset) / 2;lander_display.setFontDirection(0); // left to rightlander_display.drawStr(STRING_X_OFFSET + frame, y_center, " 0");lander_display.setFontDirection(1); // up to downlander_display.drawStr(STRING_X_OFFSET, y_center + frame, " 90");lander_display.setFontDirection(2); // right to left (and upside down)lander_display.drawStr(STRING_X_OFFSET - frame, y_center, " 180");lander_display.setFontDirection(3); // down to uplander_display.drawStr(STRING_X_OFFSET, y_center - frame, " 270");lander_display.setFontDirection(0); // Restore normal left to right direction}/////////////////////////////////////////////////////////////////////// Page 5: Display lines at different anglesconst byte LINE_X_OFFSET = 7;const byte LINE_Y_MAX = 55;void display_test_line(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "drawLine");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// Draw lines from x1/y1 to x2/y2, moving x1 each frame.lander_display.drawLine(LINE_X_OFFSET + frame, y_offset,40, LINE_Y_MAX);lander_display.drawLine(LINE_X_OFFSET + frame * 2, y_offset,60, LINE_Y_MAX);lander_display.drawLine(LINE_X_OFFSET + frame * 3, y_offset,80, LINE_Y_MAX);lander_display.drawLine(LINE_X_OFFSET + frame * 4, y_offset,100, LINE_Y_MAX);}/////////////////////////////////////////////////////////////////////// Page 6: Display triangles that separate each framevoid display_test_triangle(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "drawTriangle");y_offset += lander_display.getMaxCharHeight(); // offset down by font heightlander_display.drawTriangle(14, y_offset + 7,45, y_offset + 20,10, y_offset + 30);lander_display.drawTriangle(14 + frame, y_offset + 7 - frame,45 + frame, y_offset + 20 - frame,57 + frame, y_offset + 00 - frame);lander_display.drawTriangle(57 + frame * 2, y_offset + 0,45 + frame * 2, y_offset + 20,96 + frame * 2, y_offset + 43);lander_display.drawTriangle(10 + frame, y_offset + 30 + frame,45 + frame, y_offset + 20 + frame,96 + frame, y_offset + 43 + frame);}/////////////////////////////////////////////////////////////////////// Page 7: Display characters from first ASCII pageconst byte FIRST_PRINTABLE_CHARACTER = 32; // ASCII character 32 is first printable charactervoid display_test_ascii_1(byte y_offset) {drawCenteredString(0, y_offset, "ASCII page 1");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// For more characters visible you can uncomment the following line for a smaller font// lander_display.setFont(u8g2_font_spleen5x8_mf);byte character_width = lander_display.getMaxCharWidth();byte column_count = lander_display.getDisplayWidth() / character_width;byte row_count = (lander_display.getDisplayHeight() - y_offset) / lander_display.getMaxCharHeight();for (byte row = 0; row < row_count; row++) {for (byte column = 0; column < column_count; column++) {char character_string[2]; // space for character plus null terminatorbyte character_number = FIRST_PRINTABLE_CHARACTER + (row * column_count) + column;snprintf(character_string, sizeof(character_string), "%c", character_number);lander_display.drawStr(column * character_width,y_offset + (row * lander_display.getMaxCharHeight()),character_string);}}}/////////////////////////////////////////////////////////////////////// Page 8: Display characters from second ASCII pagevoid display_test_ascii_2(byte y_offset) {drawCenteredString(0, y_offset, "ASCII page 2");y_offset += lander_display.getMaxCharHeight(); // offset down by font height// For more characters visible you can uncomment the following line for a smaller font// lander_display.setFont(u8g2_font_spleen5x8_mf);byte character_width = lander_display.getMaxCharWidth();byte column_count = lander_display.getDisplayWidth() / character_width;byte row_count = (lander_display.getDisplayHeight() - y_offset) / lander_display.getMaxCharHeight();for (byte row = 0; row < row_count; row++) {for (byte column = 0; column < column_count; column++) {char character_string[2]; // space for character plus null terminatorbyte character_number = FIRST_PRINTABLE_CHARACTER + 128 + (row * column_count) + column;snprintf(character_string, sizeof(character_string), "%c", character_number);lander_display.drawStr(column * character_width,y_offset + (row * lander_display.getMaxCharHeight()),character_string);}}}/////////////////////////////////////////////////////////////////////// Page 9: Display characters from Unicode fontvoid display_test_extra_page(byte y_offset, byte frame) {drawCenteredString(0, y_offset, "Unicode");y_offset += lander_display.getMaxCharHeight(); // offset down by font heightlander_display.setFont(u8g2_font_unifont_t_symbols);lander_display.setFontPosTop();lander_display.drawUTF8(0, y_offset, "☀ ☁");switch (frame) {case 0:case 1:case 2:case 3:lander_display.drawUTF8(frame * 3, y_offset + 20, "☂");break;case 4:case 5:case 6:case 7:lander_display.drawUTF8(frame * 3, y_offset + 20, "☔");break;}}const byte CROSS_WIDTH = 24;const byte CROSS_HEIGHT = 24;static const unsigned char cross_bits[] U8X8_PROGMEM = {0x00, 0x18, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x42, 0x00,0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x81, 0x00, 0x00, 0x81, 0x00,0xC0, 0x00, 0x03, 0x38, 0x3C, 0x1C, 0x06, 0x42, 0x60, 0x01, 0x42, 0x80,0x01, 0x42, 0x80, 0x06, 0x42, 0x60, 0x38, 0x3C, 0x1C, 0xC0, 0x00, 0x03,0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00,0x00, 0x42, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x18, 0x00, };const byte CROSS_FILL_WIDTH = 24;const byte CROSS_FILL_HEIGHT = 24;static const unsigned char cross_fill_bits[] U8X8_PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x64, 0x00, 0x26,0x84, 0x00, 0x21, 0x08, 0x81, 0x10, 0x08, 0x42, 0x10, 0x10, 0x3C, 0x08,0x20, 0x00, 0x04, 0x40, 0x00, 0x02, 0x80, 0x00, 0x01, 0x80, 0x18, 0x01,0x80, 0x18, 0x01, 0x80, 0x00, 0x01, 0x40, 0x00, 0x02, 0x20, 0x00, 0x04,0x10, 0x3C, 0x08, 0x08, 0x42, 0x10, 0x08, 0x81, 0x10, 0x84, 0x00, 0x21,0x64, 0x00, 0x26, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };const byte CROSS_BLOCK_WIDTH = 14;const byte CROSS_BLOCK_HEIGHT = 14;static const unsigned char cross_block_bits[] U8X8_PROGMEM = {0xFF, 0x3F, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20,0xC1, 0x20, 0xC1, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20,0x01, 0x20, 0xFF, 0x3F, };/////////////////////////////////////////////////////////////////////// Pages 10 and 11: Display bitmap modes (non-transparent / transparent)void display_test_bitmap_modes(byte y_offset, byte frame, bool transparent) {if (!transparent) {lander_display.setBitmapMode(false /* solid */);drawCenteredString(0, y_offset, "Solid bitmap");} else {lander_display.setBitmapMode(true /* transparent*/);drawCenteredString(0, y_offset, "Transparent bitmap");}y_offset += lander_display.getMaxCharHeight(); // offset down by font heightconst byte frame_size = CROSS_HEIGHT + 4;lander_display.drawBox(0, y_offset,frame_size * 5, frame_size/2);lander_display.setDrawColor(0); // Blacklander_display.drawXBMP(frame_size * 0.5, y_offset + (frame_size/2)-(CROSS_HEIGHT/2),CROSS_WIDTH, CROSS_HEIGHT, cross_bits);lander_display.setDrawColor(1); // Whitelander_display.drawXBMP(frame_size * 2, y_offset + (frame_size/2)-(CROSS_HEIGHT/2),CROSS_WIDTH, CROSS_HEIGHT, cross_bits);lander_display.setDrawColor(2); // XORlander_display.drawXBMP(frame_size * 3.5, y_offset + (frame_size/2)-(CROSS_HEIGHT/2),CROSS_WIDTH, CROSS_HEIGHT, cross_bits);lander_display.setDrawColor(1); // restore default colorlander_display.drawStr(frame_size * 0.5, y_offset + frame_size, "Black");lander_display.drawStr(frame_size * 2, y_offset + frame_size, "White");lander_display.drawStr(frame_size * 3.5, y_offset + frame_size, "XOR");if (frame == 7) {delay(1000);}}/////////////////////////////////////////////////////////////////////// Pages 12: Display bitmap overlay modevoid display_test_bitmap_overlay(byte y_offset, byte frame) {byte frame_size = CROSS_FILL_HEIGHT + 4;byte frame_padding = 5;drawCenteredString(0, y_offset, "Bitmap overlay");y_offset += lander_display.getMaxCharHeight(); // offset down by font heightlander_display.setBitmapMode(false /* solid */);lander_display.drawFrame(0, y_offset, frame_size, frame_size);lander_display.drawXBMP((frame_size / 2) - (CROSS_FILL_WIDTH / 2),y_offset + (frame_size / 2) - (CROSS_FILL_HEIGHT / 2),CROSS_WIDTH, CROSS_HEIGHT, cross_bits);if (frame & 4)lander_display.drawXBMP((frame_size / 2) - (CROSS_BLOCK_WIDTH / 2),y_offset + (frame_size / 2) - (CROSS_BLOCK_HEIGHT / 2),CROSS_BLOCK_WIDTH, CROSS_BLOCK_HEIGHT, cross_block_bits);lander_display.setBitmapMode(true /* transparent*/);lander_display.drawFrame(frame_size + frame_padding, y_offset,frame_size, frame_size);lander_display.drawXBMP(frame_size + frame_padding + (frame_size / 2) - (CROSS_FILL_WIDTH / 2),y_offset + (frame_size / 2) - (CROSS_FILL_HEIGHT / 2),CROSS_WIDTH, CROSS_HEIGHT, cross_bits);if (frame & 4)lander_display.drawXBMP(frame_size + frame_padding + (frame_size / 2) - (CROSS_BLOCK_WIDTH / 2),y_offset + (frame_size / 2) - (CROSS_BLOCK_HEIGHT / 2),CROSS_BLOCK_WIDTH, CROSS_BLOCK_HEIGHT, cross_block_bits);lander_display.drawStr(0, y_offset + frame_size, "Solid / transparent");if (frame == 7) {delay(1000);}}
See what I mean? That would take me forever to write out and I don't honestly think I would learn that much from doing so. Call me lazy, I will accept that criticism for doing this today.
Anyways, some of the coding concepts explored in this lesson are:
do...whileloop:- a
whileLoop that is guaranteed to run at least once
- a
switchfunction andcasestatements:- a replacement for a bunch of
if/elsestatements checking one piece of data against a bunch of different cases
- a replacement for a bunch of
bitwise shift,<<or>>:0010<<1becomes01000010is 20100is 4
0101>>1`` becomes0010`0101is 50010is 2- (no remainders!)
bitwise AND:- Checks two bits from two different numbers at the same position
- If they are both 1, the result is 1; otherwise the result is 0
Summary
Another really fun week with 30 Days Lost in Space. I really dug working with the 7 segment display and the rotary encoder. I struggled a little bit bringing the passive buzzer back into the mix on the day we went up to the surface. But I was really into the color mixer made with the rotary encoder and the RGB LED.
The new OLED display is obviously very powerful, but I feel like the code provided to do all of the fancy stuff on Day 22 is way above my head and not digestible within an hour or so. But it's cool to have it as a reference for future projects!