Cleaned up build tool, added stub for ds3231.

This commit is contained in:
Andrew Lalis 2023-01-04 11:37:11 +01:00
parent d6d6007e62
commit 8b076146cb
16 changed files with 213 additions and 103 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
bin/
build

View File

@ -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

View File

@ -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 <command>` 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:

4
build-tools/README.md Normal file
View File

@ -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.

View File

@ -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 <command> [args...]");
writeln("");
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("flash [buildArgs] - Flashes code onto a connected AVR device via AVRDude.");
writeln("clean - Removes all build files.");
writeln("help - Shows this help information.");
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) {
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;
return flashToMCU(buildPath("bin", "gympal.hex"));
}
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[];
}

65
build-tools/hash.d Normal file
View File

@ -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);
}
}

56
build-tools/util.d Normal file
View File

@ -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[];
}

29
prepare-build-tools.d Executable file
View File

@ -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);
}

View File

@ -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 };

View File

@ -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);

3
src/lib/ds3231.c Normal file
View File

@ -0,0 +1,3 @@
#include "ds3231.h"
// TODO: Implement this!

8
src/lib/ds3231.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef DS3231_H
#define DS3231_H
// TODO: Implement this!
void ds3231_init();
#endif