Digital communication

Libm2k includes bitbang implementations on a variety of protocols.

How to install it?

Windows

In the Select Additional Tasks window, select Install libm2k tools option.

How to build it?

Build and install libm2k. Instruction can be found here. Make sure to enable tools option.

Enable tools on Linux or OSX

~/libm2k/build$
cmake -DENABLE_TOOLS=ON ../

Enable tools on Windows

Check the ENABLE_TOOLS box in CMake GUI.

Protocols

There is a common structure to use for all the protocols when trying to communicate with other device using libm2k:

  • initialize the protocol

  • read/write packets

  • free the resources allocated by initialization

Initialization of any protocol can be split into:

  • specific attributes initialization

  • generic attributes initialization

  • protocol descriptor initialization

The structure that stores all ADALM-2000 specific attributes can be found in the extra header, while all generic attributes are found in the standard header. For example SPI generic attributes are found in libm2k/tools/spi.hpp, while SPI specific attributes of ADALM-2000 are found in libm2k/tools/spi_extra.hpp.

  1. Instantiate and set all parameters of the init structure from the extra header (m2k_protocol_init).

  2. Instantiate and set all parameters of the init structure from the standard header (protocol_init_param). This structure contains a parameter called extra. The value of this parameter is supposed to be a reference to the structure from the extra header.

  3. Instantiate a protocol descriptor and then call the init function that will populate the descriptor with the information found in initial structures. Use this descriptor every time when a writing/reading operation is performed.

SPI

Initialization parameters:

  • max_speed_hz - write/read frequency

  • chip_select - index of any digital pin

  • mode - spi mode

  • extra:

    • clock - index of any digital pin

    • mosi - index of any digital pin

    • miso - index of any digital pin

    • bit_numbering - {LSB | MSB}

    • cs_polarity - {ACTIVE_LOW | ACTIVE_HIGH}

Functions:

  • spi_init - initialize the SPI communication peripheral

    • desc - SPI descriptor

    • param - structure that contains the SPI parameters

  • spi_write_and_read - write and read data to/from SPI

    • desc - SPI descriptor

    • data - buffer with the transmitted/received data

    • bytes_number - number of bytes to write/read

  • spi_remove - free the resources allocated by spi_init

    • desc - SPI descriptor

Examples:

Include headers

#include <libm2k/m2k.hpp>
#include <libm2k/contextbuilder.hpp>
#include <libm2k/tools/spi.hpp>
#include <libm2k/tools/spi_extra.hpp>

Setup SPI

libm2k::contexts::M2k context = libm2k::contexts::m2kOpen("ip:192.168.2.1");

// first step of initialization
m2k_spi_init m2KSpiInit;
m2KSpiInit.clock = 1;
m2KSpiInit.mosi = 2;
m2KSpiInit.miso = 7;
m2KSpiInit.bit_numbering = MSB;
m2KSpiInit.context = context;

// second step of initialization
spi_init_param spiInitParam;
spi_init_param.max_speed_hz = 1000000;
spi_init_param.mode = SPI_MODE_3;
spi_init_param.chip_select = 0;
spi_init_param.extra = (void)&m2KSpiInit;

// third step of initialization
spi_desc desc = nullptr;
spi_init(&desc, &spiInitParam);

Write data

uint8_t data[2] = {0xAB, 0xFF};
spi_write_and_read(desc, data, 2);

Remove the descriptor

spi_remove(desc);
libm2k::contexts::contextClose(context, true);

I²C

Initialization parameters:

  • max_speed_hz - write/read frequency

  • slave_address - 7/10 bit address

  • extra:

    • scl - index of any digital pin

    • sda - index of any digital pin

Functions:

  • i2c_init - initialize the I²C communication peripheral

    • desc - I²C descriptor

    • param - structure that contains the I²C parameters

  • i2c_write - write data to a slave device

    • desc - I²C descriptor

    • data - buffer with the transmitted data

    • bytes_number - number of bytes to write

    • option - I²C transfer mode

      • 1 = 7 bit addressing

      • 3 = 7 bit addressing with repeated start

      • 4 = 10 bit addressing

      • 6 = 10 bit addressing with repeated start

  • i2c_read - read data from a slave device

    • desc - I²C descriptor

    • data - buffer with the received data

    • bytes_number - number of bytes to read

    • option - I²C transfer mode

  • i2c_remove - free the resources allocated by i2c_init

    • desc - I²C descriptor

Examples:

Include headers

#include <libm2k/m2k.hpp>
#include <libm2k/contextbuilder.hpp>
#include <libm2k/tools/i2c.hpp>
#include <libm2k/tools/i2c_extra.hpp>

Setup I²C

libm2k::contexts::M2k context = libm2k::contexts::m2kOpen("ip:192.168.2.1");

// first step of initialization
m2k_i2c_init m2KI2CInit;
m2KI2CInit.scl = 0;
m2KI2CInit.sda = 1;
m2KI2CInit.context = context;

// second step of initialization
i2c_init_param i2CInitParam;
i2CInitParam.max_speed_hz = 100000;
i2CInitParam.slave_address = 0x48;
i2CInitParam.extra = (void)&m2KI2CInit;

// third step of initialization
i2c_desc desc = nullptr;
i2c_init(&desc, &i2CInitParam);

Write and read the data

uint8_t  data_write[] = {0x0B};
uint8_t  data_read[] = {0};
i2c_write(desc, data_write, sizeof(data_write), i2c_general_call | i2c_repeated_start);
i2c_read(desc, data_read, sizeof(data_read), i2c_general_call);

Remove the descriptor

i2c_remove(desc);
libm2k::contexts::contextClose(context, true);

UART

Initialization parameters:

  • baud_rate - write/read frequency

  • device_id - index of any digital pin

  • extra:

    • parity - {NO_PARITY | ODD | EVEN | MARK | SPACE}

    • bits_number - {5 | 6 | 7 | 8}

    • stop_bits - {ONE | ONE_AND_A_HALF | TWO}

Functions:

  • uart_init - initialize the UART communication peripheral

    • desc - UART descriptor

    • param - structure that contains the UART parameters

  • uart_write - write data to UART

    • desc - UART descriptor

    • data - buffer with the transmitted data

    • bytes_number - number of bytes to write

  • uart_read - read data to UART

    • desc - UART descriptor

    • data - buffer with the received data

    • bytes_number - number of bytes to read

  • uart_get_errors - check if UART errors occurred

    • desc - UART descriptor

  • uart_remove - free the resources allocated by uart_init

    • desc - UART descriptor

Examples:

Include headers

#include <libm2k/m2k.hpp>
#include <libm2k/contextbuilder.hpp>
#include <libm2k/tools/uart.hpp>
#include <libm2k/tools/uart_extra.hpp>

Setup UART

libm2k::contexts::M2k context = libm2k::contexts::m2kOpen("ip:192.168.2.1");

// first step of initialization
m2k_uart_init m2KUartInit;
m2KUartInit.bits_number = 8;
m2KUartInit.parity = NO_PARITY;
m2KUartInit.stop_bits = ONE;
m2KUartInit.context = context;

// second step of initialization
uart_init_param uartInitParam;
uartInitParam.device_id = 0;
uartInitParam.baud_rate = 9600;
uartInitParam.extra = (void)&m2KUartInit;

// third step of initialization
uart_desc desc = nullptr;
uart_init(&desc, &uartInitParam);

Write data

uint8_t dataWrite[] = {'A', 'D', 'I'};
uart_write(desc, dataWrite, sizeof(dataWrite));

Remove the descriptor

uart_remove(desc);
libm2k::contexts::contextClose(context, true);

Libm2k and no-OS drivers

Analog Devices offers a variety of No-OS drivers for a variety peripherals in a hardware agnostic format. These drivers can be interfaced with the libm2k in order to use the M2K as a master to configure/use the peripheral. Documentation for the drivers available at no-OS drivers list.

In order to integrate libm2k with no-OS drivers some standard steps must be followed:

  • drivers inclusion

  • libm2k headers inclusion

  • writing the logic

    • C++ file

    • drivers included as external C

Including all required driver files in the project is the first step in integrating libm2k with no-OS drivers.

The drivers will include some protocols, for example “i2c.h”. The afferent header of libm2k, libm2k/tool/i2c.hpp, does not match with the header included by the drivers (“i2c.h”). In order not to modify the drivers we are going to create a header named after the protocol (“i2c.h”) in which the libm2k header will be included. For example create the “i2c.h” header in which you will include libm2k/tools/i2c.hpp.

Each file that will call libm2k methods should be a C++ file. The inclusion of any driver in a C++ file has to be marked as an external C file inclusion.

Example:

AD5592r can be independently configured as DAC outputs, ADC inputs, digital outputs, or digital inputs. In this example we are going to generate ascending voltage values, communicating with the chip using SPI.

  • Hardware configuration (ADALM-2000 <- -> AD5592r):

    • SCK: DIO_0 <- -> P4

    • MOSI: DIO_1 <- -> P2

    • MISO: DIO_2 <- -> P3

    • SS: DIO_3 <- -> P1

    • GND: GND <- -> P5

    • VLOGIC: V+ -> P6

    • Oscilloscope: 1+ <- I/O0

  • Project structure:

    • ad5592r-base.h

    • ad5592r-base.c

    • ad5592r.h

    • ad5592r.c

    • delay.h

    • delay.c

    • spi.h

    • i2c.h

    • main.cpp

  • Get all AD5592r drivers from drivers/adc-dac/ad5592r

  • Get delay header from include/delay.h

  • Implement mdelay function from delay.h

#include "delay.h"
#include <unistd.h>

void mdelay(uint32_t msecs) {
    usleep(msecs * 1000);
}

Create spi.h and i2c.h headers (we are not going to use I²C in this example, but AD5592r drivers include I²C header). In each file include the afferent header of libm2k.

//spi.h
#include <libm2k/tools/spi.hpp>
//i2c.h
#include <libm2k/tools/i2c.hpp>

Create main.cpp file:

#include <libm2k/m2k.hpp>
#include <libm2k/contextbuilder.hpp>
#include <libm2k/analog/m2kpowersupply.hpp>
#include <libm2k/analog/m2kanalogin.hpp>
#include <libm2k/tools/spi_extra.hpp>

extern "C" {
    #include "ad5592r.h"
}
extern struct ad5592r_rw_ops ad5592r_rw_ops; //default operations

int main()
{
    struct ad5592r_init_param init_param;
    init_param.int_ref = true; // use the internal reference

    struct ad5592r_dev device;
    device.ops = &ad5592r_rw_ops; //initialize the operations

    libm2k::contexts::M2k *context = libm2k::contexts::m2kOpen("ip:192.168.2.1");
    libm2k::analog::M2kPowerSupply *powerSupply = context->getPowerSupply();
    libm2k::analog::M2kAnalogIn *analogIn = context->getAnalogIn();

    std::cout << "Calibrating . . ." << std::endl;
    context->calibrateADC();

    powerSupply->enableChannel(0, true);
    powerSupply->pushChannel(0, 5); // power up the chip

    // first step of initialization
    m2k_spi_init m2KSpiInit; // m2k specific attributes of SPI
    m2KSpiInit.clock = 0;
    m2KSpiInit.mosi = 1;
    m2KSpiInit.miso = 2;
    m2KSpiInit.bit_numbering = MSB;
    m2KSpiInit.context = context;

    // second step of initialization
    spi_init_param spiInitParam; // generic attributes of SPI
    spiInitParam.max_speed_hz = 2500000;
    spiInitParam.mode = SPI_MODE_2;
    spiInitParam.chip_select = 3;
    spiInitParam.extra = (void*)&m2KSpiInit;

    // third step of initialization
    spi_init(&device.spi, &spiInitParam); // initialize SPI communication

    device.channel_modes[0] = CH_MODE_DAC; // set the first channel as a DAC
    device.num_channels = 1; // enable just the first channel
    ad5592r_init(&device, &init_param ); //initialize AD5592r

    for(int i = 0; i < 0xfff; i+=0x0f) {
        ad5592r_write_dac(&device, 0, i); // generate voltage
        std::cout << analogIn->getVoltage(0) << std::endl; //read voltage
    }

    libm2k::contexts::contextClose(context, true);
}

Compile the project:

gcc -c delay.c
gcc -c ad5592r-base.c
gcc -c ad5592r.c
g++ -c main.cpp
g++ -o example delay.o ad5592r-base.o ad5592r.o main.o -lm2k
./example