// STRIX raw capture tool: boots the STRIX FPGA image and gives the host (tools/jitter.py) // register access and raw captures over USB serial. // // Commands (one per line, numbers in decimal): // id print the FPGA identification // r read a register, prints its value // w write a register, prints "OK" // cap start a capture with the current settings and send it as // "DATA \n" followed by n raw bytes #include #include #include "wiring_private.h" // STRIX SPI link: SERCOM0 on its alternate pins A3 MOSI (PAD0), A4 SCK (PAD1), A6 MISO (PAD3). SPIClassSAMD strixSpi(&sercom0, A6, A4, A3, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3); const int STRIX_SS = A5; const SPISettings STRIX_SPI_SETTINGS(8000000, MSBFIRST, SPI_MODE0); enum : uint8_t { REG_ID0 = 0x00, REG_VERSION = 0x04, REG_CONTROL = 0x0B, REG_STATUS = 0x0C, REG_DATA = 0x10, }; const uint8_t READ = 0x80; const uint8_t CONTROL_START_CAPTURE = 0x01; const uint8_t STATUS_CAPTURE_DONE = 0x02; const size_t CAPTURE_BYTES = 32768; const size_t CHUNK_BYTES = 512; VidorUtils fpga; void beginTransaction(uint8_t command) { strixSpi.beginTransaction(STRIX_SPI_SETTINGS); digitalWrite(STRIX_SS, LOW); strixSpi.transfer(command); } void endTransaction() { digitalWrite(STRIX_SS, HIGH); strixSpi.endTransaction(); } void writeRegister(uint8_t reg, uint8_t value) { beginTransaction(reg); strixSpi.transfer(value); endTransaction(); } uint8_t readRegister(uint8_t reg) { beginTransaction(READ | reg); uint8_t value = strixSpi.transfer(0); endTransaction(); return value; } void printId() { char id[5] = {0}; for (uint8_t i = 0; i < 4; i++) id[i] = readRegister(REG_ID0 + i); Serial.print(id); Serial.print(" v"); Serial.println(readRegister(REG_VERSION)); } void capture() { writeRegister(REG_CONTROL, CONTROL_START_CAPTURE); // 262,144 samples at 120 MHz take up to 143 s at the maximum decimation of 65535. unsigned long start = millis(); while (!(readRegister(REG_STATUS) & STATUS_CAPTURE_DONE)) { if (millis() - start > 180000) { Serial.println("ERR timeout"); return; } } Serial.print("DATA "); Serial.println(CAPTURE_BYTES); static uint8_t chunk[CHUNK_BYTES]; for (size_t sent = 0; sent < CAPTURE_BYTES; sent += CHUNK_BYTES) { beginTransaction(READ | REG_DATA); for (size_t i = 0; i < CHUNK_BYTES; i++) chunk[i] = strixSpi.transfer(0); endTransaction(); Serial.write(chunk, CHUNK_BYTES); } } // Parses "" or " " after the command word; returns the number of values found. int parseArgs(const String &args, long &a, long &b) { int space = args.indexOf(' '); if (args.length() == 0) return 0; a = args.substring(0, space < 0 ? args.length() : space).toInt(); if (space < 0) return 1; b = args.substring(space + 1).toInt(); return 2; } void handleCommand(String line) { line.trim(); int space = line.indexOf(' '); String command = space < 0 ? line : line.substring(0, space); String args = space < 0 ? "" : line.substring(space + 1); long reg = 0, value = 0; int count = parseArgs(args, reg, value); if (command == "id") { printId(); } else if (command == "r" && count == 1 && reg >= 0 && reg < 0x80) { Serial.println(readRegister(reg)); } else if (command == "w" && count == 2 && reg >= 0 && reg < 0x80 && value >= 0 && value < 256) { writeRegister(reg, value); Serial.println("OK"); } else if (command == "cap") { capture(); } else if (line.length() > 0) { Serial.println("ERR unknown or malformed command"); } } void setup() { Serial.begin(115200); while (!Serial); // Starts the 48 MHz FPGA clock and switches the FPGA from Arduino's loader to our image. if (!fpga.begin(true)) { Serial.println("ERR FPGA boot failed"); } delay(100); // configuration and PLL lock pinMode(STRIX_SS, OUTPUT); digitalWrite(STRIX_SS, HIGH); strixSpi.begin(); // SPIClassSAMD::begin() applies the pins' default mux; A3-A6 need SERCOM0's alternate mux. pinPeripheral(A3, PIO_SERCOM_ALT); pinPeripheral(A4, PIO_SERCOM_ALT); pinPeripheral(A6, PIO_SERCOM_ALT); printId(); } void loop() { if (Serial.available()) handleCommand(Serial.readStringUntil('\n')); }