diff --git a/Arduino/ReceiveCart/ReceiveCart.ino b/Arduino/ReceiveCart/ReceiveCart.ino new file mode 100644 index 0000000..0d946a3 --- /dev/null +++ b/Arduino/ReceiveCart/ReceiveCart.ino @@ -0,0 +1,118 @@ +#include +#include + +static const int eeprom_addr = 0b1010000; + +void write_addr(uint16_t addr) { + Wire.write((uint8_t)(addr >> 8)); + Wire.write((uint8_t)addr); +} + +void write_page(uint8_t page[128], uint16_t page_addr) { + uint8_t bytes_written = 0; + for (int i = 0; i < 5; i++) { + int ret; + int write_bytes = min(128 - bytes_written, 30); + do { + Wire.beginTransmission(eeprom_addr); + write_addr(page_addr + bytes_written); + Wire.write(page + bytes_written, write_bytes); + ret = Wire.endTransmission(); + delay(5); + } while (ret != 0); + bytes_written += write_bytes; + } +} + +bool check_sumcomplement(uint8_t data[128], uint16_t checksum) { + uint16_t sum = 0; + for (int i = 0; i<128; i += 2) { + sum += (data[i+1] << 8) | data[i]; + } + sum += checksum; + return !sum; +} + +volatile uint8_t pageData[128] = {}; +volatile uint16_t pageChecksum = 0; +volatile uint16_t pageAddr = 0; +volatile bool pageOk = false; +volatile bool reicivingCart = false; +volatile uint32_t last_page_update = 0; + +void setup() { + // put your setup code here, to run once: + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); + Wire.begin(); + //Serial.println("Arduino started"); +} + +void loop() { + if (reicivingCart && millis() - last_page_update > 1000) { + Serial.print("TO"); + reicivingCart = false; + pageOk = false; + pageAddr = 0; + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + } + + if (pageOk) { + pageOk = false; + bool pageValid = check_sumcomplement((uint8_t *)pageData, pageChecksum); + Serial.print(pageValid ? "ACK" : "NAK"); + if (pageValid) { + write_page(pageData, pageAddr); + pageAddr += 128; + } + + /* for (int i = 0; i<128; i++) { + if (i%16 == 0) + Serial.println(); + Serial.print(pageData[i], HEX); + Serial.print(" "); + } + Serial.println(); + Serial.println(pageChecksum, HEX);*/ + + Serial.print("OK"); + } +} + +void serialEvent() { + static size_t read = 0; + digitalWrite(LED_BUILTIN, HIGH); + if (Serial.available() == 5) { + char control[4] = {}; + Serial.readBytesUntil('\n', control, 4); + Serial.read(); /* Drop the '\n' */ + if (!strncmp(control, "CART", 4)) { + reicivingCart = true; + Serial.print("OK"); + } else if (!strncmp(control, "DONE", 4)) { + reicivingCart = false; + } else { + Serial.print("KO"); + } + pageOk = false; + pageAddr = 0; + read = 0; + } + + while (reicivingCart && Serial.available() && read < 128) { + read += Serial.readBytes((char *)pageData+read, min(128-read, 64)); + } + if (reicivingCart && read >= 128) { + pageOk = true; + Serial.readBytes((char*)&pageChecksum, 2); + read = 0; + } + digitalWrite(LED_BUILTIN, LOW); + last_page_update = millis(); +} diff --git a/Arduino/SendCart/SendCart.ino b/Arduino/SendCart/SendCart.ino new file mode 100644 index 0000000..a98aa6d --- /dev/null +++ b/Arduino/SendCart/SendCart.ino @@ -0,0 +1,71 @@ +#include +#include + +static const int eeprom_addr = 0b1010000; +uint8_t pageData[128] = {}; +uint16_t pageAddr = 0; + +void write_addr(uint16_t addr) { + Wire.write((uint8_t)(addr >> 8)); + Wire.write((uint8_t)addr); +} + +uint16_t sum_complement(const uint8_t data[128]) { + uint16_t sum = 0; + for (int i = 0; i<128; i += 2) { + sum += (data[i+1] << 8) | data[i]; + } + return -sum; +} + +void setup() { + + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); + Wire.begin(); +} + +void loop() { + char ret[4] = ""; + + // put your main code here, to run repeatedly: + do { + if (!Serial.readBytesUntil('\n', ret, 2)) + Serial.read(); + } while (strncmp(ret, "GO", 2) != 0); + + // Reset the EEPROM internal address to 0. It autoincrements after. + Wire.beginTransmission(eeprom_addr); + write_addr(0); + Wire.endTransmission(false); + + digitalWrite(LED_BUILTIN, HIGH); + /* + * We don't know the size of the cartridge, and it could be the whole EEPROM. + * Read and send all the pages (64kiB), it should not impact the final PNG. + */ + for (int i = 0; i < 512; i++) { + memset(pageData, 0, 128); + // Arduino I2C buffer is 32 bytes, so we need multiple requests per page. + for (int j = 0; j < 4; j++) { + Wire.requestFrom(eeprom_addr, 32); + for (int k = 0; k < 32; k++) { + pageData[32*j + k] = Wire.read(); + } + } + // Try to send the full page until the recipient ACKs + uint16_t page_checksum = sum_complement(pageData); + do { + memset(ret, 0, 4); + size_t written = 0; + while (written < 128) { + written += Serial.write(pageData + written, min(128 - written, 64)); + } + Serial.write((uint8_t*)&page_checksum, 2); + while(!Serial.readBytesUntil('\n', ret, 3)); + Serial.read(); + } while(strncmp(ret, "ACK", 3) != 0); + } + + digitalWrite(LED_BUILTIN, LOW); +} diff --git a/Assets/Scripts/Arduino_Com.cs b/Assets/Scripts/Arduino_Com.cs new file mode 100644 index 0000000..c5f1bad --- /dev/null +++ b/Assets/Scripts/Arduino_Com.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Ports; +using System.Linq; +using UnityEngine; +using UnityEngine.UIElements; + +// Handle the serial communication with the Arduino, +// the cartridge transfer and related UI interactions. +public class Arduino_Com : MonoBehaviour +{ + private enum ArduinoState + { + Déconnecté, + Connecté, + Lecture, + Écriture, + Invalide + } + + // ======== UI ======== + [SerializeField] private UIDocument ui; + private DropdownField _arduinoSerialPort; + private EnumField _arduinoState; + private Button _arduinoControlButton; + private ProgressBar _arduinoProgress; + + private Button _cartLoadButton; + private Button _cartWriteButton; + private VisualElement _cartImage; + + // ======== IO ======== + private SerialPort _serial = new (); + // Probably should be left alone but give the possibility to change it in the inspector. + [SerializeField] private int baudrate = 115200; + private string _activeSerialPort; + private ArduinoState _state = ArduinoState.Déconnecté; + + private string _cartPath; + private FileStream _cart; + + // ======== Cart transfer ======== + // Page data corresponding to the EEPROM. + private const int PageSize = 128; + private const int PageCount = 512; + // Page to be written out. + private Byte[] _pageBuffer = new byte[PageSize]; + // Current position in _pageBuffer. + private int _readBytes; + private int _currentPage; + + // Checksum a 128 bytes page of data by summing it into an unsigned 16 bits int, + // to be used either as a verification with a received checksum or to negate and + // send as the checksum. + private static UInt16 ComputeChecksum(Byte[] data) + { + if (data.Length != PageSize) + { + throw new InvalidDataException("Checksum only accepts "+PageSize+" bytes arrays !"); + } + + UInt16 sum = 0; + for (var i = 0; i < PageSize; i += 2) + { + sum += BitConverter.ToUInt16(data, i); + } + return sum; + } + + private IEnumerator ReceiveSerialData() + { + do + { + yield return new WaitUntil(() => _serial.BytesToRead >= 1); + // Read all available data, up to the PageSize. + _readBytes += _serial.Read(_pageBuffer, _readBytes, PageSize - _readBytes); + + // Full page received, check checksum and ACK/NAK, write received page. + if (_readBytes >= 128) + { + Byte[] checksum = new byte[2]; + // There might be less than the two characters of the checksum available, loop until complete. + var checksumRead = 0; + do + { + checksumRead += _serial.Read(checksum, checksumRead, 2-checksumRead); + } while (checksumRead < 2); + + // Implicit type of the sum is 4 bytes, so the overflow doesn't occur. Compute manually. + if (65536 - (BitConverter.ToUInt16(checksum) + ComputeChecksum(_pageBuffer)) == 0) + { + // Checksum valid, write it out to the cart. + _cart.Write(_pageBuffer); + _currentPage += 1; + _serial.Write("ACK\n"); + } + else + { + // Checksum invalid, ask for retransmit. + _serial.Write("NAK\n"); + Debug.LogWarningFormat("Page {0} reception failed, asking for retransmit.", _currentPage); + } + _readBytes = 0; + } + } while (_currentPage < PageCount); + // We are done receiving the cart, we don't expect more data to be received so the coroutine can return. + _currentPage = 0; + _state = ArduinoState.Connecté; + _cart.Close(); + + // Re-enable buttons now that the transfer is done. + _arduinoControlButton.SetEnabled(true); + _cartLoadButton.SetEnabled(true); + // _cartWriteButton.SetEnabled(true); TODO: Not implemented + + _arduinoProgress.visible = false; + + // Update cart image ! + var newCartTexture = new Texture2D(2, 2); + var textureData = File.ReadAllBytes(_cartPath); + newCartTexture.LoadImage(textureData); + _cartImage.style.backgroundImage = new StyleBackground(newCartTexture); + } + + void Start() + { + // ======== UI ======== + var uiRoot = ui.rootVisualElement; + _arduinoSerialPort = uiRoot.Query("Arduino-Serial"); + _arduinoSerialPort.RegisterValueChangedCallback(OnNewSerialPort); + _arduinoSerialPort.choices = new List(SerialPort.GetPortNames()); + + _arduinoState = uiRoot.Query("Arduino-State"); + + _arduinoControlButton = uiRoot.Query