Jump to content

A tweaked version of Handy to test Comlynx homebrew and some code examples


Recommended Posts

Developing comlynx support for Atari Lynx homebrew could seems a difficult task, but I discovered that it's easy.


I think that the real reasons for so few homebrew with comlynx support are that there aren't so many examples of code available, and comlynx is not well suported on emulators, making difficoult to test the code.


Sage's version of handy should support comlynx emulation over UDP, but using two PC is not the best solution while testing, and on my PCs I wasn't able to make it work (it crashes trying to connect).


Since I'm developig a game that uses a 2 players mode, and I have to wait long time before to have an SD cart or an EEPROM cart programmer PI hat, I decided to modify Sage's version of the emulator to run two instances of the emulator core in the same application, than crosslinked the TX and RX comlynx interfaces of the two cores to have a comlynx test enviroment that can run on a single PC and without relying on network protocols.


I made a quick and dirty tweak of the code, so don't expect an advanced version of handy. It's buggy, plays sound from both emulators core (so better disable sound) and can't emulate commercial games comlynx connection. But it works very well as a testing tools for developers.


It's possible to swap betwen the two cores with CTRL+N or from the emulator menu. The active core outputs to the screen and gets the keyboard input, the other one runs silently in background. This means that it's not possible to use the two cores at the same time.


Attached you can find a build for windows and a zip with the modified sources. When I have time I'll add the sources to my github and post the link here.


To encourage other developers to start using the comlynx feature in their games (this can give more chances to win in the atarigamers game competition of this summer ) I'm sharing here two code examples of comlynx use.


The first one is a simple piece of code from karri already posted on the forum (http://atariage.com/forums/topic/154232-cc65-link-error/?do=findComment&comment=1888745) that doesn't seem to have attracted the attention of the other developers. I slightly modified karri's code and attached it here (testcon.zip).


In this example some sample text is sent over the comlynx connection at the A key press and printed on the screen of the other endpoint. The B key clears the buffer. The direction keys move an Hello world text, so that placing it in different position can help to identify the two running instances.


The second example is a simple but complete game, an implementation of Tic Tac Toe using a 2 player mode over comlynx.


The game uses a simple handshake protocol to start the connection that defines the two players ID (I took the handshake protocol from the comlynx code example of Championship Rally posted here http://atariage.com/forums/topic/283514-multiplayer-comlynx-from-championship-rally/) and a simple communication protocol derived from a more complex one I'm developing for another game of mine.


Here are some screeshots of the game:


post-66453-0-48494800-1557611592.png post-66453-0-21897400-1557611600.png post-66453-0-35187700-1557611607.png


The game code is mine (wrote it in an hour), this means two things:

- It's public domain (do whatever you want with it, you can also develop from it a full game with muisic, gfx and and AI for 1P mode, but hurry up before I do it first)

- It can contain bugs.


I really hope to see a lot of new homebrew games with comlynx support in the next future.


Happy coding



Note: The code examples posted here aren't tested on real lynx yet. Please let me know if they work as expected.





Edited by Nop90
  • Like 5
Link to comment
Share on other sites

The echo is already in the original code and didn't change it. the CC65 serial example from karri (that is the base from my code examples) use it to check the transmission, so I suppose it's ok.


Let's wait that someone checks if the tic tac toe game works on two real lynx.


About T-Tris and SIMIS, do they use the redeye protocol? Since all the commercial games that use it don't work on handy, this could be the reason.

Link to comment
Share on other sites

About T-Tris and SIMIS, do they use the redeye protocol? Since all the commercial games that use it don't work on handy, this could be the reason.


There is not really "one" red eye protocol.


There are games which rely on the EPYX code. and other are not. Malibu for example uses something completely different.

Link to comment
Share on other sites

  • 3 weeks later...

Wow, seems my tricky experiment can run Battlezone in 2P mode.


Other games I tested (like checkered flag or warbirds) doesn't.



EDIT: all the games from Hand Made Software with multiplayer support works

Edited by Nop90
  • Like 1
Link to comment
Share on other sites

Tracing the exchange of data in warbirds and other games I saw that the two instances try to get in sync throwing a fixed sequence of data but with no success because the two transmissions overlaps.

The first thing I did was to introduce a little delay on the start of the second instance. This way the first instance becames the manster sending the sequence, the other only listens. But the sync doesn't happen.

So I had the enlightment of removing the 1024 steps every update introduced in the emulator to boost performance, and with only 1 step every update the two instances can syncronize.

I think that, with low priority respect the lynx coding, I'll add the possibility to start 1 to 4 instances,and will add multiple drawing surfaces (one for every instance) and separate inputs, so to have a full multiplayer emulator.

If someone wants to help, let me know.

Link to comment
Share on other sites

  • 4 weeks later...

Now that I have several working Lynx and the cart programmer I solved the comlynx serial protocol problems.


The most importat thing that discovered is that only ODD and EVEN parity always work. MARK and SPACE transmit only some of the values (have an idea of the reason but don't have time to investigate more now).


Changing the parity option and with small fixes to the code, the tic tac toe example works.


I also successfully used the protocol for one of my games in developement and I'm going to start coding the multiplayer mode for Nutmeg, my entry for the Lynx coding competition.


Now I'm too busy, but in the week-end I'l post some updated code that can be used as reference for adding comlynx support to homebrews (I'm considering to add this sample code to the game template too).

  • Like 2
Link to comment
Share on other sites

  • 2 months later...

As promised here is a polished and heavily tested version of the comlynx lib I use in 4Ttude and in ... something else tha will be released soon.


You need to build a protocol over it, but I'm uploading right now the code of 4Ttude on my github where you can see the protocol I implemented. It can handle the data communicaton, music change and sync, an trigger Pause and  Reset events on both sides when requested by one of the two consoles connected.


The 4Ttude protocol is only for 2 lynxes, but can be easily extended to more consoles.


It's an asymmetric daisy chain, where the first Lynx to start the connection act as game server and the other one is only a remote controller (other than drawing the screen with the game data sent by the server at every frame).


I think the communication protocol is well coded, the rest of the game not the same, remember that I coded everything in a month while finishing Nutmeg in time for the competition deadline (the code needs a lot of polishing).



Link to comment
Share on other sites

The starting handshake is taken from the comlynx snippet posted by Songbird some time ago.


To simplify things the connection needs to be started by a command of the player when the two lynx are wired (commercial Lynx games polls the comlynx connecton and switch to 2P mode as soon as the connection is phisically established, but IMHO it's a waste of CPU). If there is no comlynx cable the user can abort the connection pressing a key. I know I could check the comlynx state register, but I preferred to keep thing simple (I could improve it).


When a Lynx try to connect it reads the buffer: if it reads a value (I use #1) it kwows to be number #1 in the chain (the slave) then sends a predefined ACK.


if it doesn't receive anything it assumes to be #0 (the master), send a #1 value, then waits the ACK.


The protocol is simple and can occour race conditions, but it's unlikely.  



  • Thanks 1
Link to comment
Share on other sites

These cc65 serial commands use two 256 byte ring buffers for incoming and outgoing traffic. So all data is sent/received using interrupts. Unfortunately this code is very little tested by me. Just by glancing through it I already found out that the SerialStat is never cleared. So any error status remains forever :( 

; Global variables


TxBuffer:    .res    256
RxBuffer:    .res    256
RxPtrIn:    .res    1
RxPtrOut:    .res    1
TxPtrIn:    .res    1
TxPtrOut:    .res    1

SerialStat:    .res    1

The return value SER_ERR_OK means that the data fit in the 256 byte output buffer. If the buffer is full you will get SER_ERR_OVERFLOW and then you have to wait a while before trying a ser_put command again.


The ser_get() and ser_put() interact only with the ring buffers. They never report any communication errors.


There is a separate command ser_status(unsigned char *status) that will tell you if there were errors like RxParityErr|RxOverrun|RxFrameErr|RxBreak.


If you want to detect a clash you should ask for the status. (That was the plan. Sorry about the bug that makes it not work.)


The code will also detect a break signal that force all the receiving units to clear their ring buffers. Sending a break is a bit difficult. I believe the best way is to set the baud rate very low and send 0x00. (So to get a fresh start of a game you could first send a break. I would just pick the last 16 bits of the clock() when I receive the break as my ID. Then every Lynx just send its ID when the receive buffer is empty and last byte of the clock() is 0. Within a second or two you should know who is online. This recognition phase could go on for a while. When everyone is in someone press start game and then we go into a round robin communication method.)


The receiver will also accept all characters even if the parity is wrong. If you really want to check the parity, read the status after you have got the message. After the bug mentioned at the top of the message is fixed. Sorry about the mess.


The best way might be to post a fixed version as a stand alone asm file. It will automatically override the library version if you add it to the project. I try to do it later today.




Link to comment
Share on other sites

22 minutes ago, Nop90 said:

Thank you. But I'd like to know also the hw buffer size. Common UART chips of the Lynx age (16550 for example) use to have only 16 bytes of FIFO buffer.

I highly doubt that they have anything more than a serial register for incoming bits plus a way to transfer the assembled byte to SERDAT.

This implementation is a half-duplex thing. As long as you have something to send you just assume that every interrupt means that the transmit buffer is empty and you can feed in the next byte.

When everything has been sent you assume that every interrupt is for incoming data. See the TxIntEnable and RxIntEnable flags. They are never active at the same time.


        lda     INTSET          ; Poll all pending interrupts
        and     #SERIAL_INTERRUPT
        bne     @L0
        bit     TxDone
        bmi     @tx_irq     ; Transmit in progress
        ldx     SERDAT
        lda     SERCTL
        and     #RxParityErr|RxOverrun|RxFrameErr|RxBreak
        beq     @rx_irq
        tsb     SerialStat  ; Save error condition
        bit     #RxBreak
        beq     @noBreak
        stz     TxPtrIn     ; Break received - drop buffers
        stz     TxPtrOut
        stz     RxPtrIn
        stz     RxPtrOut
        lda     contrl
        ora     #RxIntEnable|ResetErr
        sta     SERCTL
        lda     #$10
        sta     INTRST
        bra     @IRQexit
        lda     contrl
        ora     #RxIntEnable|ResetErr
        sta     SERCTL
        ldx     RxPtrIn
        sta     RxBuffer,x
        cpx     RxPtrOut
        beq     @1
        stx     RxPtrIn
        lda     #SERIAL_INTERRUPT
        sta     INTRST
        bra     @IRQexit

        sta     RxPtrIn
        lda     #$80
        tsb     SerialStat
        ldx     TxPtrOut    ; Has all bytes been sent?
        cpx     TxPtrIn
        beq     @allSent

        lda     TxBuffer,x  ; Send next byte
        sta     SERDAT
        inc     TxPtrOut

        lda     contrl
        ora     #TxIntEnable|ResetErr
        sta     SERCTL
        lda     #SERIAL_INTERRUPT
        sta     INTRST
        bra     @IRQexit

        lda     SERCTL       ; All bytes sent
        bit     #TxEmpty
        beq     @exit1
        bvs     @exit1
        stz     TxDone
        lda     contrl
        ora     #RxIntEnable|ResetErr
        sta     SERCTL

        lda     #SERIAL_INTERRUPT
        sta     INTRST


Link to comment
Share on other sites

  • 2 weeks later...

There is a strange behaviour in the serial driver code.


When two lynx are connected with a Comlynx cable, if you turn on only one of them the code runs slowly, about at half speed.


Turning on the second Lynx everything goes fine, even if you turn off one of the two lynx.


The only thing I do a startup is to load the serial driver and turn on the connection:



Any hint on why this happens? Is there a solution to this?

Link to comment
Share on other sites

Receiving a break empties the ring buffers. There could be a bug here. Perhaps the pointers have to circle through 255 bytes after the break or something? Or perhaps the Lynx that is on believes that it has to send 255 characters to ComLynx?

Link to comment
Share on other sites

  • 2 weeks later...
  • 2 months later...
On 10/8/2019 at 11:23 AM, karri said:

Receiving a break empties the ring buffers. There could be a bug here. Perhaps the pointers have to circle through 255 bytes after the break or something? Or perhaps the Lynx that is on believes that it has to send 255 characters to ComLynx?


@karri could you help me to check if there is a bug or not. I have to fix ol this problem before Carl starts the production of Biniax2 cart. The problem is not blocking, but if I can solve it I'm happier.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...