HM2_PKTUART(3)

NAME
hm2_pktuart - functions to access the Mesa FPGA card packeted UARTs

SYNOPSIS

#include <hostmot2-serial.h>
int hm2_pktuart_setup(char* name, int bitrate, rtapi_s32 tx_mode, rtapi_s32 rx_mode, int txclear, int rxclear);
int hm2_pktuart_send(char* name,  unsigned char data[], rtapi_u8* num_frames, rtapi_u16 frame_sizes[]);
int hm2_pktuart_read(char* name, unsigned char data[],  rtapi_u8* num_frames, rtapi_u16 *max_frame_length, rtapi_u16 frame_sizes[]);
int hm2_pktuart_queue_get_frame_sizes(char *name, rtapi_u32 fsizes[]);
int hm2_pktuart_queue_read_data(char* name, rtapi_u32* data, int bytes);
int hm2_pktuart_get_clock(char* name);
int hm2_pktuart_get_version(char* name);
rtapi_u32 hm2_pktuart_get_rx_status(char* name);
rtapi_u32 hm2_pktuart_get_tx_status(char* name);

DESCRIPTION

In this context a "Packeted UART" sends data as a burst of bytes separated by blank space, and receives packets of bytes similarly delimited. Each "packet" of N bytes is sent from, or stored in 32-bit "frames" inside 16-deep FIFOs in the FPGA code.

Unlike the other hostmot2 functions, the hostmot2 uart and pktuart do not create any HAL pins or usable driver code when hostmot2 is loaded. Instead interfaxes are created to allow secondary drivers to use them.

In LinuxCNC v2.8 and earlier the PktUART driver was entirely inactive. In LinuxCNC v2.9 onwards the driver polls the Rx and Tx status registers every servo thread, and these can be read with the functions rtapi_u32 hm2_pktuart_get_rx_status() and rtapi_u32 hm2_pktuart_get_tx_status().

  Accessing the UARTs

The UART functions above can be included in your driver code by ``#include "hostmot2-serial.h"``. This will make the functions above available for use in your own C code.

The UARTs are accessed by name, and the names will be printed to the terminal (or dmesg in the case of RTAI kernel realtime) when the board driver (hm2_eth, hm2_pci etc) is loaded. Internally the UARTs are addressed by index, but the indices are per-card so not unambiguous. Internally the functions all use the private hm2_get_pktuart function which returns the index of the UART and the low level driver instance it belongs to. These functions are not hard- private, you can #include "hostmot2.h" if you need the lower-level functions, but then need to track the board instances yourself.

  Configuring the UART

You should refer to the Hostmot2 "regmap" file for up-to-date register setup information. The latest version will normally be found at https://freeby.mesanet.com/regmap.

You should use the function hm2_pktuart_get_version() to check the module version loaded to the FPGA board. This documentation is valid for Rx v0 / v1 and Tx v0. The return value of the function is 16 * Tx + Rx. If viewed in Hex then 0x01 would indicate Tx v0 and Rx v1. (The latest at the time of writing.)

When reading the Regmap file it should be considered that to an FPGA read and write addresses are not the same. You will see that there are overlaps, in that some bits in the registers have different functions when read or written.

To configure the UART use

int hm2_pktuart_setup(name, bitrate, tx_mode, rx_mode, txclear, rxclear)

bitrate is simply the bitrate (e.g. 9600, 115200 etc).

txmode is built up from the following bits (directly copied from the regmap).

    Bit  17        Parity enable WO
    Bit  18        Odd Parity  WO  (1=odd, 0=even)
    Bits 15..8     InterFrame delay in bit times RW
    Bit  6         Drive Enable bit (enables external RS-422/485 Driver when set) RW
    Bit  5         Drive enable Auto (Automatic external drive enable) RW
                   Drive Enable Auto has priority over Drive Enable (bit 6 is a no-op if bit 5 is set)
    Bits 3..0      Drive enable delay (delay from asserting drive enable to start of data transmit.
                   In Clock Low periods RW Drive enable delay is important to avoid start bit timing errors at high baud rates in RS-485 (half duplex) modes.

A reasonable starting value for txmode is 0x00000A20

rxmode is built from the following:

    Bits 29..22    RX data digital filter (in ClockLow periods)
                   Should be set to 1/2 bit time (or max=255 if it cannot be set long enough)
    Bit  17        Parity enable WO
    Bit  18        Odd Parity  WO  (1=odd, 0=even)
    Bits 15..8     InterFrame delay in bit times RW
    Bit  6         RXMask RO
    Bit  3         RXEnable (must be set to receive packets) RW
    Bit  2         RXMask Enable (enables input data masking when transmitting) RW

For low baud rates 0x3FC0140C will generally work, but the filter bits should really be set according to the actual baud rate.

The function int hm2_pktuart_get_clock(name) is provided to enable calculation of the required filter period. It returns units of Hz.

[Note] It is expected that v2 of Rx will extend the number of bits in the filter definition for better behaviour at low bitrates.

  Direct reads and writes

The function:

int hm2_pktuart_send()

Will always use the hm2_llio_queue_write function where available.

However:

int hm2_pktuart_read()

Will force an immediate read transaction. It may be used in setup and teardown code, but should not be called in the realtime functions as this will cause extra packets to be transmitted. This may be acceptable for PCI cards, but should otherwise be avoided.

  Queued Reads and Writes

In the realtime threads the queued reads and writes should be used. This means that most transactions will be spread over more than one thread period.

rtapi_u32 hm2_pktuart_get_rx_status(name)

rtapi_u32 hm2_pktuart_get_tx_status(name)

These functions will always return the latest status from the most recent data packet from the FPGA. The status should be used to check if any new data has been received, or if the UART has completed the recent transmissions.

The Tx status is encoded as:

    Bit  21        FrameBuffer Has Data RO
    Bits 20..16    Frames to send  RO
    Bit  7         Send busy RO
    Bit  4         SCFIFO Error RO

The Rx status is:

    Bit  21        FrameBuffer has data RO
    Bits 20..16    Frames received RO
    Bit  7         Buffer error (RX idle but data in RX data FIFO) RO
    Bit  6         RXMask RO
    Bit  5         Parity Error RW
    Bit  4         RCFIFO Error RW
    Bit  1         Overrun error (no stop bit when expected) (sticky) RW
    Bit  0         False Start bit error (sticky) RW

Based on the status of the Rx and Tx components reads or writes from the FPGA can then be set up. This is typically a multi-step process:

    1. rxstatus indicates that there are packets of data, but at this point we need to know how big each packet is (and reading two much or two little data from the FIFOs will cause problems).

    2. Queue a read of the frame sizes. hm2_pktuart_queue_get_frame_sizes(name, fsizes[]) On return, the fsizes[] array will have been loaded with the frame sizes (size in bytes). If fsizes are [8] [7] [6] and you only read 1 frame from the data FIFO then on the next call to get_frame_sizes the returned array would be [7] [6].

    3. Wait one thread cycle to get the data. Note that there is no serial latency here, the data is already on the FPGA but we can only know how much data to request once we know the packet size

    4. Queue enough data reads to get all the data frames that the packet is spread over. int hm2_pktuart_queue_read_data(name, data, bytes) On return the data[] array will have been loaded with enough 32-bit frames to include "bytes" bytes.

    5. Parse the data.

  Data Formats

Both the Tx and Rx pack the bytes that are to be read or written in 32-bit "frames" stored in a 16-deep FIFO.

To send the sequence 01, 02, 03, 04, 05, 06 followed by the sequence F1, F2, F3, F3, F5, F6, F7 the registers would be loaded with:

    0x04030201
    0xXXXX0605
    0xF4F3F2F1
    0xXXF7F6F5

(Where X indicates data that will be ignored).

I.e., the data is filled right-to-left and right-justified with consecutive packets not sharing a 32-bit frame.

  Typical Usage

Because the transactions are necessarily split over multiple reads, and some steps will have serial-port latency delays it is recommended to use a state machine in the realtime code where waiting on input is not possible.

    int process(void *arg, long period) {
        static int state = START;
        switch (state) {
            case START:
                // Check for received data
                if (rxstatus & 0x200000) {
                    state = WAIT_FOR_DATA_FRAME;
                    break;
                }
                // No incoming data, so service the outputs
                if (time to send data){
                    hm2_pktuart_send(pktUART_name, some_data);
                    state = WAIT_FOR_SEND_COMPLETE;
                break;
            case WAIT_FOR_SEND_COMPLETE:
                if ( ! (txstatus & 0x80)){  // i.e. the Tx is not busy
                    state = WAIT_FOR_DATA_FRAME;
                }
                break;
            case WAIT_FOR_DATA_FRAME:
                if ( ! ( rxstatus & 0x1F0000)) { // no data yet
                    break;
                }
                // find the frame size
                hm2_pktuart_queue_get_frame_sizes(pktUART_name, fsizes);
                state = WAIT_FOR_FRAME_SIZES;
                frame_inde = 0;
                break;
            case WAIT_FOR_FRAME_SIZES:
            case FETCH_MORE_DATA:
                // This step may need to be iterated if there are multiple frames
                r = hm2_pktuart_queue_read_data(pktUART_name, rxdata, fsizes[frame_index]);
                state = WAIT_FOR_DATA; // Just a one-cycle delay, the data is on the FPGA
                break;
            case WAIT_FOR_DATA:
                parse_data(rxdata);
                if ((fsizes[++frame_index] & 0x3FF) > 0){
                    state = FETCH_MORE_DATA;
                } else {
                    state = WAIT_FOR_RX_CLEAR;
                }
                break;
            case WAIT_FOR_RX_CLEAR:
                if (rxstatus & 0x200000) break;
                state = START;
                break;
        }
    }

PINS

The functions / hostmot2 component do not create any HAL pins.

EXAMPLE

See inuxcnc-dev/src/hal/components/mesa_pktgyro_test.comp for a simple example (which might not work, and uses the deprecated direct reads and writes. mesa_modbus is a better example, but significantly more complex and less instructive because of that.

TESTING

The PktUART can be tested using low-level register writes outside the realtime context using mesaflash. Here is an example bash script:

    # First setup the DDR and Alt Source regs for the 7I96
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1100=0x1F800
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1104=0x1C3FF
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1200=0x1F800
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1204=0x1C3FF
    # Next set the baud rate DDS's for 9600 baud
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6300=0x65
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6700=0x65
    # setup the TX and RX mode registers
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6400=0x00062840
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6800=0x3FC61408
    # Reset the TX and RX UARTS
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6400=0x80010000
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6800=0x80010000
    # load 7 bytes of data into the TX UART
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x54535251
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x58575655
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x64636261
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x68676665
    # Command the TX UART to send 8 bytes twice
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6200=0x08
    mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6200=0x08
    sleep .1
    # display the RX mode reg, RX count, and the data
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6800
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6600
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6800
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6600
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500
    mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500

AUTHOR

Andy Pugh

LICENSE

GPL-2.0+