commit 4ac13c534e7de4189731c2ba49d2de1708ae5496 Author: Neale Pickett Date: Sun Dec 6 18:36:12 2020 -0700 It does all kinds of shit! diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..106a450 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++14", + "intelliSenseMode": "gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6abc52e --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +FQBN = arduino:avr:micro +UF2_MOUNT = /media/neale/TRELM4BOOT +ARDUINO_DIR = /opt/arduino-1.8.13 + +install: + arduino --upload --board $(FQBN) puzzle_box.ino + +default: build/puzzle_box.ino.uf2 + + +# uf2conv.py is covered by an MIT license. +build/uf2conv.py: + mkdir -p build + curl -L https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py > $@ + chmod +x $@ + +%.uf2: %.bin build/uf2conv.py + build/uf2conv.py -b 0x4000 -c -o $@ $< + +build/%.bin: % *.cpp *.h + arduino-builder \ + -build-cache ~/.cache/arduino \ + -build-path build \ + -core-api-version 10813 \ + -fqbn $(FQBN) \ + -hardware ~/.arduino15/packages \ + -tools $(ARDUINO_DIR)/tools-builder \ + -tools ~/.arduino15/packages \ + -hardware $(ARDUINO_DIR)/hardware \ + -hardware ~/.arduino15/packages \ + -built-in-libraries $(ARDUINO_DIR)/libraries \ + -libraries ~/Arduino/libraries \ + -compile \ + $< + diff --git a/build/build.options.json b/build/build.options.json new file mode 100644 index 0000000..0994f87 --- /dev/null +++ b/build/build.options.json @@ -0,0 +1,12 @@ +{ + "additionalFiles": "..,..,..,..,..,..,..", + "builtInLibrariesFolders": "/opt/arduino-1.8.13/libraries", + "builtInToolsFolders": "/opt/arduino-1.8.13/tools-builder,/home/neale/.arduino15/packages", + "compiler.optimization_flags": "", + "customBuildProperties": "", + "fqbn": "arduino:avr:micro", + "hardwareFolders": "/home/neale/.arduino15/packages,/opt/arduino-1.8.13/hardware,/home/neale/.arduino15/packages", + "otherLibrariesFolders": "/home/neale/Arduino/libraries", + "runtime.ide.version": "10813", + "sketchLocation": "/home/neale/Arduino/puzzle_box/puzzle_box.ino" +} \ No newline at end of file diff --git a/build/sketch/morse.cpp b/build/sketch/morse.cpp new file mode 100644 index 0000000..cb472ab --- /dev/null +++ b/build/sketch/morse.cpp @@ -0,0 +1,120 @@ +#include "morse.h" + +// Each morse dit/dah is stored as a nybble + +#define dit 1 +#define dah 3 + +#define Pack(a, b, c, d) ((a << 0) | (b << 2) | (c << 4) | (d << 6)) +#define Unpack(m, pos) ((m >> (pos * 2)) & 0b11) + +const uint8_t MorseLetters[] = { + Pack(dit, dah, 0, 0), // A + Pack(dah, dit, dit, dit), // B + Pack(dah, dit, dah, dit), // C + Pack(dah, dit, dit, 0), // D + Pack(dit, 0, 0, 0), // E + Pack(dit, dit, dah, dit), // F + Pack(dah, dah, dit, 0), // G + Pack(dit, dit, dit, dit), // H + Pack(dit, dit, 0, 0), // I + Pack(dit, dah, dah, dah), // J + Pack(dah, dit, dah, 0), // K + Pack(dit, dah, dit, dit), // L + Pack(dah, dah, 0, 0), // M + Pack(dah, dit, 0, 0), // N + Pack(dah, dah, dah, 0), // O + Pack(dit, dah, dah, dit), // P + Pack(dah, dah, dit, dah), // Q + Pack(dit, dah, dit, 0), // R + Pack(dit, dit, dit, 0), // S + Pack(dah, 0, 0, 0), // T + Pack(dit, dit, dah, 0), // U + Pack(dit, dit, dit, dah), // V + Pack(dit, dah, dah, 0), // W + Pack(dah, dit, dit, dah), // X + Pack(dah, dit, dah, dah), // Y + Pack(dah, dah, dit, dit), // Z +}; + +MorseEncoder::MorseEncoder() { + SetText(""); +} +MorseEncoder::MorseEncoder(const char *s) { + SetText(s); +} + +void MorseEncoder::SetText(const char *s) { + p = s; + crumb = 0; + ticksLeft = 0; + Transmitting = false; +} + +void MorseEncoder::Quiet(int ticks) { + Transmitting = false; + ticksLeft = ticks; +} + +bool MorseEncoder::Tick() { + --ticksLeft; + if (ticksLeft > 0) { + return true; + } + + // We're out of ticks + + if (!p || !*p) { + return false; + } + + // If we were just transmitting, we have to stop for at least one dit + if (Transmitting) { + Transmitting = false; + ticksLeft = MORSE_DIT; + return true; + } + + // If that was the end of the letter, we have to pause more + if (crumb == 4) { + crumb = 0; + ++p; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + return true; + } + + switch (*p) { + case '\0': + return false; + case 'a' ... 'z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'a'], crumb++); + break; + case 'A' ... 'Z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'A'], crumb++); + break; + case ' ': + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_WORD - MORSE_DIT; + break; + default: // this should never happen! Transmit for a word pause to indicate weirdness. + crumb = 0; + ++p; + Transmitting = true; + ticksLeft = MORSE_PAUSE_WORD; + break; + } + if (0 == ticksLeft) { + // Unpack can return 0 if there are fewer than 4 emissions for a letter. + // In that case, we 're done with the letter. + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + } + + return true; +} diff --git a/build/sketch/morse.h b/build/sketch/morse.h new file mode 100644 index 0000000..4b1d901 --- /dev/null +++ b/build/sketch/morse.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#define MORSE_DIT 1 +#define MORSE_DAH 3 +#define MORSE_PAUSE_LETTER 3 +#define MORSE_PAUSE_WORD 6 + +class MorseEncoder { + public: + MorseEncoder(); + MorseEncoder(const char *s); + + /** SetText resets state with new text. + */ + void SetText(const char *s); + + /** Tick tells the encoder that a dit has elapsed. + * + * Returns true if there's data left to transmit. + * If it returns false, you need to feed it more data. + * + * You should call this every time your dit duration ends. + */ + bool Tick(); + + /** Quiet stops transmitting for this many ticks. + */ + void Quiet(int ticks); + + /** Transmitting is true if you should be transmitting right now. + */ + bool Transmitting; + + private: + const char *p; + uint8_t crumb; + int ticksLeft; +}; diff --git a/build/sketch/music.cpp b/build/sketch/music.cpp new file mode 100644 index 0000000..5d27690 --- /dev/null +++ b/build/sketch/music.cpp @@ -0,0 +1,210 @@ +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// The following functions are related to Beegees Easter Egg only + +const unsigned int freqs[] = { + 3520, // A + 3729, // A# + 3951, // B + 2093, // C + 2217, // C# + 2349, // D + 2489, // D# + 2637, // E + 2794, // F + 2960, // F# + 3136, // G + 3322, // G# +}; + +const int scale[] = { + 0, 2, 3, 5, 7, 8, 10 +}; + +Player::Player() { + +} + + +Player::PlayNote(int note, int octave, int duration) +void +playNote(int octave, int note, int duration) +{ + unsigned int freq; + int i; + + if (note >= 0) { + freq = freqs[note]; + for (i = octave; i < 7; i += 1) { + freq /= 2; + } + + tone(BUZZER2, freq, duration); + setLEDs(note + 1); + } + delay(duration); + setLEDs(0); +} + +void +play(int bpm, char *tune) +{ + unsigned int baseDuration = 60000 / bpm; + int duration = baseDuration; + int baseOctave = 4; + int octave = baseOctave; + + int note = -2; + + char *p = tune; + + for (; *p; p += 1) { + boolean playNow = false; + + switch (*p) { + case '>': + octave = baseOctave + 1; + break; + case '<': + octave = baseOctave - 1; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + playNow = true; + note = scale[*p - 'A']; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + playNow = true; + octave += 1; + note = scale[*p - 'a']; + break; + case '_': + playNow = true; + note = -1; + break; + } + + // Check for sharps or flats + switch (*(p+1)) { + case '#': + case '+': + note += 1; + if (note == 12) { + octave += 1; + note = 0; + } + p += 1; + break; + case '-': + note -= 1; + if (note == -1) { + octave -= 1; + note = 11; + } + p += 1; + break; + } + + // Check for octave + switch (*(p+1)) { + case ',': + octave -= 1; + p += 1; + break; + case '\'': + octave += 1; + p += 1; + break; + } + + // Check for duration + switch (*(p+1)) { + case '2': + duration *= 2; + p += 1; + break; + case '4': + duration *= 4; + p += 1; + break; + case '.': + duration += duration / 2; + p += 1; + break; + } + + if (playNow) { + playNote(octave, note, duration); + note = -1; + octave = baseOctave; + duration = baseDuration; + } + + if (checkButton()) { + noTone(BUZZER2); + return; + } + } + if (note >= 0) { + playNote(octave, note, duration); + } + noTone(BUZZER2); +} + +// Do nothing but play bad beegees music +// This function is activated when user holds bottom right button during power up +void play_beegees() +{ + while(checkButton() == CHOICE_NONE) { + play(104 * 4, "E-F_a-__E-2__C_B-,CE-_B-,C_E-__B-,_C_E-_F_a-_"); + } +} + +void lplay(unsigned int tempo, char *tune, int count) +{ + int i; + + for (i = 0; i < count; i += 1) { + play(tempo * 3, tune); + } +} + + +void play_march() +{ + const int tempo = 80; + + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "E-G-,B-,", 5); + play(tempo * 4, "B-"); + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "E-G-,B-,", 5); + play(tempo * 4, "B-"); + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "GB-,D", 6); + + lplay(tempo * 4, "dGB-", 6); + lplay(tempo * 4, "dGB-", 6); + lplay(tempo * 4, "dGB-", 6); + lplay(tempo * 4, "e-G-B-", 5); + play(tempo * 4, "B-"); + lplay(tempo * 4, "G-B-,F", 6); + lplay(tempo * 4, "E-G-,B-,", 5); + play(tempo * 4, "B-"); + lplay(tempo * 4, "GB-,D", 6); + lplay(tempo * 4, "GB-,D", 6); + + return; +} diff --git a/build/sketch/pitches.h b/build/sketch/pitches.h new file mode 100644 index 0000000..db54c6a --- /dev/null +++ b/build/sketch/pitches.h @@ -0,0 +1,91 @@ +#pragma once + +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 \ No newline at end of file diff --git a/build/sketch/pulse.cpp b/build/sketch/pulse.cpp new file mode 100644 index 0000000..72df45d --- /dev/null +++ b/build/sketch/pulse.cpp @@ -0,0 +1,19 @@ +#include "pulse.h" + +#include + +Pulse::Pulse(unsigned long period) { + this->period = period; + this->nextEventMillis = 0; +} + +bool Pulse::Tick() { + unsigned long now = millis(); + + if (now >= nextEventMillis) { + nextEventMillis = now + period; + return true; + } + + return false; +} diff --git a/build/sketch/pulse.h b/build/sketch/pulse.h new file mode 100644 index 0000000..765a1df --- /dev/null +++ b/build/sketch/pulse.h @@ -0,0 +1,12 @@ +#pragma once + +class Pulse { +public: + Pulse(unsigned long period); + + /** Tick tells you if a period has elapsed. */ + bool Tick(); + + unsigned long period; + unsigned long nextEventMillis; +}; diff --git a/build/sketch/puzzle_box.ino.cpp b/build/sketch/puzzle_box.ino.cpp new file mode 100644 index 0000000..55450cf --- /dev/null +++ b/build/sketch/puzzle_box.ino.cpp @@ -0,0 +1,127 @@ +#include +#line 1 "/home/neale/Arduino/puzzle_box/puzzle_box.ino" +#include +#include +#include +#include +#include +#include "morse.h" +#include "pulse.h" +#include "riddler.h" + +// WS2812 LEDs +#define LEDS_PIN 1 +#define NUM_LEDS 2 +CRGB leds[NUM_LEDS]; + +// Laser +#define LASER_PIN 0 + +// Photoresistor +#define PHOTO_PIN A0 + +// Piezo buzzer +#define BUZZER_PIN 11 + +// Display pin connections. LED needs to be PWM capable. +#define DISPLAY_WIDTH 84 +#define DISPLAY_HEIGHT 48 +#define DISPLAY_SCE 4 +#define DISPLAY_RST 7 +#define DISPLAY_DC 8 +#define DISPLAY_LED 9 +Adafruit_PCD8544 display = Adafruit_PCD8544(DISPLAY_DC, DISPLAY_SCE, DISPLAY_RST); + +// Morse code stuff +#define DIT_DURATION 100 + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + + // Turn on backlight + pinMode(DISPLAY_LED, OUTPUT); + analogWrite(9, 64); + + // Turn on display + display.begin(); + display.setContrast(30); + display.clearDisplay(); + display.drawBitmap( + (DISPLAY_WIDTH - riddler_width)/2, + (DISPLAY_HEIGHT - riddler_height)/2, + riddler_bits, + riddler_width, + riddler_height, + 0xffff + ); + display.display(); + + pinMode(LASER_PIN, OUTPUT); + pinMode(PHOTO_PIN, INPUT); + pinMode(BUZZER_PIN, OUTPUT); + + tone(BUZZER_PIN, 110); + +} + +CHSV loop_morse(Adafruit_PCD8544 *display) { + static Pulse pulse = Pulse(DIT_DURATION); + static MorseEncoder enc = MorseEncoder("CQ CQ KD7OQI"); + static uint16_t errors = 0; + bool solved = false; + int recv = analogRead(PHOTO_PIN); + bool error = ((recv >= 512) != enc.Transmitting); + CHSV color; + + if (solved) { + color = CHSV(128, 40, 64); + } else if (error) { + ++errors; + color = CHSV(0, 255, recv>>2); + } else { + color = CHSV(128, 255, recv>>2); + } + + if (! pulse.Tick()) { + return color; + } + + if (display) { + display->clearDisplay(); + display->setFont(); + display->setCursor(0, 0); + display->print("The Morse One"); + display->display(); + } + + if (enc.Tick()) { + digitalWrite(LASER_PIN, enc.Transmitting); + } else { + // We've sent the whole thing + if (errors < 500) { + solved = true; + } + enc.SetText("HO HO HO ARK"); + enc.Quiet(30); + errors = 0; + } +} + +void messageHappy() { + display.clearDisplay(); + display.setFont(&FreeSerif9pt7b); + display.setCursor(0, 12); + display.print("Happy"); + display.setCursor(0, 29); + display.print("Holiday,"); + display.setCursor(0, 46); + display.print("Martin!"); + display.display(); +} + +void loop() { + leds[0] = loop_morse(NULL); + + FastLED.show(); +} + diff --git a/build/sketch/riddler.h b/build/sketch/riddler.h new file mode 100644 index 0000000..5789e33 --- /dev/null +++ b/build/sketch/riddler.h @@ -0,0 +1,11 @@ +#define riddler_width 24 +#define riddler_height 34 +static const unsigned char PROGMEM riddler_bits[] = { + 0x01, 0xff, 0x80, 0x0f, 0xff, 0xf0, 0x3f, 0xff, 0xfc, 0x7f, 0x01, 0xfe, 0xfe, 0x00, 0xff, 0xfe, + 0x00, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xe1, 0xff, 0x3f, 0xc3, 0xfe, 0x1f, 0x87, + 0xf8, 0x00, 0x0f, 0xf0, 0x00, 0x1f, 0xe0, 0x00, 0x3f, 0xc0, 0x00, 0x7f, 0x00, 0x00, 0xfc, 0x00, + 0x01, 0xf8, 0x00, 0x03, 0xf0, 0x00, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0x07, 0xf8, 0x00, 0x07, + 0xfc, 0x00, 0x07, 0xfc, 0x00, 0x03, 0xfc, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x00, 0xf8, 0x00, 0x01, 0xfc, 0x00, 0x01, 0xfc, 0x00, 0x01, 0xfc, 0x00, + 0x01, 0xfc, 0x00, 0x00, 0xf8, 0x00 +}; diff --git a/build/uf2conv.py b/build/uf2conv.py new file mode 100755 index 0000000..b32c330 --- /dev/null +++ b/build/uf2conv.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +families = { + 'SAMD21': 0x68ed2b88, + 'SAML21': 0x1851780a, + 'SAMD51': 0x55114460, + 'NRF52': 0x1b57745f, + 'STM32F0': 0x647824b6, + 'STM32F1': 0x5ee21072, + 'STM32F2': 0x5d1a0a2e, + 'STM32F3': 0x6b846188, + 'STM32F4': 0x57755a57, + 'STM32F7': 0x53b80f00, + 'STM32G0': 0x300f5633, + 'STM32G4': 0x4c71240a, + 'STM32H7': 0x6db66082, + 'STM32L0': 0x202e3a91, + 'STM32L1': 0x1e1f432d, + 'STM32L4': 0x00ff6919, + 'STM32L5': 0x04240bdf, + 'STM32WB': 0x70d16653, + 'STM32WL': 0x21460ff0, + 'ATMEGA32': 0x16573617, + 'MIMXRT10XX': 0x4FB2D5BD, + 'GD32F350': 0x31D228C6 +} + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + ptr + newaddr = hd[3] + if curraddr == None: + appstartaddr = newaddr + curraddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + ptr + if padding > 10*1024*1024: + assert False, "More than 10M of padding needed at " + ptr + if padding % 4 != 0: + assert False, "Non-word padding size at " + ptr + while padding > 0: + padding -= 4 + outp += b"\x00\x00\x00\x00" + outp.append(block[32 : 32 + datalen]) + curraddr = newaddr + datalen + return b"".join(outp) + +def convert_to_carray(file_content): + outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr:ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": + drives.append(words[0]) + else: + rootpath = "/media" + if sys.platform == "darwin": + rootpath = "/Volumes" + elif sys.platform == "linux": + tmp = rootpath + "/" + os.environ["USER"] + if os.path.isdir(tmp): + rootpath = tmp + for d in os.listdir(rootpath): + drives.append(os.path.join(rootpath, d)) + + + def has_info(d): + try: + return os.path.isfile(d + INFO_FILE) + except: + return False + + return list(filter(has_info, drives)) + + +def board_id(path): + with open(path + INFO_FILE, mode='r') as file: + file_content = file.read() + return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) + + +def list_drives(): + for d in get_drives(): + print(d, board_id(d)) + + +def write_file(name, buf): + with open(name, "wb") as f: + f.write(buf) + print("Wrote %d bytes to %s" % (len(buf), name)) + + +def main(): + global appstartaddr, familyid + def error(msg): + print(msg) + sys.exit(1) + parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') + parser.add_argument('input', metavar='INPUT', type=str, nargs='?', + help='input file (HEX, BIN or UF2)') + parser.add_argument('-b' , '--base', dest='base', type=str, + default="0x2000", + help='set base address of application for BIN format (default: 0x2000)') + parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') + parser.add_argument('-d' , '--device', dest="device_path", + help='select a device path to flash') + parser.add_argument('-l' , '--list', action='store_true', + help='list connected devices') + parser.add_argument('-c' , '--convert', action='store_true', + help='do not flash, just convert') + parser.add_argument('-D' , '--deploy', action='store_true', + help='just flash, do not convert') + parser.add_argument('-f' , '--family', dest='family', type=str, + default="0x0", + help='specify familyID - number or name (default: 0x0)') + parser.add_argument('-C' , '--carray', action='store_true', + help='convert binary file to a C array, not UF2') + args = parser.parse_args() + appstartaddr = int(args.base, 0) + + if args.family.upper() in families: + familyid = families[args.family.upper()] + else: + try: + familyid = int(args.family, 0) + except ValueError: + error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + + if args.list: + list_drives() + else: + if not args.input: + error("Need input file") + with open(args.input, mode='rb') as f: + inpbuf = f.read() + from_uf2 = is_uf2(inpbuf) + ext = "uf2" + if args.deploy: + outbuf = inpbuf + elif from_uf2: + outbuf = convert_from_uf2(inpbuf) + ext = "bin" + elif is_hex(inpbuf): + outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) + elif args.carray: + outbuf = convert_to_carray(inpbuf) + ext = "h" + else: + outbuf = convert_to_uf2(inpbuf) + print("Converting to %s, output size: %d, start address: 0x%x" % + (ext, len(outbuf), appstartaddr)) + if args.convert or ext != "uf2": + drives = [] + if args.output == None: + args.output = "flash." + ext + else: + drives = get_drives() + + if args.output: + write_file(args.output, outbuf) + else: + if len(drives) == 0: + error("No drive to deploy.") + for d in drives: + print("Flashing %s (%s)" % (d, board_id(d))) + write_file(d + "/NEW.UF2", outbuf) + + +if __name__ == "__main__": + main() diff --git a/morse.cpp b/morse.cpp new file mode 100644 index 0000000..cb472ab --- /dev/null +++ b/morse.cpp @@ -0,0 +1,120 @@ +#include "morse.h" + +// Each morse dit/dah is stored as a nybble + +#define dit 1 +#define dah 3 + +#define Pack(a, b, c, d) ((a << 0) | (b << 2) | (c << 4) | (d << 6)) +#define Unpack(m, pos) ((m >> (pos * 2)) & 0b11) + +const uint8_t MorseLetters[] = { + Pack(dit, dah, 0, 0), // A + Pack(dah, dit, dit, dit), // B + Pack(dah, dit, dah, dit), // C + Pack(dah, dit, dit, 0), // D + Pack(dit, 0, 0, 0), // E + Pack(dit, dit, dah, dit), // F + Pack(dah, dah, dit, 0), // G + Pack(dit, dit, dit, dit), // H + Pack(dit, dit, 0, 0), // I + Pack(dit, dah, dah, dah), // J + Pack(dah, dit, dah, 0), // K + Pack(dit, dah, dit, dit), // L + Pack(dah, dah, 0, 0), // M + Pack(dah, dit, 0, 0), // N + Pack(dah, dah, dah, 0), // O + Pack(dit, dah, dah, dit), // P + Pack(dah, dah, dit, dah), // Q + Pack(dit, dah, dit, 0), // R + Pack(dit, dit, dit, 0), // S + Pack(dah, 0, 0, 0), // T + Pack(dit, dit, dah, 0), // U + Pack(dit, dit, dit, dah), // V + Pack(dit, dah, dah, 0), // W + Pack(dah, dit, dit, dah), // X + Pack(dah, dit, dah, dah), // Y + Pack(dah, dah, dit, dit), // Z +}; + +MorseEncoder::MorseEncoder() { + SetText(""); +} +MorseEncoder::MorseEncoder(const char *s) { + SetText(s); +} + +void MorseEncoder::SetText(const char *s) { + p = s; + crumb = 0; + ticksLeft = 0; + Transmitting = false; +} + +void MorseEncoder::Quiet(int ticks) { + Transmitting = false; + ticksLeft = ticks; +} + +bool MorseEncoder::Tick() { + --ticksLeft; + if (ticksLeft > 0) { + return true; + } + + // We're out of ticks + + if (!p || !*p) { + return false; + } + + // If we were just transmitting, we have to stop for at least one dit + if (Transmitting) { + Transmitting = false; + ticksLeft = MORSE_DIT; + return true; + } + + // If that was the end of the letter, we have to pause more + if (crumb == 4) { + crumb = 0; + ++p; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + return true; + } + + switch (*p) { + case '\0': + return false; + case 'a' ... 'z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'a'], crumb++); + break; + case 'A' ... 'Z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'A'], crumb++); + break; + case ' ': + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_WORD - MORSE_DIT; + break; + default: // this should never happen! Transmit for a word pause to indicate weirdness. + crumb = 0; + ++p; + Transmitting = true; + ticksLeft = MORSE_PAUSE_WORD; + break; + } + if (0 == ticksLeft) { + // Unpack can return 0 if there are fewer than 4 emissions for a letter. + // In that case, we 're done with the letter. + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + } + + return true; +} diff --git a/morse.h b/morse.h new file mode 100644 index 0000000..4b1d901 --- /dev/null +++ b/morse.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#define MORSE_DIT 1 +#define MORSE_DAH 3 +#define MORSE_PAUSE_LETTER 3 +#define MORSE_PAUSE_WORD 6 + +class MorseEncoder { + public: + MorseEncoder(); + MorseEncoder(const char *s); + + /** SetText resets state with new text. + */ + void SetText(const char *s); + + /** Tick tells the encoder that a dit has elapsed. + * + * Returns true if there's data left to transmit. + * If it returns false, you need to feed it more data. + * + * You should call this every time your dit duration ends. + */ + bool Tick(); + + /** Quiet stops transmitting for this many ticks. + */ + void Quiet(int ticks); + + /** Transmitting is true if you should be transmitting right now. + */ + bool Transmitting; + + private: + const char *p; + uint8_t crumb; + int ticksLeft; +}; diff --git a/musicplayer.cpp b/musicplayer.cpp new file mode 100644 index 0000000..fa22e88 --- /dev/null +++ b/musicplayer.cpp @@ -0,0 +1,165 @@ +#include "musicplayer.h" +#include + +#define MILLISECOND 1L +#define SECOND (1000 * MILLISECOND) +#define MINUTE (60 * SECOND) +#define REST -1 +#define OCTAVE 7 + +// Frequencies in octave 7 +const uint16_t freqs[] = { + 3520, // A + 3729, // A# + 3951, // B + 2093, // C + 2217, // C# + 2349, // D + 2489, // D# + 2637, // E + 2794, // F + 2960, // F# + 3136, // G + 3322, // G# +}; + +const int scale[] = { + 0, + 2, + 3, + 5, + 7, + 8, + 10, +}; + +MusicPlayer::MusicPlayer(uint8_t pin) { + this->pin = pin; + this->tune = NULL; + pinMode(pin, OUTPUT); +} + +void MusicPlayer::Tone(int note) { + if (REST == note) { + noTone(pin); + } else { + uint16_t freq = freqs[note % 12]; + + for (int o = OCTAVE; note < o * 12; --o) { + freq /= 2; + } + tone(pin, freq); + } +} + +void MusicPlayer::NoTone() { + Tone(REST); +} + +bool MusicPlayer::KeepPlaying() { + unsigned long now = millis(); + uint16_t duration = baseDuration; + int octave = 4; + int note = REST; + + if (!tune) { + return false; + } + if (now < nextNoteMillis) { + // Turn it off for a second in case the note is repeating. + if (now + 20 > nextNoteMillis) { + NoTone(); + } + return true; + } + + // Ignore spaces + while (*tune == ' ') { + ++tune; + } + + for (;;) { + switch (*tune) { + case '>': + ++octave; + ++tune; + break; + case '<': + --octave; + ++tune; + break; + default: + goto OCTAVE_DONE; + } + } +OCTAVE_DONE: + + switch (*tune) { + case '\0': + tune = NULL; + NoTone(); + return false; + case 'a' ... 'g': + ++octave; + note = scale[*tune - 'a'] + (12 * octave); + break; + case 'A' ... 'G': + note = scale[*tune - 'A'] + (12 * octave); + break; + case '_': + case 'P': + default: + note = REST; + break; + } + ++tune; + + // Check for sharps or flats + switch (*tune) { + case '#': + case '+': + ++note; + ++tune; + break; + case '-': + --note; + ++tune; + break; + } + + // Check for duration + for (;;) { + switch (*tune) { + case '/': + duration /= 2; + ++tune; + break; + case '2': + duration *= 2; + ++tune; + break; + case '4': + duration *= 4; + ++tune; + break; + case '.': + duration += duration / 2; + ++tune; + break; + default: + goto DURATION_DONE; + } + } +DURATION_DONE: + + Tone(note); + nextNoteMillis = now + duration; + + return true; +} + +void MusicPlayer::Play(int bpm, const char *tune) { + this->tune = tune; + this->baseDuration = MINUTE / bpm; + this->nextNoteMillis = millis(); +} diff --git a/musicplayer.h b/musicplayer.h new file mode 100644 index 0000000..f812ca8 --- /dev/null +++ b/musicplayer.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +class MusicPlayer { + public: + MusicPlayer(uint8_t pin); + + /** Play begins playing a tune */ + void Play(int bpm, const char *tune); + + /** KeepPlaying should be called once per loop. + * It returns true if a tune is currently being played. + */ + bool KeepPlaying(); + + /** Tone plays a certain tone */ + void Tone(int note); + + /** NoTone stops playing anything */ + void NoTone(); + +private: + uint8_t pin; + char *tune; + unsigned long nextNoteMillis; + uint16_t baseDuration; +}; + +#define TUNE_STAYIN_ALIVE "E- F P a- P2 E-2 P2 C P + +Pulse::Pulse(unsigned long period) { + this->period = period; + this->nextEventMillis = 0; +} + +bool Pulse::Tick() { + unsigned long now = millis(); + + if (now >= nextEventMillis) { + nextEventMillis = now + period; + return true; + } + + return false; +} diff --git a/pulse.h b/pulse.h new file mode 100644 index 0000000..765a1df --- /dev/null +++ b/pulse.h @@ -0,0 +1,12 @@ +#pragma once + +class Pulse { +public: + Pulse(unsigned long period); + + /** Tick tells you if a period has elapsed. */ + bool Tick(); + + unsigned long period; + unsigned long nextEventMillis; +}; diff --git a/puzzle_box.ino b/puzzle_box.ino new file mode 100644 index 0000000..5a38f76 --- /dev/null +++ b/puzzle_box.ino @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include "morse.h" +#include "pulse.h" +#include "riddler.h" +#include "musicplayer.h" + +// WS2812 LEDs +#define LEDS_PIN 1 +#define NUM_LEDS 2 +CRGB leds[NUM_LEDS]; + +// Laser +#define LASER_PIN 0 + +// Photoresistor +#define PHOTO_PIN A0 + +// Piezo buzzer +#define BUZZER_PIN 11 +MusicPlayer mp = MusicPlayer(BUZZER_PIN); + +// Display pin connections. LED needs to be PWM capable. +#define DISPLAY_WIDTH 84 +#define DISPLAY_HEIGHT 48 +#define DISPLAY_SCE 4 +#define DISPLAY_RST 7 +#define DISPLAY_DC 8 +#define DISPLAY_LED 9 +Adafruit_PCD8544 display = Adafruit_PCD8544(DISPLAY_DC, DISPLAY_SCE, DISPLAY_RST); + +// Morse code stuff +#define DIT_DURATION 100 + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + + // Turn on backlight + pinMode(DISPLAY_LED, OUTPUT); + analogWrite(9, 64); + + // Turn on display + display.begin(); + display.setContrast(50); + display.clearDisplay(); + display.drawBitmap( + (DISPLAY_WIDTH - riddler_width)/2, + (DISPLAY_HEIGHT - riddler_height)/2, + riddler_bits, + riddler_width, + riddler_height, + 0xffff + ); + display.display(); + + pinMode(LASER_PIN, OUTPUT); + pinMode(PHOTO_PIN, INPUT); + + mp.Play(120*4, TUNE_BOO); +} + +CHSV loop_morse(bool fg) { + static Pulse pulse = Pulse(DIT_DURATION); + static MorseEncoder enc = MorseEncoder("CQ CQ KD7OQI"); + static uint16_t errors = 0; + bool solved = false; + int recv = analogRead(PHOTO_PIN); + bool error = ((recv >= 512) != enc.Transmitting); + CHSV color; + + if (solved) { + color = CHSV(128, 40, 64); + } else if (error) { + ++errors; + color = CHSV(0, 255, recv>>2); + } else { + color = CHSV(128, 255, recv>>2); + } + + if (! pulse.Tick()) { + return color; + } + + if (fg) { + display.clearDisplay(); + display.setFont(); + display.setCursor(0, 0); + display.print("The Morse One"); + display.display(); + } + + if (enc.Tick()) { + digitalWrite(LASER_PIN, enc.Transmitting); + } else { + // We've sent the whole thing + if (errors < 500) { + solved = true; + } + enc.SetText("HO HO HO ARK"); + enc.Quiet(30); + errors = 0; + } +} + +void beHappy() { + if (!mp.KeepPlaying()) { + mp.Play(80*4, TUNE_JINGLEBELLS); + + display.clearDisplay(); + display.setFont(&FreeSerif9pt7b); + display.setCursor(0, 12); + display.print("Happy"); + display.setCursor(0, 29); + display.print("Holiday,"); + display.setCursor(0, 46); + display.print("Martin!"); + display.display(); + } +} + +void loop() { + leds[0] = loop_morse(true); + + beHappy(); + + FastLED.show(); +} diff --git a/riddler.h b/riddler.h new file mode 100644 index 0000000..5789e33 --- /dev/null +++ b/riddler.h @@ -0,0 +1,11 @@ +#define riddler_width 24 +#define riddler_height 34 +static const unsigned char PROGMEM riddler_bits[] = { + 0x01, 0xff, 0x80, 0x0f, 0xff, 0xf0, 0x3f, 0xff, 0xfc, 0x7f, 0x01, 0xfe, 0xfe, 0x00, 0xff, 0xfe, + 0x00, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xe1, 0xff, 0x3f, 0xc3, 0xfe, 0x1f, 0x87, + 0xf8, 0x00, 0x0f, 0xf0, 0x00, 0x1f, 0xe0, 0x00, 0x3f, 0xc0, 0x00, 0x7f, 0x00, 0x00, 0xfc, 0x00, + 0x01, 0xf8, 0x00, 0x03, 0xf0, 0x00, 0x07, 0xe0, 0x00, 0x07, 0xe0, 0x00, 0x07, 0xf8, 0x00, 0x07, + 0xfc, 0x00, 0x07, 0xfc, 0x00, 0x03, 0xfc, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x00, 0xf8, 0x00, 0x01, 0xfc, 0x00, 0x01, 0xfc, 0x00, 0x01, 0xfc, 0x00, + 0x01, 0xfc, 0x00, 0x00, 0xf8, 0x00 +}; diff --git a/sketch.json b/sketch.json new file mode 100644 index 0000000..048e3a8 --- /dev/null +++ b/sketch.json @@ -0,0 +1,9 @@ +{ + "cpu": { + "fqbn": "arduino:avr:micro", + "name": "Arduino Micro", + "type": "serial" + }, + "secrets": [], + "included_libs": [] +} \ No newline at end of file