Shop Learn
P2 Serial and LCD (60x40 text) Drivers using a common Formatting Object and Buffers (superceded) — Parallax Forums

P2 Serial and LCD (60x40 text) Drivers using a common Formatting Object and Buffers (superceded)

Cluso99Cluso99 Posts: 17,716
edited 2021-02-13 08:37 in Propeller 2

Replaced by this thread - because I changed the way it is implemented

This code is still valid. although it will be incompatible with the newer objects which have been renamed to avoid confusion

The title says it all!
This post brings together the following threads

P2 Multiport x16 Serial Driver working (based on JonnyMac's FullDuplexSerial)

4.0in TFT SPI 480x320 LCD ST7796 Driver and TCS2046 touchscreen support

New Generation of Low Level Driver for P2

I have been working for years on both P1 and P2 to bring together the possibility of utilising a standard buffer interface that decouples the physical driver code which usually resides in it's/their own pasm cog(s), and the formatting object that converts strings, numerics, etc into a single object that has consistent named methods and can be used across most character drivers (ie not microSD etc).

For the formatting object "mpx_fullduplexserial.spin2", I have taken @JonnyMac 's fantastic jm_fullduplexserial object to use as the base. I have tidied up a few naming methods (to include tx and rx for the most used ones) and added a few extras (txASCII and txDump, along with rxStr - a method to receive CR terminated strings and return a zero-terminated string). @Kaio has made some great suggestions too although at present they have not been included due to timing. The big thing that I did was to bring in a more common buffer pointer mechanism that can be carried over to the pasm drivers.

I have written a new multiport serial driver "mpx_multiportserialdriver.spin2"- the base version supports up to 16 serial pins in any mix of transmitters and receivers, on any pins, and any baud (note I said baud as it is not baud rate - a pet peeve of mine to fix incorrect usage). I also have a variant that caters for 64 serial pins although I don't expect much use!

Now today, I also release a new LCD SPI Text Driver "lcd_textdriver_480x320.spin2". This driver supports my standard formatting object, so all the string, numerics, dump, etc features of the formatting are supported. It utilises the standard buffering mechanism I've formulated. It's an extension to the head and tail method originally used by Chip in his P1 fullduplexserial object.

The demo "lcd_demo.spin2 supports a full duplex serial port (2 port/pins as a pair) and the lcd port. Formatted messages can be sent to both serial and LCD at will, using the duplicated, but identical formatting object as described above.

This brings me to the ultimate objective. Since the drivers have been fully decoupled from their calling and formatting routines, it is now feasible to substitute the drivers when necessary. Once these drivers can be compiled and loaded as separate entities, we will have true on-the-fly indirection on our little micro.

The LCD Driver does not have scrolling working as yet. I kludged a version over on the LCD thread but I'm still investigating other ways. At present I'm using an 8x8 font which gives 60 columns by 40 rows in landscape format. I have used a 6x8 font (80 columns by 40 rows) but it's not present as an option here - again see the LCD thread for code. I was also looking at an 8x12 font (from @baggers) but nothing yet.

The LCD I use is a 4.0" TFT SPI 480x320 V1.0 (color) which has at it's heart an ST7796 chip which is similar to the ILI9163 chip on much smaller 160x160 color LCDs. This LCD is available in smaller versions too, as well as parallel and arduino versions.
Mine also has a Touch Pad chip although I haven't used it yet. @mwroberts has kindly done a touch driver for P2 and he posted code on the LCD thread so check it out.
There are probably a few tweeks I'll need to do to make it more user friendly to call, but it's not too bad really. The main part is to look at the demo file.

BTW There's a Quick Bytes for the multiport serial object and it describes the operation in a bit more detail.
Multiple Serial Port (8 UART – 16 Tx/Rx) Object

Note: SEND and RECV have not been used. I do not feel they adequately support this mechanism. There has been discussion on the abovementioned threads. You can read this if in doubt.

Currently only tested on FastSpin v5.0.8 although the multiportserial works on pnut and proptool IIRC, so no reason this shouldn't. I'm working the next couple of days.

Enjoy B)


  • Cluso99Cluso99 Posts: 17,716
    edited 2021-02-06 20:27

    This is the buffer and support mechanism..

    dat { common port buffers }
    ' --------------------------------------------------------------------------------------------------
    ' Main hub interface (allows up to 16 uni-directional ports)
    port_control    byte      0[16]                         '\ control (always 16 ports) %atpppppp where...
                                                            '| --  a: 1 = port active, t: 1 = tx, 0 = rx, pppppp: = port pin
    port_params     long      0[4*MAX_PORTS]                '| max (16 ports) of 4 longs...   (note: (no need to fill/reserve unused ports))
                                                            '/ --  port[n]: p_head, p_tail, p_start, p_end
    ' --------------------------------------------------------------------------------------------------

    This is a fixed set of 16 bytes, one for each pin/port. It is used for serial configuration of each pin/port. It is read by the multiportserial driver as one block of 4 longs using SETQ which is extremely efficient.
    Each port byte conveys a bit to indicate if the port/pin (slot) is active, a bit to indicate whether it is a transmit or receive pin, and 6 bits for the pin number.

    Currently these follow immediately after the port_control. There are 16 sets of 4 longs for each port/pin (slot). This is the common mechanism to control the hub buffers and there is one for each driver transmitter and another for the receiver. The driver can use any of the 16 slots - it is configured on coginit to select one or more of these slots, depending upon how many slots it is currently supporting. This may change during operation (not yet supported)/operational).

    The four longs are absolute hub addresses pointing to the head, the tail, and the start and end of the buffer this slot supports. The buffer may be anywhere in hub (ie it does not need to follow the port_params).

    The reasoning behind using absolute hub addresses, and having start and end (end is end+1 = start + size) is for speed of the driver. This permits the driver to support many interfaces concurrently in one cog while limiting the mathematics involved in using offsets as was used in fullduplexserial.

    My LCD Driver uses one of these slots for the pointers to the buffer it uses. As stated above, the buffer is located in hub as is a series of bytes. I recommend you use long align and and a multiple of 4 for its' size although this is not mandatory. It is not limited in size being a two's factor, and it can be different for transmit and receive. Different buffers do not need to be adjacent. Buffers are defined in the callers' program although I am wanting to pre-define them as absolute addresses in an included CON object.

    Here is the code to put a character into the buffer

    pub txChr(chr) | n, p, head, new
    ' Print a character (optionally strip <lf>)   
    ' -- moves char into buffer if space avail, else wait
      if (chr == LF) and (tx_striplf == 1)
        return                                                      ' strip <lf>
      p     := txport                                               ' tx port[n]
      head  := long[p_port_params][4*p+0]                           ' head
      new   := head + 1                                             ' new=head+1
      if new == long[p_port_params][4*p+3]                          ' new=end? (end of buf?)
        new := long[p_port_params][4*p+2]                           ' y: new=start
      repeat while new == long[p_port_params][4*p+1]                ' new=tail? (wait while buf full)
      byte[head] := chr                                             ' place chr into buffer at head
      long[p_port_params][4*p+0] := new                             ' update head

    And here is the code to take a character from the buffer

    pub rxChr() : chr | p, head, tail, new
    '' Pulls byte from receive buffer if available
    '' -- will wait if buffer is empty
      p     := rxport                                                               ' rx port[n]
      repeat while long[p_port_params][4*p+0] == long[p_port_params][4*p+1]         ' wait while buffer empty (head=tail?)
      tail  := long[p_port_params][4*p+1]                                           ' tail
      chr   := byte[tail]                                                           ' get a char
      new   := tail + 1                                                             ' update tail pointer
      if new == long[p_port_params][4*p+3]                                          ' new=end? (end of buf?)
        new := long[p_port_params][4*p+2]                                           ' y: new=start
      long[p_port_params][4*p+1] := new                                             ' update tail
      if (rx_echo == 1) and (0 <= txport) and (txport < MAX_PORTS)
        txChr(chr)                                                                  ' echo

    This is the pasm code used by the multiportserial driver (takes a character from the buffer for transmit)

    ' xmit serial...
    tx_serial       rdpin     chr, xpin                     wc      ' check busy flag?
        if_c        ret                                             ' y: return if busy
    ' get port[n] params...
                    mov       ptrb, portnum                         ' current port[n]  (0-15)
                    shl       ptrb, #4                              ' *4*4 = offset to current port_params[n]
                    add       ptrb, p_params                        ' add base
                    setq      #4-1                                  ' read...
                    rdlong    p_head, ptrb                          ' ... current port_params[n] (4 longs: head/tail/start/end)
    ' anything to xmit...
                    cmp       p_head, p_tail                wz      ' byte(s) to tx?
        if_e        ret                                             ' n: return if nothing to xmit
    ' write serial...
                    rdbyte    chr, p_tail                           ' chr := txbuf[tail]    (read char from buffer)
                    wypin     chr, xpin                             ' uart[tx] := chr       (write char to uart)
                    add       p_tail, #1                            ' p_tail++
                    cmp       p_tail, p_end                 wz      ' end of buf?
        if_e        mov       p_tail, p_start                       ' wrap
        _ret_       wrlong    p_tail, ptrb[1]                       ' write tail back to hub

    And the pasm code used by the multiportserial driver (puts a character into the buffer from receive)

    ' recv serial...
    rx_serial       testp     xpin                          wc      ' anything waiting?
        if_nc       ret                                             ' n: abort if nothing
    ' get port[n] params...
                    mov       ptrb, portnum                         ' current port[n]  (0-15)
                    shl       ptrb, #4                              ' *4*4 = offset to current port[n] params
                    add       ptrb, p_params                        ' add base
                    setq      #4-1                                  ' read...
                    rdlong    p_head, ptrb                          ' ... current port[n] params (4 longs: head/tail/start/end)
    ' read serial...
                    rdpin     chr, xpin                             ' chr := uart[rx]       (read char from uart)
                    shr       chr, #24                              ' align lsb             (get into bottom 8-bits)
                    wrbyte    chr, p_head                           ' rxbuf[head] := ch     (write char into buffer)
                    add       p_head, #1                            ' p_head++
                    cmp       p_head, p_end                 wz      ' end of buf?
        if_e        mov       p_head, p_start                       ' wrap
        _ret_       wrlong    p_head, ptrb[0]                       ' write head back to hub

    Enjoy B)

  • Cluso99Cluso99 Posts: 17,716

    Update 07-Feb02021:

    • Added fast fdx open method for serial port(s)
    • Added home and clear screen support in LCD driver
    • Adjusted LF operation to also set column=0
    • Still no scrolling so at end of page row=0

    Code posted in first post. Enjoy B)

  • Cluso99Cluso99 Posts: 17,716

    Update 10-Feb-2021:
    Code posted in first post

    • Added alternative scrolling support
    • No cursor as yet


    • Scrolling using the LCD hardware chip does not work in landscape mode :(
    • I haven't been able to read back the contents of the LCD memory to use in scrolling.

    After each block read it's necessary to issue a hardware reset command and this takes some time. Add to that, the read needs to be done in blocks as you're reading 16-bits per pixel and there are 480*320 on this display, a few seconds of screen flicker/blanking isn't a solution.
    So what I've done is every time the cursor advances to a new line, I clear this line and the next. When the cursor goes below the screen, I reposition the cursor to the home position. This is the best I can do.
    However, hardware scrolling does work in portrait mode but then I don't get enough characters on a line.

Sign In or Register to comment.