Link Search Menu Expand Document

libvoxl_io

All serial ports and GPIO on VOXL are controlled by the Sensors DSP (SDSP) which allows for a lot of real-time processing to be offloaded from the Applications Processor for time-sensitive tasks such as handling high data-rate IMUs and Mavlink communication with a PX4 flight controller.

libvoxl_io serves to provide access to these functions from Linux Userspace without requiring the user to build Hexagon DSP Code. That being said, libvoxl_io is the only project for VOXL which is built using the voxl-hexagon docker image and serves as the best starting point for writing your own SDSP programs.

For the remainder of this page we will simply duplicate the documentation already provided in the header voxl_io.h.


Table of contents

  1. RPC Shared Memory
  2. UART
  3. Timing
  4. SPI
  5. IMU FIFO Reader
  6. GPIO
  7. I2C
  8. UART Mavlink Parsing
  9. Source

RPC Shared Memory

/**
 * @brief      allocates memory shared between SDSP and APPS proc
 *
 * @param[in]  bytes  bytes to allocate
 *
 * @return     pointer to memory on success, NULL on failure
 */
uint8_t* voxl_rpc_shared_mem_alloc(size_t bytes);


/**
 * @brief      frees previously allocated RPC shared memory
 *
 * @param      ptr   pointer to memory
 */
void voxl_rpc_shared_mem_free(uint8_t* ptr);


/**
 * @brief      deinitiazes the RPC shared memory system
 */
void voxl_rpc_shared_mem_deinit();

UART

#define UART_J7     9    // BLSP 9  on physical port J7  pins 2&3
#define UART_J10    7    // BLSP 7  on physical port J10 pins 2&3
#define UART_J11    12   // BLSP 12 on physical port J11 pins 2&3
#define UART_J12    5    // BLSP 5  on physical port J12 pins 2&3

#define UART_DATA_BUF_LEN    128    // uart read buffer should be this length

/**
 * @brief      Initializes a UART bus /dev/tty-{bus} at specified baudrate
 *
 *             This is a very generalized function that configures the bus for
 *             8-bit characters, ignores the modem status lines, disables
 *             parity, disables canonical mode, and enables 1 stop bit. More
 *             flexibility in configuration could be added in the future if
 *             necessary. For now this covers the majority of sensors.
 *
 * @param[in]  bus       UART bus number (BLSP #)
 * @param[in]  baudrate  must be one of the standard speeds in the UART spec.
 *                       115200 and 57600 are most common.
 *
 * @return     0 on success, -1 on failure
 */
int voxl_uart_init(int bus, int baudrate);


/**
 * @brief      closes a UART bus
 *
 *             Please make an effort to call this before your process exits to
 *             help ensure a safe cleanup.
 *
 * @param[in]  bus   UART bus number (BLSP #)
 *
 * @return     returns 0 on success, -1 on error.
 */
int voxl_uart_close(int bus);


/**
 * @brief      Sends data out the specified uart port through the SDSP
 *
 *             This will return immediately, even if the data hasn't been sent
 *             out yet. Use voxl_uart_drain if you need to ensure the data has
 *             been sent before continuing. Max write length is currently
 *             untested, UART_DATA_BUF_LEN is a safe limit.
 *
 * @param[in]  bus    UART bus number (BLSP #)
 * @param[in]  data   pointer to data to be sent
 * @param[in]  bytes  number of bytes to send
 *
 * @return     returns number of bytes sent or -1 on error
 */
int voxl_uart_write(int bus, uint8_t* data, size_t bytes);


/**
 * @brief      Reads any and all data available from the UART hardware buffer
 *
 *             This is a non-blocking function call! You do not get to specify
 *             the number of bytes it reads, it will always read out everything
 *             in the hardware buffer. The dataLen argument should be the size
 *             of the data buffer in bytes which should generally be set to
 *             UART_DATA_BUF_LEN (128)
 *
 * @param[in]  bus      UART bus number (BLSP #)
 * @param      data     pointer to read buffer
 * @param[in]  dataLen  length of the read buffer (should be UART_DATA_BUF_LEN)
 *
 * @return     Returns number of bytes read or -1 on error.
 */
int voxl_uart_read(int bus, uint8_t* data, size_t dataLen);


/**
 * @brief      flushes (discards) any data received but not read, or data
 *             written but not sent.
 *
 *             uses tcflush(fd, TCIOFLUSH)
 *
 * @param[in]  bus   UART bus number (BLSP #)
 *
 * @return     0 on success or -1 on failure
 */
int voxl_uart_flush(int bus);


/**
 * @brief      waits for data in the write buffer to be sent before returning
 *
 * @param[in]  bus   UART bus number (BLSP #)
 *
 * @return     0 on success or -1 on failure
 */
int voxl_uart_drain(int bus);

Timing

/**
 * @brief      get the current monotonic time of the sdsp in nanoseconds
 *
 * @return     current monotonic time of the sdsp or -1 on error
 */
int64_t voxl_sdsp_time_monotonic_ns();


/**
 * @brief      get the current realtime time of the sdsp in nanoseconds
 *
 * @return     current realtime time of the sdsp or -1 on error
 */
int64_t voxl_sdsp_time_realtime_ns();


/**
 * @brief      get the current monotonic time of the apps proc in nanoseconds
 *
 * @return     current monotonic time of the apps proc or -1 on error
 */
int64_t voxl_apps_time_monotonic_ns();


/**
 * @brief      get the current realtime time of the apps proc in nanoseconds
 *
 * @return     current realtime time of the apps proc or -1 on error
 */
int64_t voxl_apps_time_realtime_ns();


/**
 * @brief      get the time difference between apps and dsp clocks
 *
 *             sdsp clock is ahead of the apps proc so this offset should be
 *             positive. Note that the SDSP clock is sourced differently than
 *             the apps-proc clock so you may see 10us drift each second between
 *             the two clocks. Don't assume this offset is constant forever!
 *
 *             Represented as offset_ns = sdsp_monotonic_ns - apps_monotonic_ns
 *
 * @return     current monotonic time of the sdsp or -1 on error
 */
int64_t voxl_sdsp_time_offset_ns();

SPI

/**
 * The maximum length of any receive or transmit over SPI bus.
 */
#define DSPAL_SPI_TRANSMIT_BUFFER_LENGTH 512
#define DSPAL_SPI_RECEIVE_BUFFER_LENGTH  512

/**
 * @brief      Initializes a SPI port a specified mode and frequency
 *
 *             For description of the 4 SPI bus modes, see
 *             <https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers>
 *
 *             Frequency is provided in hertz. MPU9250 claims to support up to 20mhz but we observed corrupted data. In practice 10mhz is plenty fast.
 *
 * @param[in]  bus       SPI bus number (BLSP #)
 * @param[in]  bus_mode  0,1,2, or 3
 * @param[in]  freq_hz   The frequency in hz
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_init(int bus, int bus_mode, int freq_hz);


/**
 * @brief      update the speed of the SPI port
 *
 *             Use this is you wish to run the bus slower for some operations
 *             and faster for others. For example register configuration may
 *             want to run more slowly for robustness but read in data quickly.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  freq_hz  The frequency in hz
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_set_freq(int bus, int freq_hz);


/**
 * @brief      closes a SPI bus
 *
 *             Please make an effort to call this before your process exits to
 *             help ensure a safe cleanup.
 *
 * @param[in]  bus   SPI bus number (BLSP #)
 *
 * @return     returns 0 on success, -1 on error.
 */
int voxl_spi_close(int bus);

/**
 * @brief      writes data out the SPI bus
 *
 * data length can't exceed DSPAL_SPI_TRANSMIT_BUFFER_LENGTH (512)
 *
 * @param[in]  bus    SPI bus number (BLSP #)
 * @param      data   pointer to data to be written
 * @param[in]  bytes  number of bytes to send
 *
 * @return     0 on success -1 on failure
 */
int voxl_spi_write(int bus, uint8_t* data, int bytes);


/**
 * @brief      reads specified number of bytes out of a register
 *
 *             You cannot read/write more than DSPAL_SPI_RECEIVE_BUFFER_LENGTH (512)
 *             bytes at a time.
 *
 * @param[in]  bus        SPI bus number (BLSP #)
 * @param      write_buf  data to send out bus
 * @param[in]  write_len  number of bytes to write
 * @param      read_buf   Buffer to write result into
 * @param[in]  read_len   number of bytes to read
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_transfer(int bus, uint8_t* write_buf, int write_len, uint8_t* read_buf, int read_len);


/**
 * @brief      writes a single byte register at specified address
 *
 *             this works by writing the address followed by val which is how we
 *             write registers on the mpu9250 IMU. It may not work on your
 *             sensor.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  address  register address
 * @param[in]  val      The value to write
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_write_reg_byte(int bus, uint8_t address, uint8_t val);


/**
 * @brief      writes a single word (16 bit) register at specified address
 *
 *             this works by writing the address followed by val which is how we
 *             write registers on the mpu9250 IMU. It may not work on your
 *             sensor.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  address  register address
 * @param[in]  val      The value to write
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_write_reg_word(int bus, uint8_t address, uint16_t val);


/**
 * @brief      reads specified number of bytes out of a register
 *
 *             This works by sending the requested address with the MSB set to 1
 *             indicating a read operation on MPU9250 and similar IMUs. It then
 *             reads the specified number of bytes out. This is intended for MPU
 *             series IMUs and may not work with your sensor if it behaves
 *             differently.
 *
 *             If reading chunks of data you should allocate RPC shared memory
 *             with voxl_rpc_shared_mem_alloc(size_t bytes) to use for the
 *             output buffer.
 *
 *             You cannot read more than DSPAL_SPI_RECEIVE_BUFFER_LENGTH (512)
 *             bytes at a time.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  address  register address
 * @param      out_buf  pointer to buffer write the result into
 * @param[in]  length   The length of data to read in bytes
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_read_reg(int bus, uint8_t address, uint8_t* out_buf, int length);


/**
 * @brief      reads a single byte register
 *
 *             This works by sending the requested address with the MSB set to 1
 *             indicating a read operation on MPU9250 and similar IMUs. It then
 *             reads one byte out. This is intended for MPU series IMUs and may
 *             not work with your sensor if it behaves differently.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  address  register address
 * @param      out      pointer to where to write the result
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_read_reg_byte(int bus, uint8_t address, uint8_t* out);


/**
 * @brief      reads a single word (16-bit) register
 *
 *             This works by sending the requested address with the MSB set to 1
 *             indicating a read operation on MPU9250 and similar IMUs. It then
 *             reads two bytes out. This is intended for MPU series IMUs and may
 *             not work with your sensor if it behaves differently.
 *
 * @param[in]  bus      SPI bus number (BLSP #)
 * @param[in]  address  register address
 * @param      out      pointer to where to write the result
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_read_reg_word(int bus, uint8_t address, uint16_t* out);

IMU FIFO Reader

/**
 * @brief      helper function to read out a regularly formed FIFO buffer
 *
 *             This is intended for the MPU series IMU FIFO buffers. The process
 *             consists of first reading the number of bytes available in the
 *             IMU's FIFO as read from the fifo_count register address. This
 *             count should be a multiple of packet_size.
 *
 *             The data (even multiple of packet_size) is then read out of the
 *             fifo_address register and placed into the out_buf buffer.
 *
 *             The user should allocate RPC shared memory with
 *             voxl_rpc_shared_mem_alloc(size_t bytes) to use for the output
 *             buffer with at least the size of the IMU's FIFO. Also provide the
 *             size of this buffer with the dataLen argument so the functions
 *             knows not to write too far and create an overflow. You should
 *             make sure your RPC shared mem and this length are appropriately
 *             sized for the fifo you are reading.
 *
 *             For example the icm42688 has a 2k buffer but also has additional
 *             read cache so it can technically hold 103 packets (2060) bytes.
 *             We use a 2k + 128 = 2170 bytes buffer in voxl-imu-server to be
 *             safe.
 *
 *             The user can specify an optinal minimum number of packets to
 *             read. If greater than 0, the function will not return until the
 *             fifo has at least min_packets available to read. This can help
 *             reduce CPU usage at high sample rates.
 *
 * @param[in]  bus            SPI bus number (BLSP #)
 * @param[in]  count_address  fifo_count register address
 * @param[in]  fifo_address   fifo_data address
 * @param[in]  packet_size    expected bytes per fifo packet
 * @param[in]  min_packets    The minimum number of packets to read before
 *                            returning
 * @param[out] packets_read   The number of packets read
 * @param[out] data           The output buffer (should be RPC shared memory)
 * @param[in]  dataLen        Size of the output buffer.
 * @param[in]  count_speed    SPI speed in hz to read fifo_count register
 * @param[in]  data_speed     SPI speed in hz to read the fifo data register
 *
 * @return     0 on success, -1 on failure
 */
int voxl_spi_read_imu_fifo( int bus, uint8_t count_address, uint8_t fifo_address,\
                            uint8_t packet_size, int min_packets, \
                            int* packets_read, uint8_t* data, int dataLen, \
                            int count_speed, int data_speed);

GPIO

//highest number of GPIO pin which can be used (note that actual available pin count may vary)
#define MAX_NUM_GPIO_PINS     256
#define GPIO_DIRECTION_INPUT  0
#define GPIO_DIRECTION_OUTPUT 1

/**
 * @brief      Initializes a GPIO pin with desired direction and extra flags
 *
 * @param[in]  gpio_pin       GPIO pin number (not BLSP #)
 * @param[in]  direction      Direction : GPIO_DIRECTION_INPUT or GPIO_DIRECTION_OUTPUT
 * @param[in]  flags          Reserved for future use
 *
 * @return     0 on success, -1 on failure
 */
int voxl_gpio_init(int gpio_pin, int direction, int flags);

/**
 * @brief      Read the state of a GPIO pin
 *
 *             The pin must be initialized using voxl_gpio_init before reading its state
 *
 * @param[in]  gpio_pin       GPIO pin number
 * @param[in]  gpio_state_ptr Pointer to the integer where pin state will be written
 *
 * @return     0 on success, -1 on failure
 */
int voxl_gpio_read(int gpio_pin, int * gpio_state_ptr);

/**
 * @brief      Set the state of a GPIO pin
 *
 *             The pin must be initialized using voxl_gpio_init in output mode before setting its state
 *
 * @param[in]  gpio_pin       GPIO pin number
 * @param[out] gpio_state     Desired state of the pin (0=LOW or 1=High)
 *
 * @return     0 on success, -1 on failure
 */
int voxl_gpio_write(int gpio_pin, int gpio_state);

/**
 * @brief      Clean up after using GPIO pin
 *
 * @param[in]  gpio_pin       GPIO pin number
 *
 * @return     0 on success, -1 on failure
 */
int voxl_gpio_close(int gpio_pin);

I2C

#define MAX_NUM_I2C_PORTS 16 //highest number of i2c port (BLSP #)
#define MAX_I2C_TX_BUFFER_SIZE 128

/**
 * @brief      Initializes an I2C port
 *
 *             Note that I2C bus speed and slave ID are sent using voxl_i2c_slave_config
 *
 * @param[in]  bus       I2C bus number (BLSP #)
 *
 * @return     0 on success, -1 on failure
 */
int voxl_i2c_init(uint32_t bus);

/**
 * @brief      Closes an I2C port
 *
 * @param[in]  bus       I2C bus number (BLSP #)
 *
 * @return     0 on success, -1 on failure
 */
int voxl_i2c_close(uint32_t bus);

/**
 * @brief      Configures I2C port for communication with a certain i2c slave ID
 *
 * @param[in]  bus                  I2C bus number (BLSP #)
 * @param[in]  slave_address_7bit   I2C slave address (7bit format, 7bit only (10 bit slave addresses are not supported))
 * @param[in]  bit_rate             I2C bus bit rate (e.g 100000, 400000)
 * @param[in]  timeout_us           Timeout for transfer (in microseconds)
 *
 * @return     0 on success, -1 on failure
 */
int voxl_i2c_slave_config(uint32_t bus, uint16_t slave_address, uint32_t bit_rate, uint32_t timeout_us);

/**
 * @brief      Write data to I2C slave device starting from the specified 8-bit register address
 *
 * @param[in]  bus                  I2C bus number (BLSP #)
 * @param[in]  register_address     Register address to access on the device (note: byte order is LS Byte first if > 8 bits)
 * @param[in]  address_num_bits     Number of bits to use for sending register address (8, 16, 24, or 32)
 * @param[in]  output_buffer        Buffer with data to send to the device
 * @param[in]  length               Number of bytes to send to device (in addition to the register address)
 *
 * @return     0 on success, -1 on failure
 */
int voxl_i2c_write(uint32_t bus, uint32_t register_address, uint32_t address_num_bits, uint8_t * write_buffer, uint32_t write_length);

/**
 * @brief      Read data from I2C slave device starting from the specified 8-bit register address
 *
 * @param[in]  bus                  I2C bus number (BLSP #)
 * @param[in]  register_address     Register address to access on the device (note: byte order is LS Byte first if > 8 bits)
 * @param[in]  address_num_bits     Number of bits to use for sending register address (8, 16, 24, or 32)
 * @param[in]  input_buffer         Buffer to hold the incoming data
 * @param[in]  length               Number of bytes to read from device
 *
 * @return     0 on success, -1 on failure
 */
int voxl_i2c_read(uint32_t bus, uint32_t register_address, uint32_t address_num_bits, uint8_t * read_buffer, uint32_t read_length);
/**
 * @brief      starts a UART mavlink parsing thread on the SDSP
 *
 *             buflen specifies how large of a buffer the SDSP should allocate
 *             internally. The user should allocate their own RPC shared memory
 *             buffer matching this length for the data to be copied out into
 *             late.
 *
 *             There is currenly only one valid flag available:
 *
 *             FLAG_MAVPARSER_ALLOW_CRC_FAILURES
 *
 *             The allow CRC failures flag is useful because the CRC on Mavlink
 *             messages is more than just a message integrity check. It has been
 *             overloaded with a type of message version check and this can be
 *             specific to certain implementations. See the description of
 *             CRC_EXTRA at this link:
 *             https://mavlink.io/en/guide/serialization.html#crc_extra
 *
 * @param[in]  bus       The UART bus
 * @param[in]  baudrate  The baudrate
 * @param[in]  buflen    Buffer length for SDSP to use
 * @param[in]  flags     flags to change behavior
 *
 * @return     0 on success, -1 on failure
 */
int voxl_mavparser_init(int bus, int baudrate, int buflen, int flags);


/**
 * @brief      Copies out any complete parsed mavlnk messages and their status
 *             to the RPC data buffer allocated during the init step.
 *
 *             Each message has a corresponding status written to the user's
 *             status array. The status matches mavlink_framing_t:
 *
 *             MAVLINK_FRAMING_OK=1, MAVLINK_FRAMING_BAD_CRC=2,
 *             MAVLINK_FRAMING_BAD_SIGNATURE=3
 *
 *             Messages are returned to the user even if they are parsed with
 *             bad signatures or bad CRCs since these two errors don't necessary
 *             mean there was an integrity problem.
 *
 *             The CRC on Mavlink messages is more than just a message integrity
 *             check. It has been overloaded with a type of message version
 *             check and this can be specific to certain implementations. If the
 *             mavlink packet structures used by the sender differs from the
 *             version we have built into this library, the CRC may fail. But
 *             once the message is forwarded to another receiver, they may
 *             decode the packet correctly with no CRC error. See the
 *             description of CRC_EXTRA at this link:
 *             https://mavlink.io/en/guide/serialization.html#crc_extra
 *
 *             This library is always built with the latest master branch of the
 *             Mavlink project to keep up with new packets.
 *
 *             The status array should be long enough to hold the single-byte
 *             status of the maxiumum number of mavlink_message_t structs that
 *             can fit in the user's data buffer. See voxl-test-mavparser.c for
 *             an example.
 *
 *             This function will block for up to 1/2 second and will return if
 *             at least one message is ready or the 1/2 second timeout elapsed.
 *
 * @param[in]  bus     The UART bus
 * @param[out] data    pointer to a mavlink_msg_t array to write out to
 * @param      status  pointer to user's status array
 *
 * @return     returns number of mavlink messages read or -1 on failure
 */
int voxl_mavparser_read(int bus, char* data, uint8_t* status);


/**
 * @brief      stop a mavparser thread
 *
 * @param[in]  bus   The uart bus
 *
 * @return     0 on success, -1 on failure
 */
int voxl_mavparser_close(int bus);

Source

Source code available on GitLab.