Shop OBEX P1 Docs P2 Docs Learn Events
Full Duplex Eight Port Serial Driver In C? — Parallax Forums

Full Duplex Eight Port Serial Driver In C?

I'm moving an XMM C program I wrote on the Prop1 over to the Prop2.

I'm using the FlexProp compiler and so far it's going well as I've moved the various Menu displays over and I'm able to invoke them using the Prop2 Console Port (P62 & P63).

Now, it's time to bring additional serial ports into the picture, including an auxiliary diagnostic port, two GPS ports, and three radio ports.

I've been looking at this full duplex 8-Port driver for the Prop2 written in Spin2:

https://www.parallax.com/multiple-serial-port-16-object/

I've compiled and run the demo2, demo4, and demo16 programs using FlexProp and they work fine.

What I would like to do is have the C equivalent functions for these, so I tried using spin2cpp to convert them without success. I got these errors:

C:\Programs\Utility\Spin2Cpp\Sample>spin2cpp --p2 --ccode mpx_demo16.spin2
C:/Programs/Utility/Spin2Cpp/Sample/mpx_fullduplexserial.spin2:189: error: muldiv64 is not a function
C:/Programs/Utility/Spin2Cpp/Sample/mpx_fullduplexserial.spin2:191: error: pinstart is not a function
C:/Programs/Utility/Spin2Cpp/Sample/mpx_fullduplexserial.spin2:205: error: pinclear is not a function
C:/Programs/Utility/Spin2Cpp/Sample/mpx_fullduplexserial.spin2:330: error: waitct is not a function

C:\Programs\Utility\Spin2Cpp\Sample>

Has anyone tried doing this?

Is anyone aware of an 8-Port Full Duplex Serial driver with C functions for the Prop2 that will work under FlexProp or Catalina?

I suppose I could append the existing Spin2 code to my C code and make it work since FlexProp allows mixing of the code types.

But it seems that having pure C code functions to handle the serial I/O on these 8 ports would be much cleaner.

«1

Comments

  • In FlexC, you can just do

    struct __using("mpx_fullduplexserial.spin2") fds;
    

    to include a Spin2 object.

  • @Wuerfel_21 said:
    In FlexC, you can just do

    struct __using("mpx_fullduplexserial.spin2") fds;
    

    to include a Spin2 object.

    Thanks, that simplified things quite a bit.

    For this test I'm attempting to use the standard Prop2 Console Port pins (P62 & P63) to keep things simple before I wander off trying to open a bunch of other ports using different pins.

    I can call this function from C to open the port using FlexProp, but I can't get it to work because of the first variable:

    struct __using("mpx_fullduplexserial.spin2") Console;
    
    #define BAUD 115200
    #define RxDir       0
    #define TxDir       1
    #define SerMode     0
    #define BuffSize    64
    #define ConRxNum    0
    #define ConTxNum    1
    #define ConRxPin    63
    #define ConTxPin    62
    
    void main(void)
    {
     char ConTxBuf[BuffSize];
     char ConRxBuf[BuffSize];
     Console.start(@port_control,ConRxNum,ConRxPin,SerMode,BAUD,@ConRxBuf,@ConRxBuf + 
      BuffSize,ConTxNum,ConTxPin,SerMode,BAUD,@ConTxBuf,@ConTxBuf + BuffSize);
    /*
     Lots of other code to get and put things to this port but not shown here to avoid clutter
    */
    }
    

    As shown below, all of these arguments are self-explanatory except for the p_portctl which is the hub address. And that's where I'm totally lost. I don't even know where to begin when it comes to hub memory management.

    pub start(p_portctl, rxxport, rxpin, rxmode, rxbaud, p_rxstart, p_rxend, txxport, txpin, txmode, txbaud, p_txstart, p_txend)
    '' Start simple serial coms on rxpin and txpin (can be individually configured using openport)
    '' -- p_portctl. hub addr of port_control[0-15] followed by port_params[16]
    '' -- rxport.... receive  port[n]        (0-15) (-1 if not used)
    '' -- rxpin..... receive  pin                   (-1 if not used)
    '' -- rxmode.... %0xx1 = invert rx              (same for txmode)
    ''               %0x1x = invert tx
    ''               %01xx = open-drain/open-source tx
    '' -- rxbaud.... receive  baud 
    '' -- p_rxstart. hub address of start of the receive  buffer
    '' -- p_rxend... hub address of end+1 of the receive  buffer
    
    '' -- txport.... transmit port[n]        (0-15) (-1 if not used)
    '' -- txpin..... transmit pin                   (-1 if not used)
    '' -- txmode.... %0xx1 = invert rx              (same for txmode)
    ''               %0x1x = invert tx
    ''               %01xx = open-drain/open-source tx
    '' -- txbaud.... transmit baud 
    '' -- p_txstart. hub address of start of the transmit buffer
    '' -- p_txend... hub address of end+1 of the transmit buffer
    

    @Cluso99 and @JonnyMac could you provide me with some insight on this hub address variable? Thanks.

  • Cluso99Cluso99 Posts: 18,069

    p_portctl is a pointer (ie address) in hub where the control buffer is located.
    This is a structure which will be used to interface to the mpx driver.

  • I have written a serial driver in C for the P2 that I use. It integrates into the FlexProp libraries allowing it to be used with file descriptors so you can use formatted output.

     #include "serial.h"
    
    #define RX 21
    #define TX 22
    
    FILE *sr;
    
    int main(int argc, char** argv)
    {
        int i;
    
        sr = serial_open(RX, TX, 115200);
    
        i = 1234;
    
        fprintf(sr, "Number: %d\n", i);
    
        while (1)
        {
            _waitms(1000);
        }
    }
    

    Full duplex on the P2 is kind of built into the hardware so it is much simpler to implement then on the P1.

    While my library does not have full duplex in it it can be used as base to built one very simply. What I need to do is add a check function to check if a character is ready and then return with the answer which would be simple enough to add.

    Here is a sample of a full duplex program that reads a BNO080 IMU.

    /**
     * @brief Read BNO080 Orientation device
     * @author Michael Burmeister
     * @date Feburary 15, 2021
     * @version 1.0
     * 
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include "serial.h"
    
    #define FSMRX 40
    #define FSMTX 41
    #define FSMINT 42
    
    struct x {
        int16_t Yaw;
        int16_t Pitch;
        int16_t Roll;
        int16_t Xacc;
        int16_t Yacc;
        int16_t Zacc;
        int8_t res[3];
        uint8_t chksum;
    } Packet;
    
    uint32_t stack[55];
    volatile int Head;
    volatile int Tail;
    uint8_t _Buffer[1030];
    uint8_t Buffer[260];
    FILE *s;
    
    
    int main(int argc, char** argv)
    {
        int i;
        int t;
    
        s = serial_open(FSMRX, FSMTX, 115200);
    
        _cogstart(Receiver, NULL, stack, 50);
    
        _waitms(100);
    
        printf("Size: %d\n", sizeof(Packet));
    
        t = 0;
        while (1)
        {
            i = GetData(Buffer, 256);
            if (i > 0)
            {
                if (Buffer[0] == 0xaa && Buffer[1] == 0xaa)
                {
                    memcpy(&Packet, &Buffer[3], sizeof(Packet)-4);
                }
            }
            if (_getms() > t)
            {
                printf("Roll: %3d, Pitch: %3d, Yaw: %3d X: %d, Y: %d, Z: %d\n",
                 Packet.Roll/100, Packet.Pitch/100, Packet.Yaw/100, Packet.Xacc, Packet.Yacc, Packet.Zacc);
                t = _getms() + 1000;
            }
            _waitms(10);
        }
    }
    
    void Receiver(void *par)
    {
        uint8_t ch;
        Head = 0;
        Tail = 0;
    
        while (1)
        {
            ch = serial_rxChar(s);
            _Buffer[Head++] = ch;
            Head = Head & 0x3ff;
        }
    
    }
    
    int GetData(uint8_t *data, int len)
    {
        int i;
    
        i = 0;
        while (Head != Tail)
        {
            data[i++] = _Buffer[Tail++];
            Tail = Tail & 0x3ff;
            if (i >= len)
                break;
        }
    
        data[i] = 0;
        return i;
    }
    

    Buy adding a check function to the cog function you could monitor N number of serial pins.

    Here is a link to my Library enjoy.

    Mike

    PS: The easiest way to write code is to use Visual Studio Code. It has code completion and you can easily add the compile functions so you never have to leave the environment.

  • @Cluso99 said:
    p_portctl is a pointer (ie address) in hub where the control buffer is located.
    This is a structure which will be used to interface to the mpx driver.

    OK, so it's a pointer to a structure in HubRam.

    But what are the elements of the structure and where is it located in HubRam?

    Is it defined within the driver itself?

    Or do I need to create a structure in C, populate it, assign it to a location in HubRam, then pass a pointer to that address to the Console.start() function?

    And if I have to define, populate, and locate the structure in C, to what location in HubRam do I assign it?

    I got used to working with the Four Port Serial driver in Catalina on the Prop1 where HubRam locations were handled behind the scenes without my intervention, so I guess I've lived a sheltered life in that regard...

  • @iseries said:
    I have written a serial driver in C for the P2 that I use. It integrates into the FlexProp libraries allowing it to be used with file descriptors so you can use formatted output.

     #include "serial.h"
    
    #define RX 21
    #define TX 22
    
    FILE *sr;
    
    int main(int argc, char** argv)
    {
        int i;
    
        sr = serial_open(RX, TX, 115200);
        
        i = 1234;
    
        fprintf(sr, "Number: %d\n", i);
    
        while (1)
        {
            _waitms(1000);
      }
    }
    

    Full duplex on the P2 is kind of built into the hardware so it is much simpler to implement then on the P1.

    While my library does not have full duplex in it it can be used as base to built one very simply. What I need to do is add a check function to check if a character is ready and then return with the answer which would be simple enough to add.

    Here is a sample of a full duplex program that reads a BNO080 IMU.

    /**
     * @brief Read BNO080 Orientation device
     * @author Michael Burmeister
     * @date Feburary 15, 2021
     * @version 1.0
     * 
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include "serial.h"
    
    #define FSMRX 40
    #define FSMTX 41
    #define FSMINT 42
    
    struct x {
        int16_t Yaw;
        int16_t Pitch;
        int16_t Roll;
        int16_t Xacc;
        int16_t Yacc;
        int16_t Zacc;
        int8_t res[3];
        uint8_t chksum;
    } Packet;
    
    uint32_t stack[55];
    volatile int Head;
    volatile int Tail;
    uint8_t _Buffer[1030];
    uint8_t Buffer[260];
    FILE *s;
    
    
    int main(int argc, char** argv)
    {
        int i;
        int t;
    
        s = serial_open(FSMRX, FSMTX, 115200);
        
        _cogstart(Receiver, NULL, stack, 50);
    
        _waitms(100);
    
        printf("Size: %d\n", sizeof(Packet));
    
        t = 0;
        while (1)
        {
            i = GetData(Buffer, 256);
            if (i > 0)
            {
                if (Buffer[0] == 0xaa && Buffer[1] == 0xaa)
                {
                    memcpy(&Packet, &Buffer[3], sizeof(Packet)-4);
                }
            }
            if (_getms() > t)
            {
                printf("Roll: %3d, Pitch: %3d, Yaw: %3d X: %d, Y: %d, Z: %d\n",
                 Packet.Roll/100, Packet.Pitch/100, Packet.Yaw/100, Packet.Xacc, Packet.Yacc, Packet.Zacc);
                t = _getms() + 1000;
            }
            _waitms(10);
      }
    }
    
    void Receiver(void *par)
    {
        uint8_t ch;
        Head = 0;
        Tail = 0;
    
        while (1)
        {
            ch = serial_rxChar(s);
            _Buffer[Head++] = ch;
            Head = Head & 0x3ff;
        }
    
    }
    
    int GetData(uint8_t *data, int len)
    {
        int i;
    
        i = 0;
        while (Head != Tail)
        {
            data[i++] = _Buffer[Tail++];
            Tail = Tail & 0x3ff;
            if (i >= len)
                break;
        }
        
        data[i] = 0;
        return i;
    }
    

    Buy adding a check function to the cog function you could monitor N number of serial pins.

    Here is a link to my Library enjoy.

    Mike

    PS: The easiest way to write code is to use Visual Studio Code. It has code completion and you can easily add the compile functions so you never have to leave the environment.

    Thanks, I'll take a look at your Library.

  • Cluso99Cluso99 Posts: 18,069

    @Wingineer19 said:

    @Cluso99 said:
    p_portctl is a pointer (ie address) in hub where the control buffer is located.
    This is a structure which will be used to interface to the mpx driver.

    OK, so it's a pointer to a structure in HubRam.

    But what are the elements of the structure and where is it located in HubRam?

    Is it defined within the driver itself?

    Or do I need to create a structure in C, populate it, assign it to a location in HubRam, then pass a pointer to that address to the Console.start() function?

    And if I have to define, populate, and locate the structure in C, to what location in HubRam do I assign it?

    I got used to working with the Four Port Serial driver in Catalina on the Prop1 where HubRam locations were handled behind the scenes without my intervention, so I guess I've lived a sheltered life in that regard...

    You have to define the data block. The structure is shown in the example code.
    The reason you pass a pointer to the structure in the driver is because the driver needs to know where this structure (ie a block of hub memory) is located. The driver interfaces to this, and uses it to control which ports are used.

    This is what the structure looks like (from the mpx_multiportserialdriver.spin2 at the end of the file)

    dat
    ' --------------------------------------------------------------------------------------------------
    ' Main hub interface (allows up to 16 uni-directional ports)    ' defined in calling program !!!
    port_control    byte      0[16]                                 '\ port_control (always 16 ports) %atpppppp where...
                                                                    '|   a: 1 = port active; t: 1 = tx, 0 = rx; pppppp: = port pin
                                                                    '|     eg:  byte  1<<7 | 0<<6 | 63  ' active, rx, P63
                                                                    '|     eg:  byte  1<<7 | 1<<6 | 62  ' active, tx, P62
    port_params     long      0[4*MAX_PORTS]                        '| port_params max (16 ports) of 4 longs...  (no need to fill/reserve unused ports)
                                                                    '|   port[n]: (pointers to hub addresses...) p_head; p_tail; p_start; p_end
                                                                    '|     eg:  long  @xbuf_n      ' p_head
                                                                    '|     eg:  long  @xbuf_n      ' p_tail
                                                                    '|     eg:  long  @xbuf_n      ' p_start
                                                                    '/     eg:  long  @xbuf_n_end  ' p_end   (=last+1=p_start+bufsize)
    ' --------------------------------------------------------------------------------------------------
    

    This will usually be followed by the physical buffers for each slot. A slot is a single port (either a transmitter or a receiver - you would need one each for a whole full duplex serial port). So the buffer(s) are one for each transmit or receive port (or pin if you like to think this way). Often these buffers would follow the port_control and port_params bytes.
    This structure is filled/initialised by the following call(s)
    pub start(p_portctl, rxxport, rxpin, rxmode, rxbaud, p_rxstart, p_rxend, txxport, txpin, txmode, txbaud, p_txstart, p_txend)
    or
    pub openport(p_portctl, xport, xpin, xdirn, xmode, xbaud, p_xstart, p_xend) : status | spmode, baudcfg

    In spin, you define the structure like this. You'll need to do this in C.

    var
    ' Define buffers for 4 serial ports... (2 fdx uart serial ports) 
      byte  TXBUF[TXBUF_SIZE]
      byte  RXBUF[RXBUF_SIZE]
      byte  TXBUF2[TXBUF2_SIZE]
      byte  RXBUF2[RXBUF2_SIZE]
    

    and

    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
    'eg             byte      1<<7 | 0<<6 | 63                      '| port[0]: active, rx, P63
    'eg             byte      1<<7 | 1<<6 | 62                      '| port[1]: active, tx, P62
    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
    'eg             long      @xbuf_0                               '| port[0]: p_head
    'eg             long      @xbuf_0                               '|          p_tail
    'eg             long      @xbuf_0                               '|          p_start
    'eg             long      @xbuf_0_end                           '/          p_end   (=last+1=p_start+bufsize)
    ' --------------------------------------------------------------------------------------------------
    
  • Cluso99Cluso99 Posts: 18,069
    edited 2021-07-16 22:26

    Forum software crashed again :(

    So you need to create (reserve) a hub block like this

    port_control    byte  0[16]                ' control (always 16 ports) %atpppppp 
    port_params     long  0[4*16]              ' max (16 ports) of 4 longs...   (note: (no need to fill/reserve unused ports))
    TXBUF           byte  0[TXBUF_SIZE]        ' transmit and receive buffers...
    RXBUF           byte  0[RXBUF_SIZE]
    TXBUF2          byte  0[TXBUF2_SIZE]
    RXBUF2          byte  0[RXBUF2_SIZE]
    TXBUF4          byte  0[TXBUF4_SIZE]
    RXBUF4          byte  0[RXBUF4_SIZE]
    TXBUF6          byte  0[TXBUF6_SIZE]
    RXBUF6          byte  0[RXBUF6_SIZE]
    TXBUF8          byte  0[TXBUF8_SIZE]
    RXBUF8          byte  0[RXBUF8_SIZE]
    TXBUF10         byte  0[TXBUF10_SIZE]
    RXBUF10         byte  0[RXBUF10_SIZE]
    TXBUF12         byte  0[TXBUF12_SIZE]
    RXBUF12         byte  0[RXBUF12_SIZE]
    TXBUF14         byte  0[TXBUF14_SIZE]
    RXBUF14         byte  0[RXBUF14_SIZE]
    
  • @Cluso99 said:
    Forum software crashed again :(

    I'm having problems with the Forum Software regardless of which browser I'm using. I'm getting a lot of "Page Unresponsive" notifications, freezes, and glacial updates on the screen. Maybe Parallax should take a look at this problem and see what's happening. I don't remember it being like this before.

    So you need to create (reserve) a hub block like this

    port_control    byte  0[16]                ' control (always 16 ports) %atpppppp 
    port_params     long  0[4*16]              ' max (16 ports) of 4 longs...   (note: (no need to fill/reserve unused ports))
    TXBUF           byte  0[TXBUF_SIZE]        ' transmit and receive buffers...
    RXBUF           byte  0[RXBUF_SIZE]
    TXBUF2          byte  0[TXBUF2_SIZE]
    RXBUF2          byte  0[RXBUF2_SIZE]
    TXBUF4          byte  0[TXBUF4_SIZE]
    RXBUF4          byte  0[RXBUF4_SIZE]
    TXBUF6          byte  0[TXBUF6_SIZE]
    RXBUF6          byte  0[RXBUF6_SIZE]
    TXBUF8          byte  0[TXBUF8_SIZE]
    RXBUF8          byte  0[RXBUF8_SIZE]
    TXBUF10         byte  0[TXBUF10_SIZE]
    RXBUF10         byte  0[RXBUF10_SIZE]
    TXBUF12         byte  0[TXBUF12_SIZE]
    RXBUF12         byte  0[RXBUF12_SIZE]
    TXBUF14         byte  0[TXBUF14_SIZE]
    RXBUF14         byte  0[RXBUF14_SIZE]
    

    OK, regarding my code. I tried this late last night. It compiled fine but didn't do anything after I ran it:

    //Program Is SerTest.c
    //Last Revision On 16 Jul 2021
    
    struct __using("mpx_fullduplexserial.spin2") Console;
    
    #define P2_TARGET_MHZ 160
    
    #include <propeller.h>
    #include "sys/p2es_clock.h"
    
    #define _BAUD 115200
    
    #define RxDir       0
    #define TxDir       1
    #define SerMode     0
    #define BuffSize    64
    #define ConRxNum    0
    #define ConTxNum    1
    #define ConRxPin    63
    #define ConTxPin    62
    #define RxBuffSize  64
    #define TxBuffSize  64
    
    struct HubSer
    {
     char port_control[16];
     long port_params[64];
     char ConTxBuf[TxBuffSize];
     char ConRxBuf[RxBuffSize];
     char GpsTxBuf[TxBuffSize];
     char GpsRxBuf[RxBuffSize];
     char DiagTxBuf[TxBuffSize];
     char DiagRxBuf[RxBuffSize];
     char CmdTxBuf[TxBuffSize];
     char CmdRxBuf[RxBuffSize];
     char Rad1TxBuf[TxBuffSize];
     char Rad1RxBuf[RxBuffSize];
     char Rad2TxBuf[TxBuffSize];
     char Rad2RxBuf[RxBuffSize];
     char Rad3TxBuf[TxBuffSize];
     char Rad3RxBuf[RxBuffSize];
     char Rad4TxBuf[TxBuffSize];
     char Rad4RxBuf[RxBuffSize];
    };
    
    struct HubSer  SerHub;
    
    void main(void)
    {
     clkset(_SETFREQ, _CLOCKFREQ);
     Console.openport(&SerHub,0,ConRxPin,0,SerMode,_BAUD,(&SerHub)->ConRxBuf,(&SerHub)->ConRxBuf + RxBuffSize);
     Console.openPort(&SerHub,1,ConTxPin,1,SerMode,_BAUD,(&SerHub)->ConTxBuf,(&SerHub)->ConTxBuf + TxBuffSize);
     Console.txChr('T');
     Console.txChr('e');
     Console.txChr('s');
     Console.txChr('T');
    }
    

    I think I'm at a disadvantage because I don't know Spin.

    I wonder if there could be some port conflict because the standard I/O of the compiler is to use P62 and P63, and I'm trying to do the same thing here by calling the driver. But I didn't #include <stdio.h> so that shouldn't be the problem.

    I will try to muddle my way through this over the next few days and see if I can make sense of what is happening.

    I hope there's a path forward on this as I would really like to be able to call your functions from C.

  • Cluso99Cluso99 Posts: 18,069

    Unfortunately I don’t know C well enough to really help you so perhaps someone can chime in.

    The spin routines such as txchar need to be converted to C. Have you done this?

    You may need a delay before you try sending characters.

    Can you try another port rather than the P62-63 paint? Can you attach something to this port?
    If you have a LED and resistor (500 ohm to 4K7 should be fine just to test). Connect this to the tx pin. Then just loop outputting a character. These continuous pulses should light the led so we can see the led light. By delaying between groups of characters we should be able to see flashing.

    What P2 board are you using? Is there a LED on the board we can use for the above test?

    Also, zip all the files and attach it. One of our C experts might take a look and see the problem. Are you using Eric’s FlexProp?

  • I think in order to use mpx_fullduplexserial you also need mpx_multiportserialdriver. The port_control structure is used to pass information between multiportserialdriver and fullduplexserial. Here's a simple demo program in C:

    #include <propeller2.h>
    
    #define MAX_PORTS 16
    #define RX_DIRN = 0
    #define TX_DIRN 1
    #define BUF_SIZE 64
    
    #define RX_PORT_NUM 0
    #define RX1         63
    #define TX_PORT_NUM 1
    #define TX1         62
    
    #define BAUD 230400
    
    enum {
        _clkset = 200000000
    };
    
    struct __using("mpx_fullduplexserial.spin2") ser;
    struct __using("mpx_multiportserialdriver.spin2") mpxcog;
    
    char txbuf[BUF_SIZE];
    char rxbuf[BUF_SIZE];
    struct {
        char control[16];
        long params[4*MAX_PORTS];
    } port;
    
    int cog;
    
    int main() {
        _waitms(2000);
        cog = mpxcog.start(&port);
        ser.start(&port, RX_PORT_NUM, RX1, 0, BAUD, &rxbuf, &rxbuf[BUF_SIZE], TX_PORT_NUM, TX1, 0, BAUD, &txbuf, &txbuf[BUF_SIZE]);
        ser.fstr0("Hello, world!\r\n");
        ser.fstr1("  Serial driver started in cog %d\r\n", cog-1);
        _waitms(2000);
    
        return 0;
    }
    
  • Cluso99Cluso99 Posts: 18,069

    Oh I missed noticing that the mpx_multiportserialdriver was not being started.
    It runs in its' own cog and is pasm code.

  • @ersmith said:
    I think in order to use mpx_fullduplexserial you also need mpx_multiportserialdriver. The port_control structure is used to pass information between multiportserialdriver and fullduplexserial.

    Eric you are 100% correct on this. I didn't include the driver itself. Thank you for catching this. My code is working perfectly now.

    https://www.parallax.com/multiple-serial-port-16-object/

    @Cluso99 and @JonnyMac thank you very much for creating this driver and its various support functions.

    One more function you might consider adding would be txCheck(). It would return the remaining size of the transmit buffer such that if it was 0 the user could opt to have their code go do other things while the buffer is emptying, rather than have the code stop and wait until the buffer is ready. @RossH implemented this function in Catalina which I've found to be very handy on the Prop1. Actually for my application it wasn't just handy, it was essential.

    I can now proceed with transferring my GPS application over from the Prop1 to the Prop2 and have up to 8 full duplex Serial Ports in use. Right now my code has allocated 7 for use.

    Again, many thanks to all of you for getting me across the finish line on this serial port driver.

    I really look forward to putting this driver to work on my Project.

  • Cluso99Cluso99 Posts: 18,069

    This should do the trick (in spin)

    pub txAvail() : count | p
    '' Returns # of characters waiting in tx buffer
    
      p     := txport                                                               ' rx port[n]
      count := long[p_port_params][4*p+0] - long[p_port_params][4*p+1]              ' count = head - tail
      if count < 0
        count := count + long[p_port_params][4*p+3] - long[p_port_params][4*p+2]    ' count += end - start
    
  • @iseries said:
    I have written a serial driver in C for the P2 that I use. It integrates into the FlexProp libraries allowing it to be used with file descriptors so you can use formatted output.

     #include "serial.h"
    
    #define RX 21
    #define TX 22
    
    FILE *sr;
    
    int main(int argc, char** argv)
    {
        int i;
    
        sr = serial_open(RX, TX, 115200);
        
        i = 1234;
    
        fprintf(sr, "Number: %d\n", i);
    
        while (1)
        {
            _waitms(1000);
      }
    }
    

    Full duplex on the P2 is kind of built into the hardware so it is much simpler to implement then on the P1.

    While my library does not have full duplex in it it can be used as base to built one very simply. What I need to do is add a check function to check if a character is ready and then return with the answer which would be simple enough to add.

    Here is a sample of a full duplex program that reads a BNO080 IMU.

    /**
     * @brief Read BNO080 Orientation device
     * @author Michael Burmeister
     * @date Feburary 15, 2021
     * @version 1.0
     * 
    */
    
    #include <stdio.h>
    #include <propeller.h>
    #include "serial.h"
    
    #define FSMRX 40
    #define FSMTX 41
    #define FSMINT 42
    
    struct x {
        int16_t Yaw;
        int16_t Pitch;
        int16_t Roll;
        int16_t Xacc;
        int16_t Yacc;
        int16_t Zacc;
        int8_t res[3];
        uint8_t chksum;
    } Packet;
    
    uint32_t stack[55];
    volatile int Head;
    volatile int Tail;
    uint8_t _Buffer[1030];
    uint8_t Buffer[260];
    FILE *s;
    
    
    int main(int argc, char** argv)
    {
        int i;
        int t;
    
        s = serial_open(FSMRX, FSMTX, 115200);
        
        _cogstart(Receiver, NULL, stack, 50);
    
        _waitms(100);
    
        printf("Size: %d\n", sizeof(Packet));
    
        t = 0;
        while (1)
        {
            i = GetData(Buffer, 256);
            if (i > 0)
            {
                if (Buffer[0] == 0xaa && Buffer[1] == 0xaa)
                {
                    memcpy(&Packet, &Buffer[3], sizeof(Packet)-4);
                }
            }
            if (_getms() > t)
            {
                printf("Roll: %3d, Pitch: %3d, Yaw: %3d X: %d, Y: %d, Z: %d\n",
                 Packet.Roll/100, Packet.Pitch/100, Packet.Yaw/100, Packet.Xacc, Packet.Yacc, Packet.Zacc);
                t = _getms() + 1000;
            }
            _waitms(10);
      }
    }
    
    void Receiver(void *par)
    {
        uint8_t ch;
        Head = 0;
        Tail = 0;
    
        while (1)
        {
            ch = serial_rxChar(s);
            _Buffer[Head++] = ch;
            Head = Head & 0x3ff;
        }
    
    }
    
    int GetData(uint8_t *data, int len)
    {
        int i;
    
        i = 0;
        while (Head != Tail)
        {
            data[i++] = _Buffer[Tail++];
            Tail = Tail & 0x3ff;
            if (i >= len)
                break;
        }
        
        data[i] = 0;
        return i;
    }
    

    Buy adding a check function to the cog function you could monitor N number of serial pins.

    Here is a link to my Library enjoy.

    Mike

    PS: The easiest way to write code is to use Visual Studio Code. It has code completion and you can easily add the compile functions so you never have to leave the environment.

    Mike I'm also taking a close look at your Serial functions as well.

    I like the fact they are in C and thus I can tweak, adjust, and maybe even create a couple of new ones to meet the Project requirements.

    If I assign your functions to a Cog then I should be able to get the full duplex functionality I need to handle all of the I/O traffic in the background without my main program having to micromanage it.

  • Right, the code is all C so you should have no issues. I have added the rxCheck function to my code but have not put it up on Github yet. I am not buffering the output but it should be simple enough to do.

    One got ya maybe that there are not enough file descriptor available to do 8 ports.

    Mike

  • Wingineer19Wingineer19 Posts: 279
    edited 2021-07-19 05:08

    @iseries said:
    Right, the code is all C so you should have no issues. I have added the rxCheck function to my code but have not put it up on Github yet. I am not buffering the output but it should be simple enough to do.

    One got ya maybe that there are not enough file descriptor available to do 8 ports.

    Mike

    A txCheck() function could be quite helpful too, especially if you are using low baud rates and you don't want your code waiting for the TX buffer to empty. That could be fatal in time critical situations.

    Here's how @RossH put it when he added it to Catalina:

    I will add a function called s4_txcheck() which will return the number of bytes available
    in the tx buffer. So if it returns zero, it means the buffer is full, and calling s4_tx() would block.

    This means you can use code like:
    if (s4_txcheck(port)) {
    s4_tx(port, byte);
    }
    else {
    do_something_else();
    }

    On the Prop1 I found this function to be absolutely essential because my GPS receiver could only do a max of 38.4Kbps and I couldn't afford to have the application wait until the TX buffer emptied enough to add another character. That would have killed the realtime functionality.

    OK, now back to the serial port setup and my use of a Prop2 Rev B Eval Board to do this.

    I've defined up to 8 Ports so far and FlexProp compiled fine.

    However, it looks like only 3 ports can be defined and active at a time.

    I have each Port outputting the message "This Is A Test" within a loop that has a 1 second
    delay before outputting the message again.

    If I define and try to use more than 3 Ports, the messages are no longer output on any Port.

    Any way the functions can be tweaked to allow up to 8 simultaneous full duplex ports?

  • Cluso99Cluso99 Posts: 18,069

    I have run 60 ports (ie 30 full duplex connections) by daisy chaining each tx to the next rx so I suspect that you may have a problem. IIRC I used 115200 baud.
    Also you might consider posting your code (zip) so we can see what you're doing.

  • The file table is set at 10 files with stdin, stdout, and stderr taking the first 3 slots.

    That should leave 7 slots available but there maybe others used.

    In file includes/sys/unistd.h the value: #define _MAX_FILES 10

    Otherwise the file system functions of the serial driver will have to be removed to allow as many ports as you need.

    Mike

  • @iseries said:
    The file table is set at 10 files with stdin, stdout, and stderr taking the first 3 slots.

    That should leave 7 slots available but there maybe others used.

    In file includes/sys/unistd.h the value: #define _MAX_FILES 10

    Otherwise the file system functions of the serial driver will have to be removed to allow as many ports as you need.

    Mike

    I changed the MAX_FILES to 20 and it appears to work.

    I've got 8 serial ports open and in use at the same time.

    I could see the first four ports (PortZero thru PortThree) by looking at the blinking LEDs on the Prop2 Eval Board.

    I verified data transmission for ports PortFour thru PortSeven by connecting a scope to the transmit pin for each one.

    I'm delighted!

    Excellent job with your serial port driver! If you want to add buffering or any other features that would be even better.

    Here's my code:

    //Program Is SerTest.c
    //Last Revision On 19 Jul 2021
    
    
    #define P2_TARGET_MHZ 160
    #include <propeller.h>
    #include "sys/p2es_clock.h"
    #include "serial.c"
    
    #define PortZeroRxDPin     63
    #define PortZeroTxDPin     62
    
    #define PortOneRxDPin      61
    #define PortOneTxDPin      60
    
    #define PortTwoRxDPin      59
    #define PortTwoTxDPin      58
    
    #define PortThreeRxDPin    57
    #define PortThreeTxDPin    56
    
    #define PortFourRxDPin     55
    #define PortFourTxDPin     54
    
    #define PortFiveRxDPin     53
    #define PortFiveTxDPin     52
    
    #define PortSixRxDPin      51
    #define PortSixTxDPin      50
    
    #define PortSevenRxDPin    49
    #define PortSevenTxDPin    48
    
    
    FILE *PortZero;
    FILE *PortOne;
    FILE *PortTwo;
    FILE *PortThree;
    FILE *PortFour;
    FILE *PortFive;
    FILE *PortSix;
    FILE *PortSeven;
    
    
    void main(void)
    {
     int Loop=0;    
     clkset(_SETFREQ, _CLOCKFREQ);
     PortZero=serial_open(PortZeroRxDPin,PortZeroTxDPin,115200);
     PortOne=serial_open(PortOneRxDPin,PortOneTxDPin,115200);
     PortTwo=serial_open(PortTwoRxDPin,PortTwoTxDPin,115200);
     PortThree=serial_open(PortThreeRxDPin,PortThreeTxDPin,115200);
     PortFour=serial_open(PortFourRxDPin,PortFourTxDPin,115200);
     PortFive=serial_open(PortFiveRxDPin,PortFiveTxDPin,115200);
     PortSix=serial_open(PortSixRxDPin,PortSixTxDPin,115200);
     PortSeven=serial_open(PortSevenRxDPin,PortSevenTxDPin,115200);
     for(;;)
      {
       fprintf(PortZero,"This Is A Test On PortZero And Loop=%d\r",Loop++);
       fprintf(PortOne,"This Is A Test\r\n");
       fprintf(PortTwo,"This Is A Test\r\n");
       fprintf(PortThree,"This Is A Test\r\n");
       fprintf(PortFour,"This Is A Test\r\n");
       fprintf(PortFive,"This Is A Test\r\n");
       fprintf(PortSix,"This Is A Test\r\n");
       fprintf(PortSeven,"This Is A Test\r\n");
       _waitms(100);
      }
    }
    
  • @iseries said:
    Right, the code is all C so you should have no issues. I have added the rxCheck function to my code but have not put it up on Github yet. I am not buffering the output but it should be simple enough to do.

    One got ya maybe that there are not enough file descriptor available to do 8 ports.

    Mike

    I tried the rxCheck() function you posted on Github but couldn't get it to work.

    If a character is ready I want the function to return it, otherwise return -1. So, I just did a simple modification to your rxChar() function and it worked great:

    int serial_rxCheck(FILE *device)
    {
     int z=0;
     int rxbyte;
     int rx_pin;
     rx_pin = device->state >> 16;
     rx_pin = rx_pin & 0xff;
     z=_pinr(rx_pin);
     if(z == 0) return(-1);
     rxbyte = _rdpin(rx_pin) >> 24; // shift down to byte
     rxbyte = rxbyte & 0xff;
     return(rxbyte);
    }
    
  • JRoarkJRoark Posts: 1,215
    edited 2021-07-19 19:52

    I think the function you are looking for is _rxraw(). It returns -1 if no char is waiting, and 0-255 (the ascii code) if a char is available.

    There is also a _txraw() companion function you may find useful.

  • @JRoark said:
    I think the function you are looking for is _rxraw(). It returns -1 if no char is waiting, and 0-255 (the ascii code) if a char is available.

    There is also a _txraw() companion function you may find useful.

    Correct, but from what I can see those only work on the default serial port, what I call the Console port, which is P62 & P63.

    For my application I'm using up to 8 serial ports, including the Console port, so I needed a way to access each of them from C.

    @iseries (Mike) provided a list of functions to do that by reading from and writing to the smart pins.

    Each serial port is configured as a FILE system, thus the standard file I/O functions can be used for input and output.

    On my Prop2 Eval Board I have 8 serial ports open simultaneously and able to do data transfers. It's really slick.

  • Was trying to make it the same as the fdserial functions on the P1. Should have looked at the documentation.

    rxReady is the function I did which is not the rxCheck. Will fix those to conform.

    Mike

  • @iseries said:
    Was trying to make it the same as the fdserial functions on the P1. Should have looked at the documentation.

    rxReady is the function I did which is not the rxCheck. Will fix those to conform.

    Mike

    The next big challenge will be to add buffering, especially on the receive size.

    If you can't add that to the existing functions, then I will need to do that with my application.

    I suppose I can assign a cog to handle all the serial ports and incorporate ring buffers or something.

    Considering all of the serial traffic taking place, I don't see how it can be done without buffering .

  • It is simple enough to do buffering on the receive side. I already gave you that code.

    void Receiver(void *par)
    {
        uint8_t ch;
        Head = 0;
        Tail = 0;
    
        while (1)
        {
            ch = serial_rxChar(s);
            _Buffer[Head++] = ch;
            Head = Head & 0x3ff;
        }
    
    }
    
    int GetData(uint8_t *data, int len)
    {
        int i;
    
        i = 0;
        while (Head != Tail)
        {
            data[i++] = _Buffer[Tail++];
            Tail = Tail & 0x3ff;
            if (i >= len)
                break;
        }
    
        data[i] = 0;
        return i;
    }
    

    This will buffer up to 1024 bytes of data before the buffer wraps on itself.

    Then you need to start a cog to fill the buffer.

    _cogstart(Receiver, NULL, stack, 50);
    

    Use GetData to empty the buffer.

    Mike

  • Cluso99Cluso99 Posts: 18,069

    My driver does the buffering. Just assign big enough buffers. Each can be a different size.

  • RossHRossH Posts: 5,344
    edited 2021-09-04 04:22

    Hello, @Wingineer19

    I've just released a beta version of Catalina (4.7) which contains the 8 port serial driver. The interface is intentionally the same as the existing 2 and 4 port serial drivers, but with the addition of two new functions you can use to manually open and close the ports at run time if you want. However, you don't need to use these functions because you can configure Catalina to open the ports for you automatically. This means that modifying a program that uses either the existing 2 or 4 port driver to use the new 8 port driver should be trivial. Attached is the latest version of the header file. There are a couple of simple test programs in the demos\serial8 folder.

    rossh

  • @RossH,

    Many thanks for your work on this.

    I also saw your other post under "Catalina" announcing the 4.7 Beta release.

    I downloaded and installed 4.7 and will take it for a spin (no pun intended).

    I like the idea that I can open and close the serial ports on the fly under the Prop2.

    I'll let you know if I see any odd or weird behavior.

  • Wingineer19Wingineer19 Posts: 279
    edited 2021-09-04 21:56

    @RossH,

    OK, I got the 4.7 Beta release installed.

    I modified my Miniplate test program to use the s8 library functions.

    I'm still using Code::Blocks so I just added the -lserial8 by editing the Project->Build Options->Linker Settings and adding -lserial8 to the Other Linker Options box.

    The program compiled fine. I chose the Tools->Download To HubRam And Interact option under Code::Blocks and I was able to pull up the various Menus on the program. Excellent.

    Then, I wanted to upload the program to the Prop2 Eval Board so from the command prompt I typed:
    flash_payload Miniplate.bin and it appeared to upload the program to the board. Or at least it didn't give any errors.

    But when I reboot the P2 nothing ever shows up on my HyperTerminal screen. No Menus, nothing. The Port is open but nothing ever shows up on the screen.

    Maybe it didn't upload correctly? Maybe the .bin file is incorrect? I want to make sure that I don't need to select the EEPROM, Flash, or SD Card Loader options under Code::Blocks like I do for the Prop1.

    If I compiled the program correctly (apparently so since I can upload to HubRam and Interact), and it uploaded correctly to the Board (no errors received), then I have another mystery as to why it doesn't work when using HyperTerminal.

    EDIT1: OK, reviewing the Catalina Prop2 Manual, it certainly appears that I don't need to use any Loader option unless I want to upload to an SD Card, which I don't. I want to program the on-board flash. I've checked the Flash Switch settings on the Board and it appears to be OK. I'm able to upload a program from FlexProp to the Board and execute it, but not having the same luck using Catalina. The investigation continues...

Sign In or Register to comment.