diff --git a/_posts/2022-07-16-restricted-memory-and-data-framing-tricks.md b/_posts/2022-07-16-restricted-memory-and-data-framing-tricks.md new file mode 100644 index 0000000..eb580e1 --- /dev/null +++ b/_posts/2022-07-16-restricted-memory-and-data-framing-tricks.md @@ -0,0 +1,200 @@ +--- +layout: default +title: Restricted Memory & Data Framing Tricks +date: 2022-07-16 13:51:00 -0500 +tags: c memory embedded ti msp430 union +excerpt_separator: +_preview_description: +--- + +Working on microcontrollers is a far cry from the essentially unlimited memory and floating point operations available +in _Python_ and other high level languages. Here's what I have been learning at my _first_ internship... + + + +For the past 7 weeks I've been working at **Black Pearl Technology** in a Software Development Internship. +To be honest, I really wasn't expecting to be getting an internship this summer, and while I was sad about it, I +was loaded up on summer courses such that I'd have a leisurely fun summer doing what I wanted. + + +Well, fortunately (for my career, not so much for my stress levels), I managed to snag this internship at the last second, +and I've been learning _so_ much. + +## Restricted Memory + +While working there, I was assigned to a project that required usage of a Texas Instruments MSP430 microcontroller. +While there were many quirks with working on MCUs like this (like having to ditch JetBrains & VSCode altogether!), the +biggest quirk isn't working with C: it's working without `malloc` altogether. + +On low memory devices like this, memory is extremely limited and there is no certainty that your code will not leak memory. +Usage of `malloc` and other dynamic memory allocation methods are considered innately dangerous - while there is a chance +you will write perfect code that will properly allocate/deallocate, you can't be certain that your complex program +won't run out of memory on such a small footprint to work with. + +Instead, variables are assigned either inside methods for short periods (and passed around), or they are assigned statically and globally. +It appears that the libraries I use personally prefer globally accessible variables, which 99% of the time, is very wrong - but in +Microcontroller land, global variables are your friend. + +```c +#include + +uint8_t uid[4]; // A unique identifier + +int main(void) { + UID_generate(uid, 4); // Pass the pointer, the function writes to it (and does not return it) + UART_putUid(uid, 4); // Write the UID to UART +} +``` +```c +void UID_generate(uint8_t uid, int length) { + uint8_t i = 0; + while(i < length) + uid[i++] = RANDOM_char(); +} + +void UART_putUid(uint8_t* uid, int length) { + uint8_t i = 0; + while(i < length) + UART_putChar(uid[i++]); +} + +void UART_putChar(uint8_t value) { + while(!(UCB0IFG & 0x1)); + UCB0TXBUF = value; +} +``` + +
+ + + UART is a serial communication technology we use to send characters & text to the COM terminal. + For more information, click here. + + +
+ +There's not much more to this - don't use `malloc`, stick to the _stack_ for actively executing methods and use _global variables_ +when you need to go into low power mode while maintaining state. + +Overall, this doesn't hinder ones ability to write working code - the features are still there, but the way you access +methods, store data & manipulate is re-organized - sometimes at the detriment to quality & refactoring efforts. + +## Data Framing Tricks + +While at my internship, I used my MSP430 microcontroller to communicate with various devices over UART and SPI. I also sent +commands to a ISO15693 NFC wafers. All of these interfaces are extremely low level and the best documentation I have +is often just a PDF and some random code scattered across the internet. There is no library to speak of, usually. + +Communicating at a low level like this requires reading and writing individual bytes of data into _frames_, or arrays +of bytes with a well-defined structure. + +
+ ISO15693 Write Single Block (Addressed) source +
+ +[![ISO15693 Write Single Block Diagram][iso15693-diagram]][iso15693-diagram-edn] + +Traditionally, commands are built statically all at once in a mostly hardcoded manner: + +```c +uint8_t offset = 0; +ui8TRFBuffer[offset++] = 0x61; +ui8TRFBuffer[offset++] = 0x21; +ui8TRFBuffer[offset++] = 0xA7; +ui8TRFBuffer[offset++] = 0x3E; +ui8TRFBuffer[offset++] = 0xFF; +ui8TRFBuffer[offset++] = 0x58; +// ... You get the idea +``` + +Instead, what if we could format this into a `struct` that we could pass around on the stack with a pointer? + +```c +struct AddressedSingleBlockWrite { + uint8_t Flag; + uint8_t Command; + uint8_t Address[8]; + uint8_t Block; + uint8_t Data[4]; +}; + +int main() { + struct AddressedSingleBlockWrite command; + command.Flag = 0x20 | 0x40; + command.Command = 0x21; + uint8_t address[8] = {0xA7, 0x3E, 0xFF, 0x58, 0x21, 0x32, 0x10, 0xFE}; + memcpy(&command.Address, &address, sizeof(command.Address)); + command.Block = 0x05; + uint8_t data[4] = {0x11, 0x22, 0x33, 0x44}; + memcpy(&command.Data, &data, sizeof(command.Data)); +} +``` + + +Now we have a defined structure in our source code and we can move and manipulate various parts of our command +structures without having to deal with hardcoded offsets. Still though, if we want to copy this command structure into +the buffer, we have to individually copy each part of the command - which will break the second we modify its structure. + +There's a fantastic solution for it: **Unions**. + +```c +union ASBWUnion { + uint8_t data[15]; + struct AddressedSingleBlockWrite marshalled; +}; +``` + +```c +union ASBWUnion demarshalled; +demarshalled.marshalled = command; + +for (int i = 0; i < 15; i++) + printf("%x ", demarshalled.data[i]); +``` + +```sass +60 21 a7 3e ff 58 21 32 10 fe 5 11 22 33 44 +``` + +`union`s are special datatypes that share a single memory footprint (equal to it's largest member) starting at the exact same point memory. +They combine neatly with `struct`s to allow us to represent the `AddressedSingleBlockWrite` as a single byte array. + +
+Note: When implementing this, I do recommend that you create macro definitions for the length of the final command structure. +This will help greatly when it comes to refactoring or making adjustments to your command structure. +
+ +#### Reversing Endianness + +If you check out TI's [sloa141][sloa141] PDF on ISO 15693 commands, you'll notice that many of the examples have +sections of their bytes reversed - sections like the _Address_ and _Data_ sections, but not the entire compiled command. + +One such example, `60 21 9080C2E5D2C407E0 06 55443322` (spaces inserted to split it apart), has the _Flag_, _Command_, +_Address_, _Block_ and _Data_ stored in that order. But for this particular example, how could the address `E007C4D2E5C28090`? +How could the data be `0x22334455`? It's the Endianness - the order of bytes as the underlying architecture understands it. + +While for my particular usage, reversing endianness was not needed, it's an interesting problem that can be solved +quite easily with our new data structure. + +```c +void ReverseEndianness(struct AddressedSingleBlockWrite asbw) { + uint8_t temp; + int i = 0; + + for (; i < 4; i++) { + temp = command.Address[i]; + command.Address[i] = command.Address[8 - i - 1]; + command.Address[8 - i - 1] = temp; + } + + for (i = 0; i < 2; i++) { + uint8_t temp = command.Data[4 - i - 1]; + command.Data[4 - i - 1] = command.Data[i]; + command.Data[i] = temp; + } +} +``` + +[iso15693-diagram]: /assets/img/iso15693_diagram.png +[iso15693-diagram-edn]: /assets/iso15693_diagram.edn +[sloa141]: http://www.ti.com/lit/an/sloa141/sloa141.pdf \ No newline at end of file diff --git a/assets/img/iso15693_diagram.png b/assets/img/iso15693_diagram.png new file mode 100644 index 0000000..2071dfb Binary files /dev/null and b/assets/img/iso15693_diagram.png differ diff --git a/assets/img/iso15693_diagram.svg b/assets/img/iso15693_diagram.svg new file mode 100644 index 0000000..5f5d2d6 --- /dev/null +++ b/assets/img/iso15693_diagram.svg @@ -0,0 +1 @@ +0123456789abcdeFlagCommandAddressBlockData000f6121a73eff58213210fe0511223344 \ No newline at end of file diff --git a/assets/iso15693_diagram.edn b/assets/iso15693_diagram.edn new file mode 100644 index 0000000..e10603d --- /dev/null +++ b/assets/iso15693_diagram.edn @@ -0,0 +1,35 @@ +(defattrs :bg-green {:fill "#a0ffa0"}) +(defattrs :bg-yellow {:fill "#ffffa0"}) +(defattrs :bg-pink {:fill "#ffb0a0"}) +(defattrs :bg-cyan {:fill "#a0fafa"}) +(defattrs :bg-purple {:fill "#e4b5f7"}) + +(defattrs :vertical [:plain {:writing-mode "vertical-rl"}]) + +(def row-height 70) +(def boxes-per-row 15) + +(draw-column-headers {:stroke "#ffffff"}) +(draw-box (text "Flag" :vertical) :bg-purple) +(draw-box (text "Command" [:vertical {:font-size 11}]) :bg-cyan) +(draw-box (text "Address" :math) [{:span 8} :bg-yellow]) +(draw-box (text "Block" :vertical) :bg-pink) +(draw-box "Data" [{:span 4} :bg-green]) + +(draw-box 0x61 [:bg-purple {:next-row-height 30}]) +(draw-box 0x21 :bg-cyan) + +(draw-box 0xA7 :bg-yellow) +(draw-box 0x3E :bg-yellow) +(draw-box 0xFF :bg-yellow) +(draw-box 0x58 :bg-yellow) +(draw-box 0x21 :bg-yellow) +(draw-box 0x32 :bg-yellow) +(draw-box 0x10 :bg-yellow) +(draw-box 0xFE :bg-yellow) + +(draw-box 0x05 :bg-pink) +(draw-box 0x11 :bg-green) +(draw-box 0x22 :bg-green) +(draw-box 0x33 :bg-green) +(draw-box 0x44 :bg-green) \ No newline at end of file