//#define CMD_SAVE_IMAGE 0xA0 #define CMD_IMG_META_BEGIN 0xA0 #define CMD_IMG_META_CHUNK 0xA1 #define CMD_IMG_META_SAVE 0xA2 #define CMD_IMG_DATA_BEGIN 0xA3 #define CMD_IMG_DATA_CHUNK 0xA4 #define CMD_IMG_DATA_SAVE 0xA5 #define CMD_CHIP_META_BEGIN 0xA6 #define CMD_CHIP_META_CHUNK 0xA7 #define CMD_CHIP_META_SAVE 0xA8 #define CMD_CHIP_CODE_BEGIN 0xA9 #define CMD_CHIP_CODE_CHUNK 0xAA #define CMD_CHIP_CODE_SAVE 0xAB #define CMD_CHIP_STR_BEGIN 0xAC #define CMD_CHIP_STR_CHUNK 0xAD #define CMD_CHIP_STR_SAVE 0xAE #define CMD_UPLOAD_MODE_CHECK 0xAF #define CMD_SEND_IMG_COUNT 0xD0 #define CMD_ERASE_IMG_ALL 0xD1 #define CMD_SEND_CHIP_COUNT 0xD2 #define CMD_ERASE_CHIP_ALL 0xD3 #define CMD_SEND_IMG_NAMES 0xD4 #define CMD_SEND_CHIP_NAME 0xD5 #define CMD_SEND_CHIP_META 0xD6 #define CMD_SEND_CHIP_CODE 0xD7 #define CMD_SEND_CHIP_STRINGS 0xD8 #define CMD_CHIP_STRINGS_END 0xD9 #define CMD_DELETE_CHIP 0xDA #define CMD_GET_VERSION 0xDB #define CMD_FONT_META_BEGIN 0xC0 #define CMD_FONT_META_CHUNK 0xC1 #define CMD_FONT_META_SAVE 0xC2 #define CMD_FONT_DATA_BEGIN 0xC3 #define CMD_FONT_DATA_CHUNK 0xC4 #define CMD_FONT_DATA_SAVE 0xC5 #define CMD_SAVE_CHIP_GUI 0xC6 #define CMD_GET_CHIPS_GUI 0xC7 #define CMD_CHIP_COUNT_GUI 0xC8 #define CMD_VER 0x02 #define CMD_TEXT 0x03 #define CMD_LINK_SYNC 0x04 #define RESP_UPLOAD_MODE_OK 0x05 #define RESP_UPLOAD_MODE_BAD 0x06 #define RESP_ACK 0xB0 #define RESP_RETRY 0xB1 #define RESP_DONE 0xB2 #define RESP_FAIL 0xB3 #define RESP_BAD_SIZE 0xB4 #define RESP_STILL_ERASING 0xB5 #define CMD_CHANGE_SCREEN 0xB6 #define RESP_IMG_COUNT 0xB7 #define RESP_IMG_EMPTY 0xB8 #define RESP_CHIP_COUNT 0xB9 #define RESP_CHIP_NAME 0xBA #define RESP_IMAGE_NAME 0xBB #define RESP_CHIP_META 0xBC #define RESP_CHIP_CODE 0xBD #define RESP_CHIP_STRINGS 0xBE #define RESP_VERSION 0xBF #define CMD_META_USB_TEST 0xCA #define CMD_LOAD_USB_TEST 0xCB #define CMD_TEST_LENGTH 0xCD #define CMD_RUN_USB_TEST 0xCC #define CMD_SAVE_CHIP_BATCH 0xCE #define CMD_SAVE_BATCH_DONE 0xCF #define CHUNK_SIZE 63 #define MAX_ACK_LEN 60 #define MAX_PAYLOAD 57 //#define STRING_SIZE 35 extern uint8_t upload_mode_active; void ImgMetaSave(uint8_t seq); void GetAddrForImg(); void GetFreeSlot(); void USB_CMD_ProcessRx(void); void DisplayChipStrings(uint16_t chip_index); void EraseEeprom(); extern ChipMetadata temp_meta; extern uint8_t test_buffer[4096]; extern uint16_t test_buffer_len; void FanOn(uint8_t on); Works via serial CDC each packet must be sent in the following manner: [0] CMD (1 byte) [1] SEQ (1 byte) [2..] PAYLOAD (0..N bytes) [last] CRC8 (1 byte) CMD is the command you are sending to Vetus. SEQ is just a sequence number to help make sure each command is being done as part of the proper request. You can use any or the same number all of the time here but I recommend using an increasing number. PAYLOAD is the data we are sending with the command, this can be null and should be null when sending a command only... a null byte array. CRC8 is our validity check. It will only match on both sides if the data was delivered exactly as expected. SendCOBSPacket already does all of this but you may change it to suit your needs. Please note that none of these algorithms can be changed or they will not match what Vetus Pars expects. You cannot skip COBS encoding. Vetus expects that as well. Packets should be sent in exactly the way SendCOBSPacket sends it though you are free to change how it does that. private static int COBS_Decode(byte[] input, int length, byte[] output) { int readIndex = 0; int writeIndex = 0; while (readIndex < length) { byte code = input[readIndex++]; if (code == 0 || readIndex + code - 1 > length) { // Invalid frame (code byte zero or goes past end) return -1; } for (int i = 1; i < code; i++) { output[writeIndex++] = input[readIndex++]; } if (code != 0xFF && readIndex < length) { output[writeIndex++] = 0x00; } } return writeIndex; // total decoded bytes } private byte ComputeCRC8(byte[] data, int length) { byte crc = 0x00; for (int i = 0; i < length; i++) { crc ^= data[i]; // byte XOR for (int j = 0; j < 8; j++) { if ((crc & 0x80) != 0) crc = (byte)(((crc << 1) ^ 0x07) & 0xFF); else crc = (byte)((crc << 1) & 0xFF); } } return crc; } private static int COBS_Encode(byte[] input, int length, byte[] output) { int readIndex = 0; int writeIndex = 1; int codeIndex = 0; byte code = 1; while (readIndex < length) { if (input[readIndex] == 0) { output[codeIndex] = code; codeIndex = writeIndex++; code = 1; readIndex++; } else { output[writeIndex++] = input[readIndex++]; code++; if (code == 0xFF) { output[codeIndex] = code; codeIndex = writeIndex++; code = 1; } } } output[codeIndex] = code; output[writeIndex++] = 0x00; return writeIndex; // total bytes written } public byte SendCOBSPacket(byte seq, byte cmd, byte[] payload) { if (!EnsureConnected()){ ShowModal("Could not open COM port (Access Denied / port in use)."); return 0; } if (payload == null) payload = Array.Empty(); //--------------------------------------------- // Build RAW frame (CMD + SEQ + payload + CRC) //--------------------------------------------- int rawLen = 2 + payload.Length + 1; byte[] raw = new byte[rawLen]; int p = 0; raw[p++] = cmd; raw[p++] = seq; Array.Copy(payload, 0, raw, p, payload.Length); p += payload.Length; byte crc = (byte)ComputeCRC8(raw, p); raw[p++] = crc; //--------------------------------------------- // Encode COBS //--------------------------------------------- byte[] enc = new byte[rawLen + 4]; int encLen = COBS_Encode(raw, rawLen, enc); serial.Write(enc, 0, encLen); return seq; } private byte[] ReadCOBSFrame() { List buf = new List(); while (true) { int b; try { b = serial.ReadByte(); // blocks until one byte arrives or timeout } catch { throw new TimeoutException("Serial timeout reading byte."); } if (b < 0) throw new TimeoutException("Serial returned negative byte."); if (b == 0x00) break; // end of frame buf.Add((byte)b); } if (buf.Count == 0) throw new Exception("Empty COBS frame."); byte[] encoded = buf.ToArray(); byte[] decoded = new byte[encoded.Length + 1]; int len = COBS_Decode(encoded, encoded.Length, decoded); if (len <= 0) throw new Exception("Invalid COBS decode"); byte[] frame = new byte[len]; Array.Copy(decoded, frame, len); return frame; } Check Upload Mode CMD_UPLOAD_MODE_CHECK: Sending this command to Vetus tells vetus to respond with either RESP_UPLOAD_MODE_OK (0x05) or RESP_UPLOAD_MODE_BAD (0x06) based on whether or not Vetus is on the USB screen. This helps keep the system resources low while uploading and saving to Vetus Pars. In VetusGUI this is purposely only used for chips as there could possibly be a time that someone for some reason might need to compleetly wipe the system and need to install fonts with nothing on the display making getting to upload mode a bit more difficult. Upload Chip: Sending this command to Vetus Pars will upload the chip info and test code to test buffer of Vetus Pars. There the data stays until the upload screen is left or the data is saved. the following order must be followed: 41 bytes total in the packet. bytes[0-1] 2 - The packet must start with 0x5A 0xA5 for the first two bytes. Then, the ascii HEX code for the name bytes[2-33] 32 - This is for the chip name, ascii hex should be used for the characters and all 32 bytes must be sent meaning empty bytes if needed. bytes[34-35] - 16 bit integer for type. byte[36] - Number of pins the chip has byte[37] - Index of the image to use in info section. You can see the image order in settings/images Image 0 is logo and so on. bytes[38-39] - Code size in bytes. Each line has 1 32bit Opcode and 3 32bit arguments, regardless if the opcode actually takes an argument or not. Therefore, each line is 16 bytes and the max bytes per test is 4096. (256 max opcodes) x (4 x 32bit words) = 4096 byte[40] - Reserved. Use 0x0 Once you have collected the data for the packet you send CMD_META_USB_TEST as a command with the data attached using SendCOBSPacket(). After Vetus has completed the command it will send back the RESP_DONE code. If it fails it will send RESP_FAIL. 8 strings × 64 bytes each = 512 bytes • ASCII • Null-terminated within each 64-byte slot (or at least zero-filled) • Slot offsets: slot i starts at i*64 After sending the meta data you should send the strings. Keep each chunk at 60 or under. MAX_PAYLOAD = 60 TOTAL_BYTES = 512 -> each string can be up to 64 characters including formatting characters. You have 8 strings total. The strings should be added to a buffer with their full bytes, any unused bytes should be zero. You start by sending CMD_CHIP_STR_BEGIN by itself and you should get the RESP_DONE reply. You then send each chunk of strings using CMD_CHIP_STR_CHUNK + chunk. After each chunk, Vetus will respond with RESP_DONE or RESP_FAIL You should then send CMD_CHIP_STRINGS_END to tell Vetus you are done sending strings and it will respond with either RESP_DONE or RESP_FAIL. Once that is has been received you send the code upload command with the code size. CMD_TEST_LENGTH + CodeSize; and Vetus should respond with RESP_DONE or RESP_FAIL. After Vetus ackowledges it has the test size, you can now send packets of code. each packet should be 60 bytes or less. Therefore you must send the code one 60 byte chunk at a time using CMD_LOAD_USB_TEST + chunk After each chunk Vetus will respond with RESP_DONE. The GUI keeps track of how much data was sent. Once done, Vetus will switch to the Uploaded Test Pre-Test Screen. This is when code should either be run by Vetus Pars or saved. Again, if any part fails you will get back a RESP_FAIL response. Your chip is now in Limbo in a buffer in Vetus Pars. If you do nothing and leave the screen or turn off the system then the buffer will be cleared and the chip will not be saved. While it is on the Pre-Test screen you can either save the chip or run the test. As you know, you run the test by clicking OK on Vetus Pars, but to save the chip, you send the save command from the UI. Vetus will then use of the info in the buffer to save the chip. Saving Saving is a simple command. Just send CMD_SAVE_CHIP_GUI and it will save what is in the buffer. It will respond with RESP_DONE or RESP_FAIL. Special Syntax: DIMs, Functions and Registers. DIMS - These are just variable declarations. They do not take code space and they are simply a function of the GUI. In VetusGUI these just act as tokens that are replaced with the value they represent when uploading to Vetus. So Vetus gets the actual value. I'm sure that goes without saying for most of you but there it is. Registers: In some instances, the register index is written as 0x800000nn. Meaning they are numbers that will never be used therefore we can use that for storing the index value of the register. There are up to 32 registers therefore 0x80000000 - 0x80000031. They are setup in Vetus like so. #define ARG_REG_FLAG 0x80000000 #define ARG_VALUE_MASK 0x7FFFFFFF #define ARG_REG(n) (ARG_REG_FLAG | (n)) these definitions allow the firmware to take a number of 0x80000000 or more and turn it into a basic index number from 0 to 32. Theredfore, when Vetus wants to directly affect the value of a register it can use 0x800000nn to differentiate an index value from a regular value as some parameters might be able to use either a value or a register value. And in VetusGUI (Unity C#) - we turn any #Rn into the number Vetus expects. if (token.StartsWith("#R", StringComparison.OrdinalIgnoreCase)) { if (int.TryParse(token.Substring(2), out int reg)) return 0x80000000u | (uint)reg; return 0u; } Remember that when we want a value from the register we use the #R (the 0x80000000 value) and when you are doing something to a register like increasing, adding, anything other than getting its value you use a normal index number 0x01 for example. That is a requirement of Vetus. Anything you want to do to make that work is up to you. In short, #R is not really a requirement for Vetus, it is just a nice way to send 0x8000000n to Vetus Pars from VetusGUI. You are free to use any tokens you'd like to represent and convert into a register index (0x80000000 or more) for getting register values as long as you use 0x80000000 and 0x0 in the correct circumstances. Once again that is, if you do anything other than get the value of a register then you need to use a regular index number for the register ie. 0x0 if you want to get the value of the register then Vetus needs the index written as 0x80000000 Functions: When Vetus stores a function name you only need to send the Opcode for function (0xC0) and the other three 32bit values allow you to store up to 12 ascii characters for the function name. ENDFUNC is there simply to tell Vetus to end the function and return to the line after the function was called. How does it work in VetusGUI? In VetusGUI, when parsing and translating the data we check for which line the function code lives. Then, wherever we see CALL FUNC_NAME, we replace that FUNC_NAME with the line number where the function code starts. So, when we send code to Vetus Pars that has a function, the line that holds the function has the name of the function and each line after that is processed when that function is called until it hits ENDFUNC. When it hits end func it goes back to the line after the line that called the function. Sending Function Code to Vetus: VetusGUI --> FUNC MyFunc gets sent to Vetus as 0C 4D 79 46 75 6E 63. CALL however, needs to get sent with CALL (0xC3) and the line number where the function you want to call is located on. So, when you send CALL from within the code, you need to know which line number the function code starts on because the code will jump to that number any time that function is called. When VetusGUI sees the FUNC opcode it knows to use the next three 32bit words as acii for the function name. That is what gets saved to Vetus and that is what gets used. It then looks for the line that is stored in the first parameter of the OpCode and gets the name from that line and replaces the number with the name. What really matters is that, the name gets saved with the FUNC opcode, and CALL MUST have its first parameter as the line where its related FUNC is located. Ex: line no. Text in Editor Translated to what should be sent to Vetus -------- -------------- ------------------------------------------ 22 CALL FUNC1 000000C3 00000018 00000000 00000000 (32 bits each) function is stored at 0x18 (24) 23 ENDTEST 0x0 0x0 0x0 00000004 00000000 00000000 00000000 24 FUNC FUNC1 000000C0 46554E43 01000000 00000000 (0xC0 = FUNC, 46 55 4E 43 01 = FUNC1) 25 LEDON 0x0 0x0 0x0 0000006C 00000000 00000000 00000000 26 ENDFUNC 000000C2 00000000 00000000 00000000 Of course, you don't need to write the numbers out like that. I'm just making sure you know that each part is 32 bits. even the Opcode. Batch uploading uses the same functions as a single upload. Delete All Chips - This is mostly a debug feature but it could be useful if something gets corrupted. all you need to do is -> SendCOBSPacket(seq, CMD_ERASE_CHIP_ALL, Array.Empty()) it will respond with RESP_DONE or RESP_FAIL To delete a single chip we send CMD_DELETE_CHIP + the index. SendCOBSPacket(seq, CMD_DELETE_CHIP, new byte[]{(byte)(index & 0xFF), (byte)(index >> 8)}); it will responde with RESP_DONE or RESP_FAIL. Chip Count This one is fairly straight forward. You send the command to vetus and it will send the count back. SendCOBSPacket(seq, CMD_CHIP_COUNT_GUI, Array.Empty()); It weill respond with a COBS packet that contains the command and sequence numbers on the first two bytes and then the next two bytes are the chip count. Part of the C# code as an example: try { frame = ReadCOBSFrame(); } catch { return 0; } if (frame.Length < 3 || frame[0] != RESP_CHIP_COUNT) return 0; int len = frame.Length - 3; // (CRC is still on the last byte it must go too so length is payload // minus cmd, seq, crc) if (len <= 0) return 0; string ascii = Encoding.ASCII.GetString(frame, 2, len).Trim('\0', ' ', '\r', '\n'); //we start over 2 spots to skip the cmd and seq and we only go to the second to last byte // to skip the CRC value if (int.TryParse(ascii, out int count))return count; if (len >= 2)return frame[2] | (frame[3] << 8); return 0; For some reason the CRC check just got ignored in this function. It shouldn't be ignored and this will be fixed in future versions. It works but it isn't realiable. CRC should alawys be checked so we know we got all of the valid data. Load Chip Names Loading chip names should be done in a for a loop if you want them all or they can be gotten individually. using the command CMD_GET_CHIPS_GUI. the index should be in the payload like so. byte[] payload = new byte[]{(byte)(i & 0xFF),(byte)(i >> 8)}; (where i = index) and you can call it like this. SendCOBSPacket(seq, CMD_GET_CHIPS_GUI, payload); it will reapond with a COBS packet containing RESP_CHIP_NAME or RESP_FAIL. After RESP_CHIP_NAME will be the ASCII for whatever you the saved name of the chip is it will possibly need trimmed. Loading Chip info into VetusGUI First we give Vetus the index we want using SendCOBSPacket(seqHeader, CMD_SEND_CHIP_META, new byte[]{(byte)(index & 0xFF), (byte)(index >> 8)}); It should respond with RESP_CHIP_META. It will then be ready to send you bytes via the same command but instead of using the index you should now send 0xAA 0xAA so that Vetus knows to send you the data of the index you just sent with something like -> SendCOBSPacket(seqChunk, CMD_SEND_CHIP_META, new byte[] { 0xAA, 0xAA }); It will respond by sending you the bytes in packets of under 64 bytes until you get all 128 (all 128 bytes will be sent even though only about 40 bytes are real data). These will be in COBS frames. Remember that after CRC and COBS you will still have the RESPONSE CODE and SEQUENCE number as the first two bytes so those must be stripped out before putting data into the buffer you are saving data into. Once you have all of the metadata you can separate it like earlier. bytes[0-31] 32 - This is for the chip name, ascii hex should be used for the characters and all 32 bytes must be sent meaning empty bytes if needed. bytes[32-33] - 16 bit integer for type. byte[34] - Number of pins the chip has byte[35] - Index of the image to use in info section. You can see the image order in settings/images Image 0 is logo and so on. bytes[36-37] - Code size in bytes. Each line has 1 32bit Opcode and 3 32bit arguments, regardless if the opcode actually takes an argument or not. Therefore, each line is 16 bytes and the max bytes per test is 4096. (256 max opcodes) x (4 x 32bit words) = 4096 byte[38] - Reserved. Use 0x0 the rest of the 128 bytes of the struct are 0. You must keep track of the bytes in the payload you receive and when to end. This is why objects of variable size like "code" need to have their size sent first. Fonts Fonts in vetus Pars are a simple bit based format. Each column is one byte written as such 0b11111111. this would make the first column of the letter fully the font forecolor. So a vertical line. 0b00000000 would give you a blank column for that part of the letter. the more columns (bytes) you use for each letter, the wider the letter is. I will attempt to make a capital A with bytes to show how they are formed. 0 0 0 0 0 0 0 0 0 b b b b b b b b b 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 1 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 if you imagine each of those ones and zeros are white and black pixels you can see how the letter is formed. In the font file it would be written out like this. 0b00000000, 0b00000000, 0b00111111, 0b01000100, 0b10000100, 0b01000100, 0b00111111, 0b00000000, 0b00000000 While in the future, when font mking is added, VetusGUI will add sizes and name to the file itself but at the moment it does not. This is just a VetusGUI thing and when uploading to Vetus we need to include the font glyphs dimensions and the total data size bit packages. Let me help that make sense. If the 'A' I just created presents our font, Vetus uses ASCII 32 - 126. Of course, you need to send them to Vetus Pars byte by byte so you will need to have your GUI convert the bits to bytes. The font "code" is sent all as a whole and like all other packets it cannot be more than 63 bytes at a time. Uploading Font Data For font data we do an upload sequence for the font metadata and the font code separately. for metadata we do the following: metaadata for fonts is 128 bytes, you will need to send two time (packets must be 63 bytes or less) Again, you need to send this as a COBSPacket in the following order. bytes [0 -1] 0x5A 0xA5 bytes [2-33] font name (32 bytes max) byte [34] width (number of 0b000... patterns per glyph) byte [35] height (the number 0f bits per byte or 'word' which is a more accurate term) bytes [36-39] data size, this should be the number of glyphs * 0b000.... words per glyph byte [40] the number of glyphs which should always be 94 byte [41] this is just the width of each glyph again this should be in a 128 byte buffer and again, send in packets under 64 bytes which includes the seq, cmd, cobs, and crc, so the actual data that you send is best left at 60 or less. Start Upload first we tell Vetus to prepare for font upload by sending the metadata size which again is always 128 bytes. CMD_FONT_META_BEGIN + 2-byte payload Vetus will respond with RESP_DONE or RESP_FAIL After that we send the 128 bytes of the metadata in chunks. by using CMD_FONT_META_CHUNK + chunk Again, the GUI must keep track of how many bytes have been sent and when to stop. After each chunk send, you should wait for RESP_DONE before sending the next. RESP_FAIL is sent back to the GUI upon failure. Then we can save the meta data via SendCOBSPacket(seq, CMD_FONT_META_SAVE, Array.Empty()); Vetus will respond with RESP_DONE or RESP_FAIL Saving Font Data to send and save the font data we first send the font data size using the CMD_FONT_DATA_BEGIN SendCOBSPacket(seq, CMD_FONT_DATA_BEGIN, BitConverter.GetBytes(dataSize); We should get RESP_DONE or RESP_FAIL in reply. After that you should have already formatted the 0b00000000 values into bytes and now you send them to Vetus. Here are some C# functions that help organize the font data from the text and the Font Upload continues after. public string CleanFontData(string raw) { List cleaned = new List(); string[] lines = raw.Split('\n'); foreach (string l in lines) { string line = l.Trim(); if (line.Length == 0) continue; // Remove comments int commentIndex = line.IndexOf("//"); if (commentIndex >= 0) line = line.Substring(0, commentIndex).Trim(); if (line.Length == 0) continue; // Split by commas string[] parts = line.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (string p in parts) { string part = p.Trim(); if (part.StartsWith("0b")) { // Extract bits only string bits = part.Substring(2); // Must be 8 bits long if (bits.Length == 8) cleaned.Add(bits); } } } // Join each byte on a new line return string.Join("\n", cleaned); } public byte[] BuildFontPixelBytesFrom(string cleanedText) { List bytes = new List(); string[] lines = cleanedText.Split('\n'); foreach (string raw in lines) { string line = raw.Trim(); if (line.Length == 0) continue; if (line.Length != 8) { Debug.LogWarning("Skipping invalid line: " + line); continue; } byte value = 0; for (int i = 0; i < 8; i++) { value <<= 1; if (line[i] == '1') value |= 1; } bytes.Add(value); } return bytes.ToArray(); } That will give the bytes array to send as font data. Once your font data buffer is filled you can send it in chunks of 63 or less including the add-ons needed (thats why I keep data generally to 59 if I can remember). You must keep track of how many bytes you have sent (of just the data itself). And you will send them using SendCOBSPacket(seq, CMD_FONT_DATA_CHUNK, chunk); or a similar function using CMD_FONT_DATA_CHUNK. Keeping track each time of how many bytes you have sent until you have sent all bytes. After each send, Vetus will reply with RESP_DONE or RESP_FAIL After you have sent all of the data bytes you need to let Vetus know you are done and it's ok to save now using, SendCOBSPacket(seq, CMD_FONT_DATA_SAVE, Array.Empty()); Vetus will respond with RESP_DONE or RESP_FAIL Images: Images will be added here once the image system has been finalized on the new VetusGUI.