diff --git a/.gitignore b/.gitignore index 6dd29b7..44f3e23 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -bin/ \ No newline at end of file +bin/ +build \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 0923e9f..0000000 --- a/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -all: clean build - -clean: - rm -rf bin/ - -# The following settings are very important! -# -c arduino for flashing to an AVR device with arduino bootloader. -# -p atmega328p is for the specific microcontroller. -# -b 57600 is the baudrate for transmission. Nano boards ONLY accept 57600, uno boards might accept 115200. -# -P specifies the port. -flash: build - avrdude -c arduino -p atmega328p -P /dev/ttyUSB0 -b 57600 -U flash:w:bin/gympal.hex:i - -build: gympal.hex - -gympal.hex: gympal.o control.o - avr-gcc -Os -mmcu=atmega328p -o bin/gympal.elf bin/gympal.o bin/control.o - avr-objcopy -O ihex -R .eeprom bin/gympal.elf bin/gympal.hex - -gympal.o: src/gympal.c bin - avr-gcc -Wall -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o bin/gympal.o src/gympal.c - -control.o: src/control.c bin - avr-gcc -Wall -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o bin/control.o src/control.c - -bin: - mkdir bin \ No newline at end of file diff --git a/README.md b/README.md index f89e940..c350381 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,13 @@ The GymPal is a lightweight piece of hardware with built-in storage, time-keepin ## Development The software for this system is developed in C, using [avr-libc](https://www.nongnu.org/avr-libc/), and the build toolchain is managed by make, using [avr-gcc](https://linux.die.net/man/1/avr-gcc) and [avr-dude](https://github.com/avrdudes/avrdude) to upload firmware to the Atmega328p microcontroller. -To compile the firmware, you can run `make`. +```shell +sudo apt install gcc-avr +sudo apt install avr-libc +sudo apt install avrdude +``` -To upload the firmware to an Arduino, run `make flash`. +After cloning this repository, you must first compile the build script with `./prepare-build-tools.d`. Then, you can run `./build ` to run various build commands. See `./build help` for more information. ## Hardware Here's a list of the hardware that this project uses, just for reference: diff --git a/build-tools/README.md b/build-tools/README.md new file mode 100644 index 0000000..725d415 --- /dev/null +++ b/build-tools/README.md @@ -0,0 +1,4 @@ +# Build-Tools +This directory contains the sources for a small D program that manages compilation and uploading of code to AVR devices. + +Run `./prepare-build-tools.d` from the main project directory to compile it. diff --git a/build.d b/build-tools/build.d similarity index 57% rename from build.d rename to build-tools/build.d index e7311bc..49eb195 100755 --- a/build.d +++ b/build-tools/build.d @@ -9,13 +9,9 @@ import std.file; import std.string; import std.conv; import std.path; -import std.digest.md; -import std.base64; -const string MCU_ID = "atmega328p"; -const ulong CPU_FREQ = 16_000_000; -const string BOOTLOADER = "arduino"; -const ulong AVRDUDE_BAUDRATE = 57_600; +import util; +import hash; const string SOURCE_DIR = "src"; const string BUILD_DIR = "bin"; @@ -28,12 +24,20 @@ const string[] COMPILER_FLAGS = [ "-mmcu=atmega328p" ]; +const string[] AVRDUDE_FLAGS = [ + "-c arduino", + "-p m328p", + "-P /dev/ttyUSB0", + "-b 57600", + "-u" +]; + alias BuildCommand = int function(string[]); int main(string[] args) { string command; if (args.length < 2) { - command = "help"; + command = "build"; } else { command = args[1].strip.toLower; } @@ -57,16 +61,21 @@ int main(string[] args) { int helpCommand(string[] args) { writeln("build.d - A simple build script for C files."); + writeln("--------------------------------------------"); + writeln("Usage: ./build.d [args...]"); + writeln(""); writeln("The following commands are available:"); - writeln("build [-f] - Compiles source code. Use -f to force rebuild."); - writeln(" By default, sources are hashed, and only built if changes are detected."); - writeln("flash [buildArgs] - Flashes code onto a connected AVR device via AVRDude."); - writeln("clean - Removes all build files."); - writeln("help - Shows this help information."); + writeln(" build [-f] Compiles source code. Use -f to force rebuild."); + writeln(" By default, sources are hashed, and only built if changes are detected."); + writeln(" This is also the default command if none is specified."); + writeln(" flash Flashes code onto a connected AVR device via AVRDude."); + writeln(" clean Removes all build files."); + writeln(" help Shows this help information."); return 0; } int clean(string[] args) { + if (!exists(BUILD_DIR)) return 0; rmdirRecurse(BUILD_DIR); return 0; } @@ -77,9 +86,16 @@ int buildCommand(string[] args) { } int flashCommand(string[] args) { - int result = buildCommand(args); - if (result != 0) return result; - return flashToMCU(buildPath("bin", "gympal.hex")); + string hexFilePath = buildPath(BUILD_DIR, "gympal.hex"); + if (!exists(hexFilePath)) { + writeln("Hex file doesn't exist yet; building it now."); + int result = buildCommand(args); + if (result != 0) return result; + } + string flags = join(AVRDUDE_FLAGS, " "); + string cmd = format!"avrdude %s -U flash:w:%s:i"(flags, hexFilePath); + writeln(cmd); + return run(cmd); } int build(bool force = false) { @@ -101,35 +117,8 @@ int build(bool force = false) { return 0; } -//-------- Utility functions below here ------- - -int run(string shellCommand) { - import std.process : Pid, spawnShell, wait; - Pid pid = spawnShell(shellCommand); - return wait(pid); -} - -void runOrQuit(string shellCommand, int[] successExitCodes = [0]) { - import core.stdc.stdlib : exit; - import std.algorithm : canFind; - int result = run(shellCommand); - if (!canFind(successExitCodes, result)) exit(result); -} - -void quitIfNonZero(int n) { - import core.stdc.stdlib : exit; - if (n != 0) exit(n); -} - bool shouldCompileSource(string sourcePath) { - string name = baseName(sourcePath); - name = name[0 .. name.lastIndexOf('.')]; - string hashPath = buildPath("bin", "hash", name ~ ".md5"); - string objectPath = buildPath("bin", name ~ ".o"); - if (!exists(hashPath) || !exists(objectPath)) return true; - ubyte[] storedHash = Base64.decode(readText(hashPath).strip()); - ubyte[16] currentHash = md5Of(readText(sourcePath)); - return storedHash != currentHash; + return !contentMatchesHash(sourcePath); } string compileSourceToObject(string sourcePath, bool force = false) { @@ -137,7 +126,6 @@ string compileSourceToObject(string sourcePath, bool force = false) { string name = baseName(sourcePath); name = name[0 .. name.lastIndexOf('.')]; string objectPath = buildPath("bin", name ~ ".o"); - string hashPath = buildPath("bin", "hash", name ~ ".md5"); if (!force && !shouldCompileSource(sourcePath)) { writefln!"Not compiling %s because no changes detected."(sourcePath); @@ -151,12 +139,7 @@ string compileSourceToObject(string sourcePath, bool force = false) { ); writeln(cmd); runOrQuit(cmd); - - ubyte[16] hash = md5Of(readText(sourcePath)); - string hashDir = buildPath("bin", "hash"); - if (!exists(hashDir)) mkdir(hashDir); - std.file.write(hashPath, Base64.encode(hash)); - + saveHash(sourcePath); return objectPath; } @@ -184,25 +167,3 @@ string copyToHex(string elfFile) { runOrQuit(cmd); return hexFile; } - -int flashToMCU(string hexFile) { - string cmd = format!"avrdude -c %s -p m328p -P /dev/ttyUSB0 -b %d -u -U flash:w:%s:i"( - BOOTLOADER, - AVRDUDE_BAUDRATE, - hexFile - ); - writeln(cmd); - return run(cmd); -} - -string[] findFiles(string path, string suffix = null) { - import std.array; - import std.algorithm; - auto app = appender!(string[]); - foreach (DirEntry entry; dirEntries(path, SpanMode.breadth, false)) { - if (entry.isFile && (suffix is null || entry.name.endsWith(suffix))) { - app ~= entry.name; - } - } - return app[]; -} diff --git a/build-tools/hash.d b/build-tools/hash.d new file mode 100644 index 0000000..fc4fdf5 --- /dev/null +++ b/build-tools/hash.d @@ -0,0 +1,65 @@ +/** + * Helper for hashing the content of source files to determine if we need to + * re-compile certain sources. + */ +module hash; + +import std.stdio; +import std.file; +import std.string; +import std.digest.md; +import std.base64; + +const HASH_FILE = "bin/hash.txt"; + +/** + * Determines if the content of the source file at the given path matches + * the hash we have for it (if possible). + * Params: + * sourcePath = The path to the source file. + * Returns: True if we have a hash for the source file, and it matches the + * current hash of the file contents. False if we don't have a hash or it + * doesn't match. + */ +bool contentMatchesHash(string sourcePath) { + if (!exists(sourcePath) || !isFile(sourcePath)) return false; + string[string] hashes = readHashes(); + if (sourcePath !in hashes) return false; + ubyte[16] rawHash = md5Of(readText(sourcePath)); + ubyte[] savedHash = Base64.decode(hashes[sourcePath]); + return rawHash == savedHash; +} + +/** + * Saves the hash of the given source path to the hash file. + * Params: + * sourcePath = The source path of the file to save. + */ +void saveHash(string sourcePath) { + if (!exists(sourcePath) || !isFile(sourcePath)) return; + ubyte[16] rawHash = md5Of(readText(sourcePath)); + string hash = Base64.encode(rawHash); + string[string] hashes = readHashes(); + hashes[sourcePath] = hash; + saveHashes(hashes); +} + +private string[string] readHashes() { + string[string] hashes; + if (!exists(HASH_FILE)) return hashes; + auto file = File(HASH_FILE, "r"); + foreach (string line; lines(file)) { + string[] parts = split(line, ":"); + string sourcePath = parts[0].strip; + string hashBase64 = parts[1].strip; + hashes[sourcePath] = hashBase64; + } + return hashes; +} + +private void saveHashes(string[string] hashes) { + auto file = File(HASH_FILE, "w"); + foreach (string sourcePath, string hash; hashes) { + file.writefln!"%s:%s"(sourcePath, hash); + } +} \ No newline at end of file diff --git a/build-tools/util.d b/build-tools/util.d new file mode 100644 index 0000000..42022f7 --- /dev/null +++ b/build-tools/util.d @@ -0,0 +1,56 @@ +module util; + +/** + * Runs the given shell command. + * Params: + * shellCommand = The command to run. + * Returns: The exit code of the command. + */ +int run(string shellCommand) { + import std.process : Pid, spawnShell, wait; + Pid pid = spawnShell(shellCommand); + return wait(pid); +} + +/** + * Runs the given command, and exits if an unsatisfactory exit code is returned. + * Params: + * shellCommand = The command to run. + * successExitCodes = The list of exit codes which are considered success. + */ +void runOrQuit(string shellCommand, int[] successExitCodes = [0]) { + import core.stdc.stdlib : exit; + import std.algorithm : canFind; + int result = run(shellCommand); + if (!canFind(successExitCodes, result)) exit(result); +} + +/** + * Exits the program if the given number is non-zero. + * Params: + * n = The number to check. + */ +void quitIfNonZero(int n) { + import core.stdc.stdlib : exit; + if (n != 0) exit(n); +} + +/** + * Finds a list of files in a directory which match a given suffix. + * Params: + * path = The path to look in. + * suffix = The suffix to match. + * Returns: The list of paths to files that match. + */ +string[] findFiles(string path, string suffix = null) { + import std.array; + import std.algorithm; + import std.file; + auto app = appender!(string[]); + foreach (DirEntry entry; dirEntries(path, SpanMode.breadth, false)) { + if (entry.isFile && (suffix is null || entry.name.endsWith(suffix))) { + app ~= entry.name; + } + } + return app[]; +} \ No newline at end of file diff --git a/prepare-build-tools.d b/prepare-build-tools.d new file mode 100755 index 0000000..1b01249 --- /dev/null +++ b/prepare-build-tools.d @@ -0,0 +1,29 @@ +#!/usr/bin/env rdmd +/** + * Simple script that just compiles this project's build system to an executable. + * Run this before building: `./prepare-build-tools.d` + */ +module prepare_build_tools; + +import std.process; +import std.array; +import std.file; +import std.algorithm; + +int main() { + auto sourceApp = appender!(string[]); + foreach (DirEntry entry; dirEntries("build-tools", SpanMode.shallow, false)) { + if (entry.isFile && endsWith(entry.name, ".d")) { + sourceApp ~= entry.name; + } + } + string[] sources = sourceApp[]; + string sourcesStr = join(sources, " "); + Pid pid = spawnShell("dmd -O " ~ sourcesStr ~ " -release -of=build"); + scope (exit) { + if (exists("build.o")) { + std.file.remove("build.o"); + } + } + return wait(pid); +} diff --git a/src/display.c b/src/display.c index 2451c90..7e14984 100644 --- a/src/display.c +++ b/src/display.c @@ -1,5 +1,6 @@ #include "display.h" +// Chip Select struct signal cs = { .ddr = &DDRB, .port = &PORTB, .pin = 2 }; // Back Light struct signal bl = { .ddr = &DDRB, .port = &PORTB, .pin = 1 }; diff --git a/src/display.h b/src/display.h index 2505322..d272fda 100644 --- a/src/display.h +++ b/src/display.h @@ -7,13 +7,18 @@ #ifndef DISPLAY_H #define DISPLAY_H -#include "st7735.h" +#include "lib/st7735.h" /** * @brief Initializes the display. */ void display_init(); +/** + * @brief Gets a pointer to the underlying ST7735 LCD struct, which can be used + * for calling functions on it manually. + * @return A pointer to the ST7735 LCD struct. + */ struct st7735* display_get_lcd(); void display_show_str(char* str); diff --git a/src/lib/ds3231.c b/src/lib/ds3231.c new file mode 100644 index 0000000..3968297 --- /dev/null +++ b/src/lib/ds3231.c @@ -0,0 +1,3 @@ +#include "ds3231.h" + +// TODO: Implement this! \ No newline at end of file diff --git a/src/lib/ds3231.h b/src/lib/ds3231.h new file mode 100644 index 0000000..a0cd63c --- /dev/null +++ b/src/lib/ds3231.h @@ -0,0 +1,8 @@ +#ifndef DS3231_H +#define DS3231_H + +// TODO: Implement this! + +void ds3231_init(); + +#endif \ No newline at end of file diff --git a/src/font.c b/src/lib/font.c similarity index 100% rename from src/font.c rename to src/lib/font.c diff --git a/src/font.h b/src/lib/font.h similarity index 100% rename from src/font.h rename to src/lib/font.h diff --git a/src/st7735.c b/src/lib/st7735.c similarity index 100% rename from src/st7735.c rename to src/lib/st7735.c diff --git a/src/st7735.h b/src/lib/st7735.h similarity index 100% rename from src/st7735.h rename to src/lib/st7735.h