You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**TODO:** explain something about interrupts? but don't be weird about it, I guess...
270
-
271
-
---
246
+
All this short routine really *does* is `call SioPortEnd`.
247
+
But because this is an interrupt handler we have to do a little dance to prevent *bad things* from happening.
248
+
We need to preserve the values in the registers that will be modified by `SioPortEnd`, `af` and `hl`.
249
+
/// make sure the registers are in the same state as when the interrupt occured so that when the interrupted code is resumed, it can continue as if nothing happened.
250
+
*The stack* is the perfect way to do this.
251
+
`push` copies the value from the register to the top of the stack and `pop` takes the top value off of the stack and moves it to the register.
252
+
Note that a stack is a first-in first-out (FIFO) container, so we push `af` then `hl` -- leaving `hl` on the top -- and pop `hl` then `af`.
272
253
254
+
:::tip We interrupt this broadcast to briefly explain what interrupts are
273
255
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.
256
+
An interrupt is a way to run a certain piece of code when an external event occurs.
257
+
Interrupts are requested by *peripherals* (hardware connected to the CPU) and the CPU literally interrupts whatever it was doing to go an execute some different code instead.
258
+
In this case, the event is the serial port counting to eight (meaning a whole byte was transferred), and the code that will be executed is whatever is at memory address `$58` -- the routine above.
302
259
303
260
:::
304
261
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
262
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*.
263
+
### A little protocol
264
+
265
+
Before diving into (more) implementation, let's take a minute to describe a *protocol*.
266
+
A protocol is a set of rules that govern communication.
312
267
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*.
268
+
/// Everything is packets.
269
+
/// Packets: small chunks of data with rudimentary error detection
315
270
316
-
:::tip
271
+
For reliable data transfer, alternate between two message types:
272
+
protocol metadata in *SYNC* packets, and application data in *DATA* packets.
273
+
DATA packets are required to include a sequential message ID -- the rest is up to the application.
274
+
SYNC packets include the ID of the most recently received DATA packet.
317
275
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.
276
+
Packet integrity can be tested on the receiving end using a checksum.
277
+
Damaged packets of either type are discarded.
278
+
Successful delivery of a DATA packet is acknowledged when its ID is included in a SYNC packet.
279
+
The sender should retransmit the packet if successful delivery is not acknowledged.
320
280
321
-
:::
281
+
There's one more thing our protocol needs: some way to get both devices on the same page and kick things off.
282
+
We need a *handshake* that must be completed before doing anything else.
283
+
This is a simple sequence that checks that there is a connection and tests that the connection is working.
284
+
The handshake can be performed in one of two roles: *A* or *B*.
285
+
To be successful, one peer must be *A* and the other must be *B*.
286
+
Which 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.
288
+
If an unexpected value is received, or something goes wrong with the transfer, that handshake attempt is aborted.
322
289
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 -->
328
290
291
+
### /// SioPacket
329
292
330
-
### SioPacket
331
293
We'll implement some functions to facilitate constructing, sending, receiving, and checking packets.
332
294
The packet functions will operate on the existing serial data buffers.
333
295
@@ -382,7 +344,7 @@ It's probably not very suitable for real-world projects.
382
344
:::
383
345
384
346
385
-
## Using Sio
347
+
## /// Using Sio
386
348
387
349
/// Because we have an extra file (sio.asm) to compile now, the build commands will look a little different:
388
350
```console
@@ -423,7 +385,7 @@ Other interrupts may be in use by other parts of the code, which are clearly out
423
385
:::
424
386
425
387
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.
388
+
This way the two functions can share code with zero overhead and `LinkReset` can be called without performing the startup initialisation again.
427
389
This pattern is often referred to as *fallthrough*: `LinkInit`*falls through* to `LinkReset`.
428
390
429
391
Call the init routine once before the main loop starts:
@@ -432,41 +394,61 @@ Call the init routine once before the main loop starts:
432
394
call LinkInit
433
395
```
434
396
397
+
/// `LinkTx`, alternate between sending the two types of packet:
/// If `wHandshakeState` is zero, handshake is complete
496
-
497
-
/// If the user has pressed START, abort the current handshake and start again as the clock provider.
498
-
499
-
/// Monitor Sio. If the serial port is not busy, start the handshake, using the DIV register as a pseudorandom value to decide if we should be the clock or not.
475
+
The handshake can be forced to restart in the clock provider role by pressing START.
476
+
This is included as a fallback and manual override for the automatic role selection implemented below.
500
477
501
-
:::tip The DIV register
478
+
If a transfer is completed, process the received data by jumping to `HandshakeMsgRx`.
502
479
503
-
/// is not particularly random...
504
-
505
-
/// but we just need the value to be different when each device reads it, and for the value to occasionally be an odd number
506
-
507
-
:::
508
-
509
-
/// If a transfer is complete (`SIO_DONE`), jump to `HandshakeMsgRx` (described below) to check the received value.
480
+
If the serial port is otherwise inactive, (re)start the handshake.
481
+
To automatically determine which device should be the clock provider, we check the lowest bit of the DIV register.
482
+
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.
/// If the received message is correct, set `wHandshakeState` to zero
488
+
Check that a packet was received and that it contains the expected handshake value.
489
+
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.
490
+
If all is well, decrement the `wHandshakeState` counter.
491
+
If the counter is zero, there is nothing left to do.
492
+
Otherwise, more exchanges are required so start the next one immediately.
0 commit comments