Cleaned up build tool, added stub for ds3231.
This commit is contained in:
parent
d6d6007e62
commit
8b076146cb
|
@ -1 +1,2 @@
|
||||||
bin/
|
bin/
|
||||||
|
build
|
27
Makefile
27
Makefile
|
@ -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
|
|
|
@ -6,9 +6,13 @@ The GymPal is a lightweight piece of hardware with built-in storage, time-keepin
|
||||||
## Development
|
## 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.
|
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 <command>` to run various build commands. See `./build help` for more information.
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
Here's a list of the hardware that this project uses, just for reference:
|
Here's a list of the hardware that this project uses, just for reference:
|
||||||
|
|
|
@ -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.
|
|
@ -9,13 +9,9 @@ import std.file;
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
import std.path;
|
import std.path;
|
||||||
import std.digest.md;
|
|
||||||
import std.base64;
|
|
||||||
|
|
||||||
const string MCU_ID = "atmega328p";
|
import util;
|
||||||
const ulong CPU_FREQ = 16_000_000;
|
import hash;
|
||||||
const string BOOTLOADER = "arduino";
|
|
||||||
const ulong AVRDUDE_BAUDRATE = 57_600;
|
|
||||||
|
|
||||||
const string SOURCE_DIR = "src";
|
const string SOURCE_DIR = "src";
|
||||||
const string BUILD_DIR = "bin";
|
const string BUILD_DIR = "bin";
|
||||||
|
@ -28,12 +24,20 @@ const string[] COMPILER_FLAGS = [
|
||||||
"-mmcu=atmega328p"
|
"-mmcu=atmega328p"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const string[] AVRDUDE_FLAGS = [
|
||||||
|
"-c arduino",
|
||||||
|
"-p m328p",
|
||||||
|
"-P /dev/ttyUSB0",
|
||||||
|
"-b 57600",
|
||||||
|
"-u"
|
||||||
|
];
|
||||||
|
|
||||||
alias BuildCommand = int function(string[]);
|
alias BuildCommand = int function(string[]);
|
||||||
|
|
||||||
int main(string[] args) {
|
int main(string[] args) {
|
||||||
string command;
|
string command;
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
command = "help";
|
command = "build";
|
||||||
} else {
|
} else {
|
||||||
command = args[1].strip.toLower;
|
command = args[1].strip.toLower;
|
||||||
}
|
}
|
||||||
|
@ -57,16 +61,21 @@ int main(string[] args) {
|
||||||
|
|
||||||
int helpCommand(string[] args) {
|
int helpCommand(string[] args) {
|
||||||
writeln("build.d - A simple build script for C files.");
|
writeln("build.d - A simple build script for C files.");
|
||||||
|
writeln("--------------------------------------------");
|
||||||
|
writeln("Usage: ./build.d <command> [args...]");
|
||||||
|
writeln("");
|
||||||
writeln("The following commands are available:");
|
writeln("The following commands are available:");
|
||||||
writeln("build [-f] - Compiles source code. Use -f to force rebuild.");
|
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(" 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(" This is also the default command if none is specified.");
|
||||||
writeln("clean - Removes all build files.");
|
writeln(" flash Flashes code onto a connected AVR device via AVRDude.");
|
||||||
writeln("help - Shows this help information.");
|
writeln(" clean Removes all build files.");
|
||||||
|
writeln(" help Shows this help information.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int clean(string[] args) {
|
int clean(string[] args) {
|
||||||
|
if (!exists(BUILD_DIR)) return 0;
|
||||||
rmdirRecurse(BUILD_DIR);
|
rmdirRecurse(BUILD_DIR);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -77,9 +86,16 @@ int buildCommand(string[] args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int flashCommand(string[] args) {
|
int flashCommand(string[] args) {
|
||||||
int result = buildCommand(args);
|
string hexFilePath = buildPath(BUILD_DIR, "gympal.hex");
|
||||||
if (result != 0) return result;
|
if (!exists(hexFilePath)) {
|
||||||
return flashToMCU(buildPath("bin", "gympal.hex"));
|
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) {
|
int build(bool force = false) {
|
||||||
|
@ -101,35 +117,8 @@ int build(bool force = false) {
|
||||||
return 0;
|
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) {
|
bool shouldCompileSource(string sourcePath) {
|
||||||
string name = baseName(sourcePath);
|
return !contentMatchesHash(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string compileSourceToObject(string sourcePath, bool force = false) {
|
string compileSourceToObject(string sourcePath, bool force = false) {
|
||||||
|
@ -137,7 +126,6 @@ string compileSourceToObject(string sourcePath, bool force = false) {
|
||||||
string name = baseName(sourcePath);
|
string name = baseName(sourcePath);
|
||||||
name = name[0 .. name.lastIndexOf('.')];
|
name = name[0 .. name.lastIndexOf('.')];
|
||||||
string objectPath = buildPath("bin", name ~ ".o");
|
string objectPath = buildPath("bin", name ~ ".o");
|
||||||
string hashPath = buildPath("bin", "hash", name ~ ".md5");
|
|
||||||
|
|
||||||
if (!force && !shouldCompileSource(sourcePath)) {
|
if (!force && !shouldCompileSource(sourcePath)) {
|
||||||
writefln!"Not compiling %s because no changes detected."(sourcePath);
|
writefln!"Not compiling %s because no changes detected."(sourcePath);
|
||||||
|
@ -151,12 +139,7 @@ string compileSourceToObject(string sourcePath, bool force = false) {
|
||||||
);
|
);
|
||||||
writeln(cmd);
|
writeln(cmd);
|
||||||
runOrQuit(cmd);
|
runOrQuit(cmd);
|
||||||
|
saveHash(sourcePath);
|
||||||
ubyte[16] hash = md5Of(readText(sourcePath));
|
|
||||||
string hashDir = buildPath("bin", "hash");
|
|
||||||
if (!exists(hashDir)) mkdir(hashDir);
|
|
||||||
std.file.write(hashPath, Base64.encode(hash));
|
|
||||||
|
|
||||||
return objectPath;
|
return objectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,25 +167,3 @@ string copyToHex(string elfFile) {
|
||||||
runOrQuit(cmd);
|
runOrQuit(cmd);
|
||||||
return hexFile;
|
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[];
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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[];
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
|
|
||||||
|
// Chip Select
|
||||||
struct signal cs = { .ddr = &DDRB, .port = &PORTB, .pin = 2 };
|
struct signal cs = { .ddr = &DDRB, .port = &PORTB, .pin = 2 };
|
||||||
// Back Light
|
// Back Light
|
||||||
struct signal bl = { .ddr = &DDRB, .port = &PORTB, .pin = 1 };
|
struct signal bl = { .ddr = &DDRB, .port = &PORTB, .pin = 1 };
|
||||||
|
|
|
@ -7,13 +7,18 @@
|
||||||
#ifndef DISPLAY_H
|
#ifndef DISPLAY_H
|
||||||
#define DISPLAY_H
|
#define DISPLAY_H
|
||||||
|
|
||||||
#include "st7735.h"
|
#include "lib/st7735.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the display.
|
* @brief Initializes the display.
|
||||||
*/
|
*/
|
||||||
void display_init();
|
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();
|
struct st7735* display_get_lcd();
|
||||||
|
|
||||||
void display_show_str(char* str);
|
void display_show_str(char* str);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "ds3231.h"
|
||||||
|
|
||||||
|
// TODO: Implement this!
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef DS3231_H
|
||||||
|
#define DS3231_H
|
||||||
|
|
||||||
|
// TODO: Implement this!
|
||||||
|
|
||||||
|
void ds3231_init();
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue