@@ -284,7 +284,7 @@ This is a simple sequence that checks that there is a connection and tests that
284284The handshake can be performed in one of two roles: * A* or * B* .
285285To be successful, one peer must be * A* and the other must be * B* .
286286Which role to perform is determined by the clock source setting of the serial port.
287- In each exchange, each peer sends a number associated with its role and expects to receive a number associated with the other role .
287+ The handshake then involves a number of exchanges, with each peer sending a certain value that the other expects .
288288If an unexpected value is received, or something goes wrong with the transfer, that handshake is rejected.
289289
290290
@@ -369,18 +369,24 @@ It's time to implement the protocol and build the application-level features on
369369<!-- Link defs -->
370370At the top of main.asm, define the constants for keeping track of Link's state:
371371
372- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo -defs}}
373- {{#include ../../unbricked/serial-link/main.asm:serial-demo -defs}}
372+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link -defs}}
373+ {{#include ../../unbricked/serial-link/main.asm:link -defs}}
374374```
375375
376376<!-- Link state -->
377377We'll need some variables in WRAM to keep track of things.
378378Add a section at the bottom of main.asm:
379379
380- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-wram }}
381- {{#include ../../unbricked/serial-link/main.asm:serial-demo-wram }}
380+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-state }}
381+ {{#include ../../unbricked/serial-link/main.asm:link-state }}
382382```
383383
384+ - these will make more sense as we use them, but ...
385+ - ` wLink ` holds the state/status of the Link feature itself.
386+ - the constants prefixed with ` LINK_ ` correspond to ` wLink `
387+ - ` wShakeFailed ` is used to indicate handshake failure, and to delay (re-)connection attempts
388+
389+ <!-- FIX: Doesn't match the unbricked link feature.
384390`wLocal` and `wRemote` are two identical structures for storing the Link state information of each peer.
385391- `state` holds the current mode and some flags (the `LINKST_` constants)
386392- `tx_id` & `rx_id` are for the IDs of the most recently sent & received `MSG_DATA` message
@@ -389,32 +395,24 @@ The contents of application data messages (`MSG_DATA` only) will be stored in th
389395
390396`wAllowTxAttempts` is the number of transmission attempts remaining for each DATA message.
391397`wAllowRxFaults` is the "budget" of delivery faults allowed before causing an error.
398+ -->
392399
393400
394401### LinkInit
395- Lots of variables means lots of initialisation so let's add a function for that:
402+ We're going to add quite a few functions for the new link feature and they'll all be prefixed with ` Link ` .
403+ To keep things organised, add a new ` ROM0 ` section for the ` Link ` implementation:
396404
397- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-init }}
398- {{#include ../../unbricked/serial-link/main.asm:link-init }}
405+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl }}
406+ {{#include ../../unbricked/serial-link/main.asm:link-impl }}
399407```
400408
401- This initialises Sio by calling ` SioInit ` and then enables something called the serial interrupt which will be explained soon.
402- Execution continues into ` LinkReset ` .
403-
404- ` LinkReset ` can be called to reset the whole Link feature if something goes wrong.
405- This resets Sio and then writes default values to all the variables we defined above.
406- Finally, a function called ` HandshakeDefault ` is jumped to and for that one you'll have to wait a little bit!
407-
408- Make sure to call the init routine once before the main loop starts:
409+ First things first: we need to initialise the variables we created, as well as Sio, so create the ` LinkInit ` function:
409410
410- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo -init-callsite }}
411- {{#include ../../unbricked/serial-link/main.asm:serial-demo -init-callsite }}
411+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl -init}}
412+ {{#include ../../unbricked/serial-link/main.asm:link-impl -init}}
412413```
413414
414- We'll also add a utility function for handling errors:
415- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-error-stop}}
416- {{#include ../../unbricked/serial-link/main.asm:link-error-stop}}
417- ```
415+ After calling ` SioInit ` this enables something called the * serial interrupt* by setting the associated bit (` IEF_SERIAL ` ) of the ` rIE ` register.
418416
419417
420418### Serial Interrupt
@@ -454,156 +452,180 @@ If you would like to continue digging, have a look at [evie's interrupts tutoria
454452
455453
456454### LinkUpdate
457- ` LinkUpdate ` is the main per-frame update function.
455+ ` LinkUpdate ` is the main per-frame update function for the link feature .
458456
459- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-update}}
460- {{#include ../../unbricked/serial-link/main.asm:link-update}}
457+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl- update}}
458+ {{#include ../../unbricked/serial-link/main.asm:link-impl- update}}
461459```
462460
463- The order of each part of this is important -- note the many (conditional) places where execution can exit this procedure .
461+ To follow the code here, it helps to see the whole thing as a state machine .
464462
465- Check input before anything else so the user can always reset the demo.
463+ - ` LINK_ENABLE ` flag controls the entire feature
464+ - if its not set, do nothing
465+ - update Sio:
466+ - ` SioTick ` needs to be called regularly, so we do that here
467+ - check ` wSioState ` -- if Sio is waiting for a transfer to complete, we wait too.
468+ - the ` LINK_CONNECTED ` flag is set once we've successfully performed a handshake
469+ - jump to ` .conn_up: ` if the flag is set
470+ - check ` wSioState ` to decide what to do
471+ - the implementation of each of these functions is below
472+ - otherwise, continue into ` .conn_shake: ` to perform a handshake
473+ - ` wShakeFailed ` is set non-zero when a handshake fails -- the value acts as a countdown timer to delay retry attempts
474+ - decrement it (` dec a ` ) and store the new value
475+ - if the new value is zero, jump to ` LinkStart ` to try again
476+ - if ` wShakeFailed ` is zero, a handshake attempt is already underway
477+ - check ` wSioState ` to decide what to do
478+ - the implementation of each of these functions is below
466479
467- The ` LINKST_MODE_ERROR ` mode is an unrecoverable error state that can only be exited via the reset.
468- To check the current mode, read the ` wLocal.state ` byte and use ` and a, LINKST_MODE ` to keep just the mode bits.
469- There's nothing else to do in the ` LINKST_MODE_ERROR ` mode, so simply return from the function if that's the case.
470480
471- Update Sio by calling ` SioTick ` and then call a specific function for the current mode.
481+ #### LinkPacketRx
482+ ` LinkPacketRx ` is used to check for and validate received packets from any state.
472483
473- ` LINKST_MODE_CONNECT ` manages the handshake process.
474- Update the handshake if it's incomplete ( ` wHandshakeState ` is non-zero).
475- Otherwise, transition to the active connection mode.
484+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-packet-rx}}
485+ {{#include ../../unbricked/serial-link/main.asm:link-impl-packet-rx}}
486+ ```
476487
477- ` 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.
488+ The first thing to do is flush Sio's state (set it to ` SIO_IDLE ` ) to indicate that the received data has been processed.
489+ Technically the data hasn't actually been processed yet, but this is a promise to do that!
478490
491+ Check that a packet was received and that it arrived intact by calling ` SioPacketRxCheck ` .
492+ Return here if Sio's checks failed.
479493
480- ### LinkTx
481- ` LinkTx ` builds the next message packet and starts transferring it .
494+ The last part checks that the received packet count matches the local one in ` wLinkPacketCount ` .
495+ This is done to check that both peers are in sync .
482496
483- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-send-message}}
484- {{#include ../../unbricked/serial-link/main.asm:link-send-message}}
485- ```
497+ Note that ` LinkPacketRx ` uses the zero flag to return a status code.
486498
487- There's two types of message that are sent while the link is active -- SYNC and DATA.
488- The ` LINKST_STEP_SYNC ` flag is used to alternate between the two types and ensure at least every second message is a SYNC.
489- A DATA message will only be sent if the ` LINKST_STEP_SYNC ` flag is clear and the ` LINKST_TX_ACT ` flag is set.
499+ ::: tip
490500
491- 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 ` .
501+ Actually we test against ` wLinkPacketCount ` minus one ( ` dec a ` ) because the value stored is the number of packets * sent * .
492502
493- To make sending DATA messages more convenient, add a utility function to take care of the details:
494- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-tx-start}}
495- {{#include ../../unbricked/serial-link/main.asm:link-tx-start}}
496- ```
503+ :::
497504
498505
499- ### LinkRx
500- When a transfer has completed ( ` SIO_DONE ` ), process the received data in ` LinkRx ` :
506+ #### Sending messages
507+ ` LinkShakeTx ` and ` LinkGameTx ` are quite simple and work in the same way.
501508
502- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-receive-message }}
503- {{#include ../../unbricked/serial-link/main.asm:link-receive-message }}
509+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-shake-tx }}
510+ {{#include ../../unbricked/serial-link/main.asm:link-impl-shake-tx }}
504511```
505512
506- The first thing to do is flush Sio's state (set it to ` SIO_IDLE ` ) to indicate that the received data has been processed.
507- Technically the data hasn't actually been processed yet, but this is a promise to do that!
513+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-game-tx}}
514+ {{#include ../../unbricked/serial-link/main.asm:link-impl-game-tx}}
515+ ```
508516
509- Check that a packet was received and that it arrived intact by calling ` SioPacketRxCheck ` .
510- 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.
517+ To send a packet:
518+ 1 . ` call SioPacketTxPrepare ` ,
519+ 2 . write the data to the packet buffer (` HL ` was set by Sio),
520+ 3 . ` call SioPacketTxFinalise ` .
511521
512- <!-- Faults -->
513- If the result of ` SioPacketRxCheck ` was negative, or the message type is unrecognised, it's considered a delivery * fault* .
514- In case of a fault, the received data is discarded and the fault counter is updated.
515- The fault counter state is loaded from ` wAllowRxFaults ` .
516- If the value of the counter is zero (i.e. there's zero (more) faults allowed) the error mode is acivated.
517- If the value of the counter is more than zero, it's decremented and saved.
522+ The contents of the packet
523+ - the packet sequence ID / count (value of ` wLinkPacketCount ` )
524+ - required to pass the check in ` LinkPacketRx `
525+ - one of the ` MSG_* ` constants
526+ - message-specific data, if any
527+ - ` MSG_GAME ` includes the local score (` wScore ` )
528+ - ` MSG_SHAKE ` has none
518529
519- <!-- SYNC -->
520- ` MSG_SYNC ` messages contain the sender's Link state, so first we copy the received data to ` wRemote ` .
521- Now we want to check if the remote peer has acknowledged delivery of a message sent to them.
522- Copy the new ` wRemote.rx_id ` value to register ` B ` , then load ` wLocal.state ` and copy it into register ` C `
523- Check the ` LINKST_TX_ACT ` flag (using the ` and ` instruction) and return if it's not set.
524- Otherwise, an outgoing message has not been acknowledged yet, so load ` wLocal.tx_id ` and compare it to ` wRemote.rx_id ` (in register ` B ` ).
525- If the two are equal that means the message was delivered, so clear the ` LINKST_TX_ACT ` flag and update ` wLocal.state ` .
526530
527- <!-- DATA -->
528- Receiving ` MSG_DATA ` messages is straightforward.
529- The first byte is the message ID, so copy that from the packet to ` wLocal.rx_id ` .
530- The rest of the packet data is copied straight to the ` wRxData ` buffer.
531- Finally, a flag is set to indicate that data was newly received.
531+ #### Completing the handshake
532+ ` LinkShakeRx ` is responsible for completing the handshake.
532533
534+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-shake-rx}}
535+ {{#include ../../unbricked/serial-link/main.asm:link-impl-shake-rx}}
536+ ```
533537
534- ### Main
538+ - ` LinkPacketRx `
539+ - check that received MSG_SHAKE
540+ - handshake is complete when ` wLinkPacketCount ` reaches three
541+ - as in 3 handshake packets have been sent & received successfully
542+ - set the ` LINK_CONNECTED ` flag if handshake is complete
535543
536- Demo update routine:
537- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-update}}
538- {{#include ../../unbricked/serial-link/main.asm:serial-demo-update}}
539- ```
540544
541- Call the update routine from the main loop:
542- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:serial-demo-update-callsite}}
543- {{#include ../../unbricked/serial-link/main.asm:serial-demo-update-callsite}}
545+ #### Handshake failure
546+ ` LinkShakeFail ` ends the handshake attempt in failure.
547+ This is called when a Sio transfer fails during the handshake and when an invalid handshake message is received.
548+
549+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-shake-fail}}
550+ {{#include ../../unbricked/serial-link/main.asm:link-impl-shake-fail}}
544551```
545552
553+ Set ` wShakeFailed ` to a non-zero to indicate failure.
554+ The value used depends on the clock source setting of the serial port.
546555
547- ### Implement the handshake protocol
556+ - this is part of the automatic role selection strategy
557+ - because the clock provider transfers will occur immediately...
558+ - makes it more likely that (after a failed handshake) the externally clocked device will enable its serial port before the clock provider does.
548559
549- /// Establish contact by trading magic numbers
550560
551- /// Define the codes each device will send:
552- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:handshake-codes}}
553- {{#include ../../unbricked/serial-link/main.asm:handshake-codes}}
554- ```
561+ #### LinkGameRx
555562
556- ///
557- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:handshake-state}}
558- {{#include ../../unbricked/serial-link/main.asm:handshake-state}}
563+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-game-rx}}
564+ {{#include ../../unbricked/serial-link/main.asm:link-impl-game-rx}}
559565```
560566
561- /// Routines to begin handshake sequence as either the internally or externally clocked device.
562567
563- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:handshake-begin}}
564- {{#include ../../unbricked/serial-link/main.asm:handshake-begin}}
565- ```
568+ #### LinkStop
566569
567- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:handshake-update }}
568- {{#include ../../unbricked/serial-link/main.asm:handshake-update }}
570+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-stop }}
571+ {{#include ../../unbricked/serial-link/main.asm:link-impl-stop }}
569572```
570573
571- The handshake can be forced to restart in the clock provider role by pressing START.
572- This is included as a fallback and manual override for the automatic role selection implemented below.
573574
574- If a transfer is completed, process the received data by jumping to ` HandshakeMsgRx ` .
575+ #### LinkStart
576+ ` LinkStart ` starts a new handshake attempt.
575577
576- If the serial port is otherwise inactive, (re)start the handshake.
577- To automatically determine which device should be the clock provider, we check the lowest bit of the DIV register.
578- This value increments at around 16 kHz which, for our purposes and because we only check it every now and then, is close enough to random.
579-
580- ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:handshake-xfer-complete}}
581- {{#include ../../unbricked/serial-link/main.asm:handshake-xfer-complete}}
578+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-impl-start}}
579+ {{#include ../../unbricked/serial-link/main.asm:link-impl-start}}
582580```
583581
584- Check that a packet was received and that it contains the expected handshake value.
585- The state of the serial port clock source bit is used to determine which value to expect -- ` SHAKE_A ` if set to use an external clock and ` SHAKE_B ` if using the internal clock.
586- If all is well, decrement the ` wHandshakeState ` counter.
587- If the counter is zero, there is nothing left to do.
588- Otherwise, more exchanges are required so start the next one immediately.
582+ The handshake can be forced to restart in the clock provider role by holding START.
583+ This is included as a fallback and manual override for the automatic role selection described below.
589584
590- ::: tip
585+ To automatically determine which device should be the clock provider, we could use a random number generator, but we don't have one, so we'll just check the lowest bit of the DIV register.
586+ The value in DIV is automatically incremented at around 16 kHz, which is not at all random, but all we really need is a single bit that's unlikely to be the same as the one on the remote device.
591587
592- This is a trivial example of a handshake protocol.
593- In a real application, you might want to consider:
594- - using a longer sequence of codes as a more unique app identifier
595- - sharing more information about each device and negotiating to decide the preferred clock provider
588+ :::tip DIV is a Pretend Random Number Generator
589+
590+ Not to be confused with a [ Pseudorandom Number Generator] ( https://en.wikipedia.org/wiki/Pseudorandom_number_generator ) .
596591
597592:::
598593
599594
595+ ### Finally!
596+ To integrate the link feature, make some changes to the main loop and entry point:
597+
598+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-main}}
599+ {{#include ../../unbricked/serial-link/main.asm:link-main}}
600+ ```
601+
602+ - Call ` LinkInit ` at startup, just before the ` Main: ` loop.
603+ - In the main loop,
604+ - call ` LinkUpdate `
605+ - ` ei ` /` di ` & code to update the display
606+ - display remote score, & a serial port status icon
607+ - check ` wLink ` status & skip ball update if not connected
608+ - freezes the game if not connected
609+
610+ Copy this function, which is used to to display the remote score (which is a BCD number).
611+ You don't need to pay attention to this, it just adapts printing code from the BCD lesson.
612+
613+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-print-bcd}}
614+ {{#include ../../unbricked/serial-link/main.asm:link-print-bcd}}
615+ ```
616+ Copy these new tiles to the end of the tile data -- they should be immediately after the digits, right before ` TilesEnd ` .
617+ ``` rgbasm,linenos,start={{#line_no_of "" ../../unbricked/serial-link/main.asm:link-tiles}}
618+ {{#include ../../unbricked/serial-link/main.asm:link-tiles}}
619+ ```
600620
601- ## /// Running the test ROM
602621
603- /// Because we have an extra file (sio.asm) to compile now, the build commands will look a little different:
622+ ## Running the test ROM
623+ Because we have an extra file (sio.asm) to compile now, the build commands will look a little different:
624+
604625``` console
605626$ rgbasm -o sio.o sio.asm
606627$ rgbasm -o main.o main.asm
607628$ rgblink -o unbricked.gb main.o sio.o
608629$ rgbfix -v -p 0xFF unbricked.gb
609630```
631+
0 commit comments