initial commit

This commit is contained in:
matthias@arch 2023-12-08 00:11:50 +01:00
parent bedd5f86ef
commit 283076a9ab
11 changed files with 648 additions and 7 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
spi-transfer
*build*
host/argparse

172
arduino/arduino.ino Normal file
View File

@ -0,0 +1,172 @@
#include <SPI.h>
#include "api/Compat.h"
#include "common.hpp"
#include <WiFiNINA.h>
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;
}
}
}

40
arduino/common.hpp Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#ifndef NO_ARDUINO
#include <Arduino.h>
using string_type = String;
#else
#include <cstdint>
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"; }
}
}

View File

@ -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"
}
]

8
arduino/sketch.yaml Normal file
View File

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

3
host/.clangd Normal file
View File

@ -0,0 +1,3 @@
CompileFlags: # Tweak the parse settings
Add: [-std=c++2a, -Wall, -Wextra, -Wpedantic, -Iargparse/include]
# https://clangd.llvm.org/config

57
host/Makefile Normal file
View File

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

330
host/main.cpp Normal file
View File

@ -0,0 +1,330 @@
#include <bits/fs_fwd.h>
#include <charconv>
#include <chrono>
#include <fstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <iostream>
#include <format>
#include <csignal>
#include <filesystem>
#include <serial/serial.h>
#include "argparse/argparse.hpp"
#define NO_ARDUINO
#include "../arduino/common.hpp"
#include <gz-util/string/conversion.hpp>
static bool stopRequested = false;
// TODO: remove when c++23 is used
namespace std {
template <typename... Args>
inline void println(const std::format_string<Args...> 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<std::integral T> */
/* class NumberWrapper { */
/* public: */
/* NumberWrapper() : t(0) {}; */
/* NumberWrapper(const std::string& s) { */
/* if (s.size() >= 2) { */
/* if (s.at(1) == 'x') { */
/* t = gz::fromHexString<T>(s); */
/* return; */
/* } */
/* else if (s.at(1) == 'b') { */
/* t = gz::fromBinString<T>(s); */
/* return; */
/* } */
/* else if (s.at(1) == 'o') { */
/* t = gz::fromOctString<T>(s); */
/* return; */
/* } */
/* } */
/* t = gz::fromString<T>(s); */
/* } */
/* operator T() const { return t; } */
/* private: */
/* T t; */
/* }; */
/* // overload the argparse::get, which is used to convert the strings */
/* namespace argparse { */
/* template<typename T> */
/* inline NumberWrapper<T> get(const std::string& v) { return NumberWrapper<T>(v); }; */
/* } */
// formater for ControlBytes enum
template<>
struct std::formatter<ControlBytes> : std::formatter<std::string> {
template<class FormatContext>
auto format(ControlBytes c, FormatContext& fc) const {
return std::formatter<std::string>::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, <retry> 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<ControlBytes>(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<ControlBytes>(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<uint8_t>(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<uint8_t> 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<uint8_t>& 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<uint8_t>& correctData, const std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>(file), std::istream_iterator<uint8_t>());
}
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<Arguments>(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<uint8_t> 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();
}

BIN
host/print.bin Normal file

Binary file not shown.

BIN
host/spi.bin Normal file

Binary file not shown.

View File

@ -1,7 +0,0 @@
{
"cpu": {
"fqbn": "arduino:mbed_nano:nanorp2040connect",
"name": "Arduino Nano RP2040 Connect",
"port": "serial:///dev/ttyACM0"
}
}