Skip to content

Commit 8a9346b

Browse files
committed
Write detailed Link/demo sections, various fixes for demo impl
- Actually initialise TX attempt count each time a data packet is sent. - If in error state, don't update Sio or anything else (requires input to reset). - LinkErrorStop cancels any transfer with SioAbort. - Add to display: data packet IDs, TX attempts counter - Simplify delivery (rx) fault detection - Link FSM states are 'modes'
1 parent 98b03fe commit 8a9346b

File tree

2 files changed

+291
-191
lines changed

2 files changed

+291
-191
lines changed

src/part2/serial-link.md

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -363,43 +363,60 @@ It's probably worth looking into better solutions for real-world projects.
363363

364364

365365
## Connecting it all together
366+
It's time to implement the protocol and build the application-level features on top of everything we've done so far.
366367

367-
<!-- "Link" -->
368-
It's time to build the application-level link features.
369-
We're going to
370-
- implement the protocol logic
371-
- implement the handshake
372-
- build a demo/test program
368+
<!-- Link defs -->
369+
At the top of main.asm, define the constants for keeping track of Link's state:
373370

374-
/// tiles
371+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-defs}}
372+
{{#include ../../unbricked/serial-link/main.asm:serial-demo-defs}}
373+
```
374+
375+
<!-- Link state -->
376+
We'll need some variables in WRAM to keep track of things.
377+
Add a section at the bottom of main.asm:
378+
379+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-wram}}
380+
{{#include ../../unbricked/serial-link/main.asm:serial-demo-wram}}
381+
```
382+
383+
`wLocal` and `wRemote` are two identical structures for storing the Link state information of each peer.
384+
- `state` holds the current mode and some flags (the `LINKST_` constants)
385+
- `tx_id` & `rx_id` are for the IDs of the most recently sent & received `MSG_DATA` message
375386

376-
/// defs
387+
The contents of application data messages (`MSG_DATA` only) will be stored in the buffers `wTxData` and `wRxData`.
377388

389+
`wAllowTxAttempts` is the number of transmission attempts remaining for each DATA message.
390+
`wAllowRxFaults` is a budget of delivery faults allowed before causing an error.
378391

379-
<!-- LinkInit -->
380-
In main.asm (code section)...
381392

382-
Implement `LinkInit`:
393+
### LinkInit
394+
Lots of variables means lots of initialisation so let's add a function for that:
383395

384396
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-init}}
385397
{{#include ../../unbricked/serial-link/main.asm:link-init}}
386398
```
387399

388-
- Initialise Sio by calling `SioInit`.
389-
- enable serial interrupt and interrupts globally
400+
This initialises Sio by calling `SioInit` and then enables something called the serial interrupt which will be explained soon.
401+
Execution continues into `LinkReset`.
390402

391-
Note that `LinkReset` starts part way through `LinkInit`.
392-
This way the two functions can share code with zero overhead and `LinkReset` can be called without performing the startup initialisation again.
393-
This pattern is often referred to as *fallthrough*: `LinkInit` *falls through* to `LinkReset`.
403+
`LinkReset` can be called to reset the whole Link feature if something goes wrong.
404+
This resets Sio and then writes default values to all the variables we defined above.
405+
Finally, a function called `HandshakeDefault` is jumped to and for that one you'll have to wait a little bit!
394406

395-
Call the init routine once before the main loop starts:
407+
Make sure to call the init routine once before the main loop starts:
396408

397-
```rgbasm
398-
call LinkInit
409+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-init-callsite}}
410+
{{#include ../../unbricked/serial-link/main.asm:serial-demo-init-callsite}}
411+
```
412+
413+
We'll also add a utility function for handling errors:
414+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-error-stop}}
415+
{{#include ../../unbricked/serial-link/main.asm:link-error-stop}}
399416
```
400417

401418

402-
<!-- Serial interrupt -->
419+
### Serial Interrupt
403420
Sio needs to be told when to process each completed byte transfer.
404421
The best way to do this is by using the serial interrupt.
405422
Copy this code (it needs to be exact) to `main.asm`, just above the `"Header"` section:
@@ -435,58 +452,95 @@ If you would like to continue digging, have a look at [evie's interrupts tutoria
435452
:::
436453

437454

438-
/// `LinkTx`, alternate between sending the two types of packet:
455+
### LinkUpdate
456+
`LinkUpdate` is the main per-frame update function.
439457

440-
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-send-message}}
441-
{{#include ../../unbricked/serial-link/main.asm:link-send-message}}
458+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-update}}
459+
{{#include ../../unbricked/serial-link/main.asm:link-update}}
442460
```
443461

444-
/// Implement `LinkUpdate`:
462+
The order of each part of this is important -- note the many (conditional) places where execution can exit this procedure.
445463

446-
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-update}}
447-
{{#include ../../unbricked/serial-link/main.asm:link-update}}
464+
Check input before anything else so the user can always reset the demo.
465+
466+
The `LINKST_MODE_ERROR` mode is an unrecoverable error state that can only be exited via the reset.
467+
To check the current mode, read the `wLocal.state` byte and use `and a, LINKST_MODE` to keep just the mode bits.
468+
There's nothing else to do in the `LINKST_MODE_ERROR` mode, so simply return from the function if that's the case.
469+
470+
Update Sio by calling `SioTick` and then call a specific function for the current mode.
471+
472+
`LINKST_MODE_CONNECT` manages the handshake process.
473+
Update the handshake if it's incomplete (`wHandshakeState` is non-zero).
474+
Otherwise, transition to the active connection mode.
475+
476+
`LINKST_MODE_UP` just checks the current state of the Sio state machine in order to jump to an appropriate function to handle certain cases.
477+
478+
479+
### LinkTx
480+
`LinkTx` builds the next message packet and starts transferring it.
481+
482+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-send-message}}
483+
{{#include ../../unbricked/serial-link/main.asm:link-send-message}}
448484
```
449485

450-
/// reset the demo if the B button is pressed
486+
There's two types of message that are sent while the link is active -- SYNC and DATA.
487+
The `LINKST_STEP_SYNC` flag is used to alternate between the two types and ensure at least every second message is a SYNC.
488+
A DATA message will only be sent if the `LINKST_STEP_SYNC` flag is clear and the `LINKST_TX_ACT` flag is set.
451489

452-
/// update Sio every frame
490+
Both cases then send a packet in much the same way -- `call SioPacketPrepare`, write the data to the packet (starting at `HL`), and then `call SioPacketFinalise`.
453491

454-
In the `LINK_INIT` state, do handshake until its done.
455-
Once the handshake is complete, change to the `LINK_UP` state.
492+
To make sending DATA messages more convenient, add a utility function to take care of the details:
493+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-tx-start}}
494+
{{#include ../../unbricked/serial-link/main.asm:link-tx-start}}
495+
```
456496

457-
/// In the `LINK_UP` state, ...
458497

498+
### LinkRx
459499
When a transfer has completed (`SIO_DONE`), process the received data in `LinkRx`:
460500

461501
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-receive-message}}
462502
{{#include ../../unbricked/serial-link/main.asm:link-receive-message}}
463503
```
464504

465-
/// We flush Sio's state (set it to `SIO_IDLE`) here so ... LinkTx ... next update...
466-
467-
Check that we received a packet and its checksum matches.
468-
If the packet integrity test comes back negative, increment the inbound transmission errors counter.
505+
The first thing to do is flush Sio's state (set it to `SIO_IDLE`) to indicate that the received data has been processed.
506+
Technically the data hasn't actually been processed yet, but this is a promise to do that!
469507

470-
:::tip
471-
472-
/// This class of error -- which we're calling a *transmission errors* -- are very significant.
473-
/// assuming code works -- & have reason to believe it does -- this means the data was damaged during transmission, if a valid packet was sent
508+
Check that a packet was received and that it arrived intact by calling `SioPacketRxCheck`.
509+
If the packet checks out OK, read the message type from the packet data and jump to the appropriate routine to handle messages of that type.
474510

475-
/// You might want to do something a bit more sophisticated than counting errors in a real-world application.
511+
<!-- Faults -->
512+
If the result of `SioPacketRxCheck` was negative, or the message type is unrecognised, it's considered a delivery *fault*.
513+
In case of a fault, the received data is discarded and the fault counter is updated.
514+
The fault counter state is loaded from `wAllowRxFaults`.
515+
If the value of the counter is zero (i.e. there's zero (more) faults allowed) the error mode is acivated.
516+
If the value of the counter is more than zero, it's decremented and saved.
476517

477-
:::
518+
<!-- SYNC -->
519+
`MSG_SYNC` messages contain the sender's Link state, so first we copy the received data to `wRemote`.
520+
Now we want to check if the remote peer has acknowledged delivery of a message sent to them.
521+
Copy the new `wRemote.rx_id` value to register `B`, then load `wLocal.state` and copy it into register `C`
522+
Check the `LINKST_TX_ACT` flag (using the `and` instruction) and return if it's not set.
523+
Otherwise, an outgoing message has not been acknowledged yet, so load `wLocal.tx_id` and compare it to `wRemote.rx_id` (in register `B`).
524+
If the two are equal that means the message was delivered, so clear the `LINKST_TX_ACT` flag and update `wLocal.state`.
478525

479-
/// with the packet checking out OK, move on to process the message it contains
480-
/// jump to the appropriate handler ...
481-
/// if the msg type is unknown/unexpected, increment the message/protocol error counter.
526+
<!-- DATA -->
527+
Receiving `MSG_DATA` messages is straightforward.
528+
The first byte is the message ID, so copy that from the packet to `wLocal.rx_id`.
529+
The rest of the packet data is copied straight to the `wRxData` buffer.
530+
Finally, a flag is set to indicate that data was newly received.
482531

483-
/// SYNC messages...
484532

485-
---
533+
### Main
486534

487-
/// TEST_DATA messages...
535+
Demo update routine:
536+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-update}}
537+
{{#include ../../unbricked/serial-link/main.asm:serial-demo-update}}
538+
```
488539

489-
---
540+
Call the update routine from the main loop:
541+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-update-callsite}}
542+
{{#include ../../unbricked/serial-link/main.asm:serial-demo-update-callsite}}
543+
```
490544

491545

492546
### Implement the handshake protocol
@@ -542,6 +596,7 @@ In a real application, you might want to consider:
542596
:::
543597

544598

599+
545600
## /// Running the test ROM
546601

547602
/// Because we have an extra file (sio.asm) to compile now, the build commands will look a little different:

0 commit comments

Comments
 (0)