Shop OBEX P1 Docs P2 Docs Learn Events
Different/best ways to communicate with I/O (serial/vga/lcd/keyboard) driver — Parallax Forums

Different/best ways to communicate with I/O (serial/vga/lcd/keyboard) driver

Premise: The actual driver is in its' own cog (cpu core) most likely running pasm (assembler).

So, what is the best/easiest way to interface to this/these drivers from a user program (spin, pasm, c, basic, python, or other) running in a different cog?

Can we substitute the driver without changing the user program? Hopefully even without re-compiling the program???

There are a couple of solutions carried over from P1...

FullDuplexSerial (FDX) uses a common hub buffer, typically of 16 bytes, with a head pointer and a tail pointer although it's more of an offset than a pointer. There are two sets in FDX, one for transmit and one for receive. The buffer starts as empty where both the head and tail are "0". As the master (the user program for transmit, and the driver for receive) puts a character into the buffer, it increments the head. The slave (the driver for transmit, and the user program for the receive) waits for the head and tail to be different (indicates a character in the buffer), and then reads the character from the buffer at the tail (offset), increments the tail, and if it exceeds the buffer length it is reset to zero.

This is a simple mechanism where no real processing of the characters is done within the driver, meaning no character functions/manipulations/expansions (eg string. hex, decimal, etc) are done within the driver. Depending on the driver, control character interpretation could be performed within the driver.

Any character functions/manipulations/expansions (eg string. hex, decimal, etc) were done by the user program calling a spin interface (and code) residing within the driver's code, but not within its' cog pasm code. This meant that the call had to be made from spin, meaning the users program had to also be in spin, and compiled as part of the users spin program. This meant a different mechanism, and code and driver, was necessary for other programming languages, such as C and basic.

VGA/LCD/Video drivers typically used a different mechanism where the driver typically consisted of spin calls, and in spin, and compiled as part of the users spin program. This spin code typically handled any screen clearing, scrolling, etc. The screen buffer was in hub and was manipulated by these spin routines. The pasm driver cog merely rendered the video output to the VGA/LCD/Video hardware from the hub screen buffer, rendering with a font where necessary.

PS2 Keyboard - I think it used a buffer with head and tail like FDX. I'm not sure and haven't checked. I do know the pasm cog driver section did the conversion to characters from the keypresses it received.

Problems - These differing methods, as well as the fact that the spin interfaces were named differently (eg OUT vs TX etc) meant substituting one driver for another was complex. Adding to that, the main compiler (PropTool) did not permit condition compilation, so you just couldn't write hardware independent code easily. Luckily for us, Brad and Mark produced bst and homespun although its' use wasn't as widespread as it could have been.

Dracula wrote some code that could load "coglets" which permitted some driver substitution but other than his own use it didn't go anywhere. I wrote my Propeller OS that could compile using either FDX or 1-pin TV and 1-pin Keyboard, using an indirection interface. Again, there was little use outside my own.

What do we have for P2 now?
JonnyMac has written an FDX equivalent for P2 jm_fullduplexserial.spin2 together with a string handler jm_nstr.spin2. His fdx uses the buffer with head and tail mechanism.

I wrote the serial/monitor/debugger in the P2 ROM. It uses hubexec calls and 16 registers in the user program. This can be run from any language, provided you can reserve the 16 cog register space. I have re-written this to be reloadable pasm hubexec code which can again be called from any language provided you can reserve the 16 cog registers. I'm sure (with overheads) I could remove this constraint.
It has all the inbuild functions string, hex, dump (a line of memory dump including ascii), read, readline, etc.
I started making this a standalone pasm cog driver but abandoned this as I'm unconvinced anyone will use it beside myself.

Where to from here?
Do we just go down the same old P1 path where you need to modify your program and create a different version?
Or do we want to get smarter and use some hardware independence?
And if yes, how?

Comments

  • I do this all the time and the driver is independent of the app or even of the hardware in that it could be a serial LCD etc. But I use vectors for input and output and I have total flexibility because that vector might point to a simple stub of code that accesses a mailbox or it might just write to the smartpin, or it might call some larger section of code from the calling cog etc. But one thing I always do is treat the driver as if it is a physically separate and remote serial device. That way the app will not have special hooks in it to check buffers etc as Spin drivers so often do, but instead will always work even if you change the "device" because it appears as a serial device.

    In Tachyon/TAQOZ there are two vectors "uemit" and "ukey" and words like KEYB and VGA and CON and File I/O will simply change these vectors. My "app" code just outputs or inputs from any of these etc so if I were to dump memory for instance I could redirect to a file instead and then back to the console.

    That's my 2cents. Do you have change?
  • Cluso99Cluso99 Posts: 18,069
    edited 2020-11-18 04:01
    I do this all the time and the driver is independent of the app or even of the hardware in that it could be a serial LCD etc. But I use vectors for input and output and I have total flexibility because that vector might point to a simple stub of code that accesses a mailbox or it might just write to the smartpin, or it might call some larger section of code from the calling cog etc. But one thing I always do is treat the driver as if it is a physically separate and remote serial device. That way the app will not have special hooks in it to check buffers etc as Spin drivers so often do, but instead will always work even if you change the "device" because it appears as a serial device.

    In Tachyon/TAQOZ there are two vectors "uemit" and "ukey" and words like KEYB and VGA and CON and File I/O will simply change these vectors. My "app" code just outputs or inputs from any of these etc so if I were to dump memory for instance I could redirect to a file instead and then back to the console.
    I do similar/same in my P1 OS, with the exception I don't have/allow for the input or output to be a file. I've always felt that file I/O is different to terminal I/O even tho I know *nix always treats them the same.

    This is my _STDIN spin object
    VAR
      long  pRENDEZVOUS                                     'stores a pointer to the hub mailbox
    
    PUB start(rendezvous)
      pRENDEZVOUS := rendezvous                             'get rendezvous location
    
    PUB in : c
    '' Wait for an input character
    
      repeat until c := long[pRendezvous]                   'wait until the mailbox has a character
      long[pRendezvous]~                                    'clear the mailbox
      c &= $FF                      '$100 -> $00            'extract lower 8 bits (byte)
    
    PUB peek
    '' Returns a 0 if the buffer is empty,
    '' else next character (+$100) but doesn't remove from the buffer.
    '' The user routine must extract the 8 bits as the whole long is passed.
    
      return long[pRendezvous]                              'return ALL bits (long) in the mailbox
    
    and my _STDOUT spin object
    VAR
      long  pRENDEZVOUS
    
    PUB start(rendezvous)
      pRENDEZVOUS := rendezvous                             'get rendezvous location
    
    PUB out(c)
    '' Print a character
    
      c |= $100   '0.FF -> 100..1FF                         'add bit9=1 (allows $00 to be passed)
      repeat while long[pRENDEZVOUS]                        'wait for mailbox to be empty (=$0)
      long[pRENDEZVOUS] := c                                'place in mailbox for driver to act on
    

    It is a single character hub mailbox each way, so the driver must do any required buffering tho there is usually plenty of time to do this, and peel it off as required.
    That's my 2cents. Do you have change?
    Sorry, I only work/round with 5c minimum ;)
  • Wuerfel_21Wuerfel_21 Posts: 5,068
    edited 2020-11-18 14:20
    For VentilatorOS on P1, I wrote a TV driver that reads from a mailbox and does the scrolling, screen clearing and even the terminal bell all in one cogs worth of PASM. On P2 the video code becomes a lot smaller, so it'd work even better there.

    So I think for simple terminal I/O, the mailbox approach holds worth: Compatible with any language, pushes the buffering onto the drivers, pushes the formatting into the application. Just need to find some standard for these...

    Here's my arbitrary proposal:
    - word[$7BFF0] for input mailbox, word[$7BFF2] for output mailbox. entire area from $7BFF0 to $7BFFF is reserved for future use.
    - All characters get taken +1 when placed in a mailbox to be able to pass $00.
    - Characters greater than $FF are reserved for future use and must be ignored by output drivers when emitted by the application and ignored by the application when emitted from an input driver.
    - Input drivers must not emit carriage returns, only line feeds
    - Applications should not emit carriage returns, output drivers should ignore them.
    - output drivers should support PST-style control codes (because easy to implement). 0 may be interpreted as "clear screen", but applications should use 16 for that.
    - output drivers may or may not support ANSI escape codes.
    - input drivers may report cursor movement using 3 for left, 4 for right, 5 for up and 6 for down. All other non-printable characters are to be ignored by applications.

    This doesn't really cover where the drivers may keep their larger buffers. OTOH, there's 2K LUT RAM per cog and always reserving more than 2K hub would be wasteful, anyways. 2K is enough for an 80 column display, so it's probably fine.
Sign In or Register to comment.