First commit
This commit is contained in:
commit
da93288b4d
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
### Git ###
|
||||
*.orig
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Vim ###
|
||||
# swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
# session
|
||||
Session.vim
|
||||
# temporary
|
||||
.netrwhist
|
||||
# auto-generated tag files
|
||||
tags
|
||||
|
||||
# Custom
|
||||
.clangd/
|
||||
.cache
|
||||
.build
|
||||
compile_commands.json
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "libs/serial"]
|
||||
path = libs/serial
|
||||
url = https://github.com/wjwwood/serial
|
||||
[submodule "libs/CLI11"]
|
||||
path = libs/CLI11
|
||||
url = https://github.com/CLIUtils/CLI11
|
40
Makefile
Normal file
40
Makefile
Normal file
|
@ -0,0 +1,40 @@
|
|||
CXX = g++
|
||||
BUILD = .build
|
||||
TARGET = upload
|
||||
OPT = g -g
|
||||
|
||||
CFLAGS = -Wall -Wextra -std=c++20 -O$(OPT) -flto -Iinclude -Ilibs/serial/include -Ilibs/CLI11/include
|
||||
LDFLAGS = -Wall -Wextra -O$(OPT) -flto
|
||||
|
||||
SRC = \
|
||||
src/main.cpp \
|
||||
libs/serial/src/serial.cc \
|
||||
libs/serial/src/impl/unix.cc \
|
||||
libs/serial/src/impl/list_ports/list_ports_linux.cc \
|
||||
|
||||
OBJ_1 = $(notdir $(SRC:.cpp=.o))
|
||||
OBJ_2 = $(notdir $(OBJ_1:.cc=.o))
|
||||
OBJ = $(addprefix $(BUILD)/, $(OBJ_2))
|
||||
# We can't use this as it will use the wrong main
|
||||
# vpath %.c $(sort $(dir $(SRC)))
|
||||
vpath %.cpp $(dir $(SRC))
|
||||
vpath %.cc $(dir $(SRC))
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BUILD) $(BUILD)/$(TARGET)
|
||||
|
||||
$(BUILD)/%.o: %.cpp Makefile | $(BUILD)
|
||||
$(CXX) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/%.o: %.cc Makefile | $(BUILD)
|
||||
$(CXX) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/$(TARGET): $(OBJ) Makefile
|
||||
$(CXX) $(LDFLAGS) $(OBJ) -o $@
|
||||
|
||||
$(BUILD):
|
||||
mkdir $@
|
||||
|
||||
clean:
|
||||
rm -fr $(BUILD)
|
1
libs/CLI11
Submodule
1
libs/CLI11
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f862849488557eeee0397814a47449ecfdae0383
|
1
libs/serial
Submodule
1
libs/serial
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit cbcca7c83745fedd75afb7a0a27ee5c4112435c2
|
248
src/main.cpp
Normal file
248
src/main.cpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
#include <algorithm>
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
#include "CLI/App.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
#include "CLI/Config.hpp"
|
||||
|
||||
#include "serial/serial.h"
|
||||
|
||||
void enumerate_port() {
|
||||
std::vector<serial::PortInfo> devices_found = serial::list_ports();
|
||||
|
||||
auto iter = devices_found.begin();
|
||||
|
||||
while (iter != devices_found.end()) {
|
||||
serial::PortInfo device = *iter++;
|
||||
|
||||
printf("(%s, %s, %s)\n", device.port.c_str(), device.description.c_str(), device.hardware_id.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Template length
|
||||
template <typename T>
|
||||
T read_length(std::unique_ptr<serial::Serial>& ser) {
|
||||
uint8_t r[sizeof(T)];
|
||||
if (ser->read(r, sizeof(r)) != sizeof(r)) {
|
||||
throw std::runtime_error("Failed to read bytes");
|
||||
}
|
||||
|
||||
T value = 0;
|
||||
for (size_t i = 0; i < sizeof(T); ++i) {
|
||||
value += r[i] << (8*i);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void wait_for_ack(std::unique_ptr<serial::Serial>& ser) {
|
||||
uint8_t r;
|
||||
if (ser->read(&r, 1) != 1) {
|
||||
throw std::runtime_error("Failed to read byte");
|
||||
}
|
||||
|
||||
if (r == 0xFE) {
|
||||
throw std::runtime_error("Received nack");
|
||||
} else if (r != 0x01) {
|
||||
std::cout << "Response: " << r << ", " << std::hex << (uint32_t)r << '\n';
|
||||
throw std::runtime_error("Invalid response");
|
||||
}
|
||||
}
|
||||
|
||||
void write_command(std::unique_ptr<serial::Serial>& ser, uint8_t c) {
|
||||
if (ser->write(&c, 1) != 1) {
|
||||
throw std::runtime_error("Failed to write command");
|
||||
}
|
||||
|
||||
wait_for_ack(ser);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_length(std::unique_ptr<serial::Serial>& ser, T length) {
|
||||
uint8_t s[sizeof(T)];
|
||||
|
||||
for (size_t i = 0; i < sizeof(T); ++i) {
|
||||
s[i] = (length >> (8*i)) & 0xFF;
|
||||
}
|
||||
|
||||
if (ser->write(s, sizeof(s)) != sizeof(s)) {
|
||||
throw std::runtime_error("Failed to write length");
|
||||
}
|
||||
|
||||
if (length != read_length<T>(ser)) {
|
||||
throw std::runtime_error("Received back wrong length");
|
||||
}
|
||||
}
|
||||
|
||||
void write_data(std::unique_ptr<serial::Serial>& ser, uint8_t* data, uint16_t length) {
|
||||
if (ser->write(data, length) != length) {
|
||||
throw std::runtime_error("Failed to write data");
|
||||
}
|
||||
|
||||
// @todo: As a response we need to send the CRC of the data back and compare it
|
||||
|
||||
wait_for_ack(ser);
|
||||
}
|
||||
|
||||
void read_data(std::unique_ptr<serial::Serial>& ser, uint8_t* data, uint16_t length) {
|
||||
if (ser->read(data, length) != length) {
|
||||
throw std::runtime_error("Failed to read data");
|
||||
}
|
||||
}
|
||||
|
||||
// @todo We need to check if we are already in the bootloader
|
||||
void program_stm32(std::unique_ptr<serial::Serial>& ser, std::string filename) {
|
||||
write_command(ser, 0xFF);
|
||||
write_command(ser, 'b');
|
||||
|
||||
// @todo Can we somehow check if the device is present
|
||||
std::cout << "Waiting for device to reboot...\n";
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// @todo We need some sort of dfu library because this is terrible
|
||||
std::string cmd = "dfu-util -a 0 -D " + filename + " -s 0x08000000:leave";
|
||||
std::cout << "Running command: " << cmd << '\n';
|
||||
system(cmd.c_str());
|
||||
}
|
||||
|
||||
// @todo Send over base address
|
||||
void program_rom(std::unique_ptr<serial::Serial>& ser, std::string filename, uint16_t address) {
|
||||
// Get the file size
|
||||
std::ifstream file(filename, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open the file");
|
||||
}
|
||||
file.seekg(0, std::ios::end);
|
||||
uint16_t length = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
write_command(ser, 0xFF);
|
||||
write_command(ser, 'u');
|
||||
// We are targeting ROM
|
||||
write_command(ser, 0x01);
|
||||
|
||||
std::cout << "Uploading data... (size: " << length << ")\n";
|
||||
|
||||
// Send length of data
|
||||
write_length<uint16_t>(ser, length);
|
||||
|
||||
// Send actual data
|
||||
uint8_t* data = new uint8_t[length];
|
||||
file.read(reinterpret_cast<char*>(data), length);
|
||||
write_data(ser, data, length);
|
||||
delete[] data;
|
||||
|
||||
// Track progress
|
||||
std::cout << "Programming... (base address: 0x" << std::hex << address << std::dec << ")\n";
|
||||
for (uint16_t progress = 0; progress != length; progress = read_length<uint16_t>(ser)) {
|
||||
std::cout << "Progress: " << progress << '/' << length << '\r';
|
||||
}
|
||||
std::cout << '\n';
|
||||
|
||||
// Wait for otherside to signal we are done
|
||||
wait_for_ack(ser);
|
||||
std::cout << "Done!\n";
|
||||
}
|
||||
|
||||
// @todo This shares a lot of code with ROM upload
|
||||
void program_i2c(std::unique_ptr<serial::Serial>& ser, std::string filename, uint8_t address) {
|
||||
// Get the file size
|
||||
std::ifstream file(filename, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open the file");
|
||||
}
|
||||
file.seekg(0, std::ios::end);
|
||||
uint16_t length = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
write_command(ser, 0xFF);
|
||||
write_command(ser, 'u');
|
||||
// We are targeting I2C
|
||||
write_command(ser, 0x02);
|
||||
|
||||
std::cout << "Uploading data... (size: " << length << ")\n";
|
||||
|
||||
// Send length of data
|
||||
write_length<uint16_t>(ser, length);
|
||||
|
||||
// Send actual data
|
||||
uint8_t* data = new uint8_t[length];
|
||||
file.read(reinterpret_cast<char*>(data), length);
|
||||
write_data(ser, data, length);
|
||||
delete[] data;
|
||||
|
||||
// Send the I2C address
|
||||
write_length<uint8_t>(ser, address);
|
||||
|
||||
// Checking that the device is present
|
||||
wait_for_ack(ser);
|
||||
|
||||
std::cout << "Restarting target to bootloader... (target: 0x" << std::hex << address << std::dec << ")\n";
|
||||
|
||||
wait_for_ack(ser);
|
||||
|
||||
char version_string[16] = {0};
|
||||
read_data(ser, reinterpret_cast<uint8_t*>(version_string), sizeof(version_string));
|
||||
std::cout << "Bootloader version: " << version_string << '\n';
|
||||
|
||||
// Track progress
|
||||
std::cout << "Programming...\n";
|
||||
for (uint16_t progress = 0; progress != length; progress = read_length<uint16_t>(ser)) {
|
||||
std::cout << "Progress: " << progress << '/' << length << '\r';
|
||||
}
|
||||
std::cout << '\n';
|
||||
|
||||
// Wait for otherside to signal we are done
|
||||
wait_for_ack(ser);
|
||||
std::cout << "Done!\n";
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
CLI::App app("Upload utility for Z80 computer");
|
||||
app.require_subcommand(1);
|
||||
|
||||
std::string filename;
|
||||
std::string device;
|
||||
uint16_t address;
|
||||
std::unique_ptr<serial::Serial> ser = nullptr;
|
||||
|
||||
app.add_option("filename", filename, "File to upload")->required();
|
||||
app.add_option("--dev,-d", device, "Serial device to use")->default_val("/dev/ttyUSB0");
|
||||
|
||||
app.add_subcommand("stm", "Program STM32 mcu")->callback([&] {
|
||||
program_stm32(ser, filename);
|
||||
});
|
||||
|
||||
CLI::App* rom = app.add_subcommand("rom", "Program ROM/RAM")->callback([&] {
|
||||
program_rom(ser, filename, address);
|
||||
});
|
||||
rom->add_option("--address,-a", address, "Base address")->default_val(0x0000);
|
||||
|
||||
CLI::App* i2c = app.add_subcommand("i2c", "Program I2C bootloader")->callback([&] {
|
||||
program_i2c(ser, filename, address);
|
||||
});
|
||||
i2c->add_option("--address,-a", address, "I2C address")->default_val(0x29);
|
||||
|
||||
// @todo Implement upload for bload
|
||||
// CLI::App* bload = app.add_subcommand("bload", "Upload for bload");
|
||||
|
||||
app.parse_complete_callback([&] {
|
||||
std::cout << "Initializing serial\n";
|
||||
ser = std::make_unique<serial::Serial>("/dev/ttyUSB0", 115200, serial::Timeout::simpleTimeout(1000));
|
||||
|
||||
if (!ser->isOpen()) {
|
||||
throw std::runtime_error("Port is not open");
|
||||
}
|
||||
|
||||
ser->flushInput();
|
||||
ser->flushOutput();
|
||||
});
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
}
|
Loading…
Reference in New Issue
Block a user