diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd8dd51 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +spi-transfer +*build* +host/argparse diff --git a/arduino/arduino.ino b/arduino/arduino.ino new file mode 100644 index 0000000..84841a7 --- /dev/null +++ b/arduino/arduino.ino @@ -0,0 +1,172 @@ +#include +#include "api/Compat.h" +#include "common.hpp" +#include + + +uint8_t buffer[0x8000]; +const long SERIAL_TIMEOUT = 6000; // ms +SPISettings spiSettings(100, MSBFIRST, SPI_MODE0); // max freq [Hz], bit order, mode +// lowest frequency seems to be 2000 Hz + +/* void maskedWrite(uint32_t mask, uint32_t bits) { */ +/* for (uint32_t i = 0; i < 32; i++) { */ +/* if (mask & (1 << i)) { */ +/* digitalWrite(i, bits & (1 << i)); */ +/* } */ +/* } */ +/* } */ +void blinkLED(unsigned n=5, unsigned delay_=200) { + for (unsigned i = 0; i < n; i++) { + digitalWrite(LED_BUILTIN, HIGH); + delay(delay_); + digitalWrite(LED_BUILTIN, LOW); + delay(delay_); + } +} + + +void sendErrorMSG(const String& msg1, int i1, const String& msg2="", int i2=-2, int mode=DEC) { + Serial.write(ControlBytes::PRINT); + Serial.print(msg1); + Serial.print(i1, mode); + Serial.print(msg2); + if (i2 != -2) + Serial.print(i2, mode); + Serial.println(""); +} + + +uint8_t receiveControlByte(const String& fname) { + uint8_t recByte; + // not using Serial.read() because it doesnt use the set serial timeout + if (Serial.readBytes(&recByte, 1) != 1) { + Serial.write(ControlBytes::PRINT); + Serial.print(fname); + Serial.print(": failed to receive control byte"); + return 0xff; + } + return recByte; +} + + +buffer_t receiveSize() { + // receive size to read or write + buffer_t size = 0; + for (unsigned i = 0; i < sizeof(buffer_t); i++) { + // not using Serial.read() because it doesnt use the set serial timeout + uint8_t receivedByte = 0; + if (Serial.readBytes(&receivedByte, 1) != 1) { + sendErrorMSG("receiveSize: could not read bufferSize from Serial, failed at byte nr. ", i+1); + return 0; + } + size |= (receivedByte << i * 8); + } + if (size > MAX_BUFFER_SIZE) { + sendErrorMSG("Received bufferSize=", size, " is larger than MAX_BUFFER_SIZE=", MAX_BUFFER_SIZE, HEX); + return 0; + } + return size; +} + + +void write() { + buffer_t size = receiveSize(); + if (size == 0) { + return; + } + int receivedBytes = Serial.readBytes(buffer, size); + if (receivedBytes != size) { + sendErrorMSG("write: Received buffer of size=", receivedBytes, ", expected size=", size, HEX); + return; + } + uint8_t ctrlByte = receiveControlByte("write"); + if (ctrlByte != ControlBytes::WRITE) { + sendErrorMSG("write: Did not receive control byte write: byte=", ctrlByte); + return; + } + /* // debug */ + /* Serial.write(ControlBytes::PRINT); */ + /* Serial.print("BufSize="); */ + /* Serial.print(size, HEX); */ + /* Serial.print(", buffer="); */ + /* for (unsigned i = 0; i < size; i++) { */ + /* Serial.print(buffer[i], HEX); */ + /* Serial.print(","); */ + /* } */ + /* Serial.println("."); */ + + SPI.transfer(buffer, size); + Serial.write(ControlBytes::WRITE); +} + + + +void setup() { + pinMode(LEDR, OUTPUT); + pinMode(LEDG, OUTPUT); + pinMode(LEDB, OUTPUT); + digitalWrite(LEDR, LOW); + digitalWrite(LEDG, HIGH); + digitalWrite(LEDB, HIGH); + + + Serial.begin(9600); + // wait until available + /* while (!Serial); { */ + /* delay(100); */ + /* } */ + /* digitalWrite(LED_BUILTIN, LOW); */ + // empty buffer + while (Serial.read() != -1); + Serial.setTimeout(SERIAL_TIMEOUT); + + SPI.begin(); + SPI.beginTransaction(spiSettings); + + digitalWrite(LEDR, LOW); + digitalWrite(LEDG, LOW); + digitalWrite(LEDB, LOW); +} + + +void loop() { + if (!Serial) { + delay(100); + digitalWrite(LEDG, LOW); + digitalWrite(LEDB, HIGH); + return; + } + else { + digitalWrite(LEDG, HIGH); + digitalWrite(LEDB, LOW); + } + + int ctrlByte = Serial.read(); + if (ctrlByte == -1) { + delay(100); + return; + } + /* blinkLED(3, 50); */ + switch(ctrlByte) { + case ControlBytes::READY: { + Serial.write(ControlBytes::READY); + break; + } + case ControlBytes::WRITE: { + digitalWrite(LEDG, LOW); + digitalWrite(LEDR, HIGH); + write(); + digitalWrite(LEDR, LOW); + digitalWrite(LEDG, HIGH); + break; + } + default: { + digitalWrite(LEDR, HIGH); + digitalWrite(LEDG, LOW); + digitalWrite(LEDB, HIGH); + sendErrorMSG("loop: received invalid ControlByte: byte=", ctrlByte); + break; + } + } +} diff --git a/arduino/common.hpp b/arduino/common.hpp new file mode 100644 index 0000000..9fd2363 --- /dev/null +++ b/arduino/common.hpp @@ -0,0 +1,40 @@ +#pragma once + + +#ifndef NO_ARDUINO +#include +using string_type = String; +#else +#include +using string_type = std::string; +#endif + + +using buffer_t = uint16_t; +using address_t = uint32_t; + +const buffer_t MAX_BUFFER_SIZE = 0x8000; + +enum ControlBytes : uint8_t { + WRITE = 1, + READ = 2, + SET_ADDRESS = 3, + PRINT = 4, + READY = 5, + MEM_256KB = 11, + MEM_2M = 12, + MAX_ENUM, +}; +string_type ControlBytesString(ControlBytes ctrl) { + switch (ctrl) { + case ControlBytes::WRITE: { return "WRITE"; break; } + case ControlBytes::READ: { return "READ"; break; } + case ControlBytes::SET_ADDRESS: { return "SET_ADDRESS"; break; } + case ControlBytes::PRINT: { return "PRINT"; break; } + case ControlBytes::READY: { return "READY"; break; } + case ControlBytes::MEM_256KB: { return "MEM_256KB"; break; } + case ControlBytes::MEM_2M: { return "MEM_2M"; break; } + case ControlBytes::MAX_ENUM: {return "MAX_ENUM"; break; } + default: {return "UNKNOWN"; } + } +} diff --git a/arduino/compile_commands.json b/arduino/compile_commands.json new file mode 100644 index 0000000..b76d263 --- /dev/null +++ b/arduino/compile_commands.json @@ -0,0 +1,35 @@ +[ + { + "directory": "/home/matth/Projekte/tool/cpp-ad-eeprom/eeprom", + "arguments": [ + "/home/matth/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-g++", + "-c", + "-w", + "-g3", + "@/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/variants/NANO_RP2040_CONNECT/defines.txt", + "@/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/variants/NANO_RP2040_CONNECT/cxxflags.txt", + "-DARDUINO_ARCH_RP2040", + "-MMD", + "-mcpu=cortex-m0plus", + "-DARDUINO=10607", + "-DARDUINO_NANO_RP2040_CONNECT", + "-DARDUINO_ARCH_MBED_NANO", + "-DARDUINO_ARCH_MBED", + "-DARDUINO_LIBRARY_DISCOVERY_PHASE=0", + "-I/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/cores/arduino", + "-I/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/variants/NANO_RP2040_CONNECT", + "-I/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/cores/arduino/api/deprecated", + "-I/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/cores/arduino/api/deprecated-avr-comp", + "/home/matth/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include/", + "/home/matth/.arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include/c++/7.2.1/", + "/home/matth/.arduino15/internal/arduino_arm-none-eabi-gcc_7-2017q4_7b7be9f526b2cb64/arm-none-eabi/include/c++/7.2.1", + "/home/matth/.arduino15/internal/arduino_arm-none-eabi-gcc_7-2017q4_7b7be9f526b2cb64/arm-none-eabi/include/c++/7.2.1/arm-none-eabi/", + "-iprefix/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/cores/arduino", + "@/home/matth/.arduino15/packages/arduino/hardware/mbed_nano/4.0.8/variants/NANO_RP2040_CONNECT/includes.txt", + "/tmp/arduino/sketches/C40DAEE1A4F3FCAB8238A459D50B91D5/sketch/eeprom.ino.cpp", + "-o", + "/tmp/arduino/sketches/C40DAEE1A4F3FCAB8238A459D50B91D5/sketch/eeprom.ino.cpp.o" + ], + "file": "/tmp/arduino/sketches/C40DAEE1A4F3FCAB8238A459D50B91D5/sketch/eeprom.ino.cpp" + } +] diff --git a/arduino/sketch.yaml b/arduino/sketch.yaml new file mode 100644 index 0000000..3023f01 --- /dev/null +++ b/arduino/sketch.yaml @@ -0,0 +1,8 @@ +profiles: + nanorp: + fqbn: arduino:mbed_nano:nanorp2040connect + port: /dev/ttyACM0 + platforms: + - platform: arduino:mbed_nano (4.0.2) +default_port: /dev/ttyACM0 +default_fqbn: arduino:mbed_nano:nanorp2040connect diff --git a/host/.clangd b/host/.clangd new file mode 100644 index 0000000..28f6ada --- /dev/null +++ b/host/.clangd @@ -0,0 +1,3 @@ +CompileFlags: # Tweak the parse settings + Add: [-std=c++2a, -Wall, -Wextra, -Wpedantic, -Iargparse/include] +# https://clangd.llvm.org/config diff --git a/host/Makefile b/host/Makefile new file mode 100644 index 0000000..b2fb51a --- /dev/null +++ b/host/Makefile @@ -0,0 +1,57 @@ +CXX = /usr/bin/g++ +CXXFLAGS = -std=c++20 -MMD -MP -Wall -Wpedantic -Wextra +LDFLAGS = +LDLIBS = -lserial +IFLAGS = -Iargparse/include + + +OBJECT_DIR = ../build +EXEC_NAME = spi-transfer +EXEC = ../$(EXEC_NAME) + +SRC = $(wildcard *.cpp) $(wildcard */*.cpp) +# OBJECTS = $(SRC:%.cpp=$(OBJECT_DIR)/%.o) +OBJECTS = $($(notdir SRC):%.cpp=$(OBJECT_DIR)/%.o) +DEPENDS = ${OBJECTS:.o=.d} + +CXXFLAGS += $(IFLAGS) + + +default: $(EXEC) + echo $(OBJECTS) + +.PHONY: release install debug run clean +release: CXXFLAGS += -O3 +release : default + +# rule for the executable +$(EXEC): $(OBJECT_DIR) $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) +# include the makefiles generated by the -M flag +-include $(DEPENDS) + +$(OBJECT_DIR)/%.o: %.cpp + $(CXX) -c $< -o $@ $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) + +$(OBJECT_DIR): + mkdir -p $@ + +# Extra Options +install: + install -D -m 751 $(EXEC) $(DESTDIR)/$(EXEC_NAME) + + +# with debug flags +debug: CXXFLAGS += -g +debug: default + +# make with debug flags and run afterwards +run: CXXFLAGS += -g +run: default + ./$(EXEC) + +# remove all object and dependecy files +clean: + -rm -r $(OBJECT_DIR) + -rm $(EXEC) + diff --git a/host/main.cpp b/host/main.cpp new file mode 100644 index 0000000..f88f61b --- /dev/null +++ b/host/main.cpp @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include "argparse/argparse.hpp" + +#define NO_ARDUINO +#include "../arduino/common.hpp" + +#include + +static bool stopRequested = false; + + +// TODO: remove when c++23 is used +namespace std { + template + inline void println(const std::format_string fmt, Args&&... args) { + std::cout << std::vformat(fmt.get(), std::make_format_args(args...)) << std::endl; + } +} + +/** + * @brief Wrapper for numbers, so that the default get from argparse isnt used for ints, since it does not handle 0x and 0b prefixes + */ +/* template */ +/* class NumberWrapper { */ +/* public: */ +/* NumberWrapper() : t(0) {}; */ +/* NumberWrapper(const std::string& s) { */ +/* if (s.size() >= 2) { */ +/* if (s.at(1) == 'x') { */ +/* t = gz::fromHexString(s); */ +/* return; */ +/* } */ +/* else if (s.at(1) == 'b') { */ +/* t = gz::fromBinString(s); */ +/* return; */ +/* } */ +/* else if (s.at(1) == 'o') { */ +/* t = gz::fromOctString(s); */ +/* return; */ +/* } */ +/* } */ +/* t = gz::fromString(s); */ +/* } */ +/* operator T() const { return t; } */ +/* private: */ +/* T t; */ +/* }; */ +/* // overload the argparse::get, which is used to convert the strings */ +/* namespace argparse { */ +/* template */ +/* inline NumberWrapper get(const std::string& v) { return NumberWrapper(v); }; */ +/* } */ + + + +// formater for ControlBytes enum +template<> +struct std::formatter : std::formatter { + template + auto format(ControlBytes c, FormatContext& fc) const { + return std::formatter::format(ControlBytesString(c), fc); + } +}; + + +class ArduinoException : public std::exception { + public: + ArduinoException(const std::string& message) : message(std::format("Unexpected Arduino behaviour: '{}'", message)) {} + virtual const char* what() const noexcept { + return message.c_str(); + } + private: + const std::string message; +}; +class ConnectionException : public std::exception { + public: + ConnectionException(const std::string& message) : message(std::format("Connection error: '{}'", message)) {} + virtual const char* what() const noexcept { + return message.c_str(); + } + private: + const std::string message; +}; + + +/** + * @brief Read data from a file and print it + */ +void signalHandler(int signal) { + std::println("Caught signal {}, exiting.", signal); + stopRequested = true; +} + + +/** + * @brief Wait for a response from the Arduino + * @details + * The response must always start with a ControlByte. + * If the ControlByte is invalid, more bytes are tried. + * If the ControlByte is PRINT, the message following the byte is printed and PRINT is returned. + * Else returnes the ControlByte + */ +ControlBytes waitArduino(serial::Serial& s, const std::string& fname) { + uint8_t ctrl; + while (!stopRequested) { + if (s.read(&ctrl, 1) == 0) + throw ConnectionException(std::format("{}: did not receive answer from Arduino", fname)); + /* std::println("waitArduino: Received ctrl: {}", static_cast(ctrl)); */ + switch (ctrl) { + case ControlBytes::PRINT: { + std::println("Arduino: {}", s.readline()); + return ControlBytes::PRINT; + } + case ControlBytes::READ: + case ControlBytes::WRITE: + case ControlBytes::READY: + case ControlBytes::MEM_256KB: + case ControlBytes::MEM_2M: + case ControlBytes::SET_ADDRESS: { + return static_cast(ctrl); + break; + } + default: { + std::println("waitArduino: Received invalid ControlByte: '{}'", ctrl); + return ControlBytes::MAX_ENUM; + } + } + } + return ControlBytes::MAX_ENUM; +} + + +void sendControlByte(serial::Serial& s, ControlBytes ctrl, const std::string& fname) { + uint8_t ctrl8 = static_cast(ctrl); + if (s.write(&ctrl8, 1) != 1) { + throw ConnectionException(std::format("{}: could not send {}", fname, ControlBytesString(ctrl))); + } +} + + +void receiveControlByte(serial::Serial& s, ControlBytes ctrl, const std::string& fname) { + ControlBytes receivedCtrl = waitArduino(s, fname); + if (receivedCtrl != ctrl) { + std::string err = std::format("{}: did not receive {}", fname, ControlBytesString(ctrl)); + // SerialException saves the string, no ptr problems here + throw ConnectionException(err.data()); + } +} + +/** + * @brief Send a ready? command + * @details + * 1) Send `READY` + * 2) Receive `READY` or throw ArduinoException + */ +void getReady(serial::Serial& s) { + sendControlByte(s, ControlBytes::READY, "getReady"); + receiveControlByte(s, ControlBytes::READY, "getReady"); +} + + +/** + * @brief Send a write command and buffer + * @details + * 1) Send `WRITE` - `buffer size` - `buffer` - `WRITE` + * 2) Receive `WRITE` or throw ArduinoException + */ +void write(serial::Serial& s, std::vector buffer) { + sendControlByte(s, ControlBytes::WRITE, "write(1)"); + uint8_t cmd; + for (unsigned i = 0; i < sizeof(buffer_t); i++) { + cmd = (buffer.size() >> i * 8); + if (s.write(&cmd, 1) != 1) + throw ConnectionException("write: Could not send buffer size"); + } + if (s.write(buffer) != buffer.size()) + throw ConnectionException("write: Could not send buffer"); + sendControlByte(s, ControlBytes::WRITE, "write(2)"); + receiveControlByte(s, ControlBytes::WRITE, "write"); +} + + +/** + * @brief Send a read command and receive the buffer + * @details + * 1) Send `READ` - `buffer size` - `READ` + * 2) Receive: `READ` - `buffer size` - `buffer` - `READ` or throw ArduinoException + */ +void read(serial::Serial& s, std::vector& buffer, buffer_t bufferSize) { + sendControlByte(s, ControlBytes::READ, "read(1)"); + uint8_t cmd; + std::println("read: bufferSize={}", bufferSize); + for (unsigned i = 0; i < sizeof(buffer_t); i++) { + cmd = (bufferSize >> i * 8); + /* std::println("read: sending={}", cmd); */ + if (s.write(&cmd, 1) != 1) + throw ConnectionException("read: Could not send buffer size"); + } + sendControlByte(s, ControlBytes::READ, "read(2)"); + + receiveControlByte(s, ControlBytes::READ, "read(1)"); + buffer_t announcedBufferSize = 0; + for (unsigned i = 0; i < sizeof(buffer_t); i++) { + if (s.read(&cmd, 1) == 0) + throw ArduinoException(std::format("read: Could not receive buffer size")); + /* std::println("read: received={}", cmd); */ + announcedBufferSize |= (cmd << i * 8); + } + if (announcedBufferSize != bufferSize) + throw ArduinoException(std::format("read: bufferSize={:#0x}, but announcedBufferSize={:#0x}", bufferSize, announcedBufferSize)); + + buffer.clear(); + size_t receivedBufferSize = s.read(buffer, bufferSize); + if (receivedBufferSize != bufferSize) + throw ArduinoException(std::format("read: bufferSize={:#0x}, but receivedBufferSize={:#0x}", bufferSize, receivedBufferSize)); + std::println("read: Received buffer size {}", receivedBufferSize); + receiveControlByte(s, ControlBytes::READ, "read(2)"); +} + + +void validate(const std::vector& correctData, const std::vector& readData, address_t startAddress, bool fileStartsFromZero=false) { + size_t correctDataSize = fileStartsFromZero ? correctData.size() - startAddress : correctData.size(); + unsigned errors = 0; + if (correctDataSize != readData.size()) { + std::println("validate: Buffers have different sizes. correctData.size={}, readData.size={}", correctDataSize, readData.size()); + return; + } + for (unsigned i = 0; i < correctData.size(); i++) { + uint8_t correct = correctData.at(i); + uint8_t actual = readData.at(i); + if (correct != actual) { + std::println("validate: address=[0x{:04x} 0b{:015b}]: correct={{0x{:02x} 0b{:08b}}} - actual={{0x{:02x} 0b{:08b}}}", startAddress+i, startAddress+i, correct, correct, actual, actual); + errors++; + } + } + std::println("validate: {: 4} mismatches found", errors); +} + + +void readFile(const std::string& path, std::vector& buffer) { + std::ifstream file(path, std::ios::binary); + + // TODO: check if required + // Stop eating new lines in binary mode!!! + file.unsetf(std::ios::skipws); + + std::streampos fileSize; + file.seekg(0, std::ios::end); + fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + buffer.clear(); + buffer.reserve(fileSize); + // read the data: + buffer.insert(buffer.begin(), std::istream_iterator(file), std::istream_iterator()); +} + + +struct Arguments : public argparse::Args { + std::string& filename = kwarg("f,file", "path to a binary file"); + bool& verbose = flag("verbose", "Also print successful operations"); + /* bool& write = flag("w,write", "Write data"); */ + /* bool& read = flag("r,read", "Read data"); */ + // timeout needs to be high enough for the entire write cycle + unsigned& timeout = kwarg("timeout", "Timeout in ms").set_default(5000); + std::string& device = kwarg("device", "Path to the serial device (Arduino)").set_default("/dev/ttyACM0"); + /* virtual void welcome() { */ + /* std::println("EEEPROM-programmer"); */ + /* } */ +}; + +namespace fs = std::filesystem; + +int main(int argc, const char** argv) { + auto args = argparse::parse(argc, argv); + if (!fs::exists(fs::path(args.device))) { + std::println("Error: device '{}' not found", args.device); + return 1; + } + + if (!fs::exists(fs::path(args.device))) { + std::println("Error: device '{}' not found", args.device); + return 1; + } + + if (!fs::exists(fs::path(args.filename))) { + std::println("Error: file '{}' not found", args.filename); + return 1; + } + + // read file + std::vector binFromFile{}; + if (!args.filename.empty()) { + readFile(args.filename, binFromFile); + } + + const uint32_t baud_rate = 9600; + serial::Serial s(args.device, baud_rate); + auto timeout = serial::Timeout::simpleTimeout(args.timeout); + s.setTimeout(timeout); + s.flush(); + + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + try { + getReady(s); + write(s, binFromFile); + } + catch (const ArduinoException& e) { + std::println("ArduinoException: {}", e.what()); + for (const auto& s : s.readlines()) { + std::println("From Arduino: '{}'", s); + } + } + + + s.close(); +} diff --git a/host/print.bin b/host/print.bin new file mode 100644 index 0000000..eceff75 Binary files /dev/null and b/host/print.bin differ diff --git a/host/spi.bin b/host/spi.bin new file mode 100644 index 0000000..8d079c3 Binary files /dev/null and b/host/spi.bin differ diff --git a/sketch.json b/sketch.json deleted file mode 100755 index 45332f0..0000000 --- a/sketch.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cpu": { - "fqbn": "arduino:mbed_nano:nanorp2040connect", - "name": "Arduino Nano RP2040 Connect", - "port": "serial:///dev/ttyACM0" - } -} \ No newline at end of file