Skip to content

Commit 88923c3

Browse files
committed
Send messages with packets, checksums, delivery reports.
1 parent 7eb499f commit 88923c3

File tree

3 files changed

+351
-97
lines changed

3 files changed

+351
-97
lines changed

src/part2/serial-link.md

Lines changed: 179 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,123 @@ To do this we'll use the serial interrupt:
266266
{{#include ../../unbricked/serial-link/sio.asm:sio-serial-interrupt-vector}}
267267
```
268268

269-
---
270-
271269
**TODO:** explain something about interrupts? but don't be weird about it, I guess...
272270

273271
---
274272

275273

276-
## Using Sio
274+
### A little protocol
275+
276+
Before diving into implementation, let's take a minute to describe a *protocol*.
277+
A protocol is a set of rules that govern communication.
278+
279+
The most critical communications are those that support the application's features, which we'll call *messages*.
280+
281+
/// Transmission errors: do not want. Transmission errors: cannot be eliminated.
282+
/// Lots of possible ways to deal with damaged message packets.
283+
/// Need to *detect* errors before you can deal with them.
284+
285+
There's always a possibility that a message will be damaged in transmission or even due to a bug.
286+
The most important step to take in dealing with this reality is *detection* -- the application needs to know if a message was delivered successfully (or not).
287+
To check that a message arrived intact, we'll use checksums.
288+
Every packet sent will include a checksum of itself.
289+
At the receiving end, the checksum can be computed again and checked against the one sent with the packet.
290+
291+
:::tip Checksums, a checksummary
292+
293+
A checksum is a computed value that depends on the value of some *input data*.
294+
In our case, the input data is all the bytes that make up a packet.
295+
In other words, every byte of the packet influences the sum.
296+
297+
<!-- A checksum of a packet can be sent alongside the packet, which the receiver can use to check if the packet arrived intact. -->
298+
The packet includes a field for such a checksum, which is initialised to `0`.
299+
The checksum is computed using the whole packet -- including the zero -- and the result is written to the checksum field.
300+
When the packet checksum is recomputed now, the result will be zero!
301+
This is a common feature of popular checksums because it makes checking if data is intact so simple.
302+
303+
:::
304+
305+
Checking the packet checksum will indicate if the message was damaged, but only the receiver will have this information.
306+
To inform the sender we'll make a rule that every message transfer must be followed by a delivery *report*.
307+
In terms of information, a report is a boolean value -- either the message was received intact, or not.
308+
309+
Because reports are so simple -- but very important -- we'll employ a simple technique to deliver them reliably.
310+
Define two magic numbers -- one to send when the packet checksum matched and another for if it didn't.
311+
For this tutorial we'll use `DEF STATUS_OK EQU $11` for *success* and flip every bit, giving `DEF STATUS_ERROR EQU $EE` to mean *failed*.
312+
313+
To increase the likelihood of the report getting interpreted correctly, we'll simply repeat the value multiple times.
314+
At the receiving end, check each received byte -- finding just one byte equal to `STATUS_OK` will be interpreted as *success*.
315+
316+
:::tip
317+
318+
The binary values used here should be far apart in terms of [*hamming distance*](https://en.wikipedia.org/wiki/Hamming_distance).
319+
In essence, either value should be very hard to confuse for the other, even if some bits were randomly changed.
320+
321+
:::
322+
323+
<!-- PROTOCOL RULES
324+
- two communication "channels": application (messages) & meta (reports)
325+
- message packet includes a checksum of itself to be validated by receiver
326+
- every message packet is followed by a delivery report
327+
- message begins with 'MessageType' code -->
277328

278-
---
279329

280-
**TODO:**
330+
### SioPacket
331+
We'll implement some functions to facilitate constructing, sending, receiving, and checking packets.
332+
The packet functions will operate on the existing serial data buffers.
281333

282-
/// building serial link test program, separate to unbricked main.asm?
334+
The packets follow a simple structure: starting with a header containing a magic number and the packet checksum, followed by the payload data.
335+
The magic number is a constant that marks the start of a packet.
336+
337+
At the top of `sio.asm` define some constants:
338+
339+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/sio.asm:sio-packet-defs}}
340+
{{#include ../../unbricked/serial-link/sio.asm:sio-packet-defs}}
341+
```
342+
343+
/// function to call to start building a new packet:
344+
345+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/sio.asm:sio-packet-prepare}}
346+
{{#include ../../unbricked/serial-link/sio.asm:sio-packet-prepare}}
347+
```
348+
349+
/// returns packet data pointer in `hl`
350+
351+
/// After calling `SioPacketTxPrepare`, the payload data can be added to the packet.
352+
353+
Once the desired data has been copied to the packet, the checksum needs to be calculated before the packet can be transferred.
354+
We call this *finalising* the packet and this is implemented in `SioPacketTxFinalise`:
355+
356+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/sio.asm:sio-packet-finalise}}
357+
{{#include ../../unbricked/serial-link/sio.asm:sio-packet-finalise}}
358+
```
359+
360+
/// call `SioPacketChecksum` to calculate the checksum and write the result to the packet.
361+
362+
/// a function to perform the checksum test when receiving a packet, `SioPacketRxCheck`:
363+
364+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/sio.asm:sio-packet-check}}
365+
{{#include ../../unbricked/serial-link/sio.asm:sio-packet-check}}
366+
```
367+
368+
/// Checks that the packet begins with the magic number `SIO_PACKET_START`, before checking the checksum.
369+
/// For convenience, a pointer to the start of packet data is returned in `hl`.
370+
371+
/// Finally, implement the checksum:
372+
373+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/sio.asm:sio-checksum}}
374+
{{#include ../../unbricked/serial-link/sio.asm:sio-checksum}}
375+
```
376+
377+
:::tip
378+
379+
The checksum implemented here has been kept very simple for this tutorial.
380+
It's probably not very suitable for real-world projects.
381+
382+
:::
383+
384+
385+
## Using Sio
283386

284387
/// Because we have an extra file (sio.asm) to compile now, the build commands will look a little different:
285388
```console
@@ -289,28 +392,79 @@ $ rgblink -o unbricked.gb main.o sio.o
289392
$ rgbfix -v -p 0xFF unbricked.gb
290393
```
291394

395+
<!-- "Link" -->
396+
/// serial link features: *Link*
397+
292398
/// tiles
293399

294400
/// defs
295401

296-
/// init/reset
402+
<!-- LinkInit -->
403+
/// one function to initialise basic serial link state.
404+
405+
/// Implement `LinkInit`:
297406

298-
/// initialise Sio
299-
Before doing anything else with Sio, `SioInit` needs to be called.
407+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-init}}
408+
{{#include ../../unbricked/serial-link/main.asm:link-init}}
409+
```
410+
411+
Calling `SioInit` prepares Sio for use, except for one thing: **e**nabling **i**nterrupts with the `ei` instruction.
412+
413+
:::tip
414+
415+
If interrupts must be enabled for Sio to work fully, you might be wondering why we don't just do it in `SioInit`.
416+
Sio is in control of the serial interrupt, but `ei` enables interrupts globally.
417+
Other interrupts may be in use by other parts of the code, which are clearly outside of Sio's responsibility.
418+
419+
/// Sio doesn't enable or disable interrupts because side effects ...
420+
421+
/// [Interrupts](https://gbdev.io/pandocs/Interrupts.html)
422+
423+
:::
424+
425+
Note that `LinkReset` starts part way through `LinkInit`.
426+
This way the two functions can share code without zero overhead and `LinkReset` can be called without performing the startup initialisation again.
427+
This pattern is often referred to as *fallthrough*: `LinkInit` *falls through* to `LinkReset`.
428+
429+
Call the init routine once before the main loop starts:
300430

301431
```rgbasm
302-
call SioInit
432+
call LinkInit
433+
```
434+
303435

304-
; enable interrupts!
305-
ei
436+
### Link impl go
437+
438+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-send-status}}
439+
{{#include ../../unbricked/serial-link/main.asm:link-send-status}}
306440
```
307441

308-
/// update Sio every frame...
309-
```rgbasm
310-
call SioTick
442+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-send-test-data}}
443+
{{#include ../../unbricked/serial-link/main.asm:link-send-test-data}}
311444
```
312445

313-
---
446+
<!-- LinkUpdate -->
447+
/// Implement `LinkUpdate`:
448+
449+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-update}}
450+
{{#include ../../unbricked/serial-link/main.asm:link-update}}
451+
```
452+
453+
/// update Sio every frame...
454+
455+
/// in the `INIT` state, do handshake until its done.
456+
457+
Once the handshake is complete, change to the `READY` state and notify the other device.
458+
459+
/// in any of the other active states, reset if the B button is pressed
460+
461+
/// finally we jump to different routines based on Sio's transfer state
462+
463+
464+
<!-- handle received message -->
465+
/// **(very) TODO:** handling received messages...
466+
467+
314468

315469
### Handshake
316470

@@ -363,3 +517,12 @@ Before doing anything else with Sio, `SioInit` needs to be called.
363517
/// Second byte must be `wHandshakeExpect`
364518

365519
/// If the received message is correct, set `wHandshakeState` to zero
520+
521+
:::tip
522+
523+
This is a trivial example of a handshake protocol.
524+
In a real application, you might want to consider:
525+
- using a longer sequence of codes as a more unique app identifier
526+
- sharing more information about each device and negotiating to decide the preferred clock provider
527+
528+
:::

0 commit comments

Comments
 (0)