Getting Started with C++

Library Description

The C++ libraries (C++11 standard) contain all of the required code to form and decode communication packets. They also contain tools for buffering packets until ready for transmission on your hardware and for storing received packets until parsing.

Each communication client object is capable of forming packets to send get, set, and save messages to a motor controller. This is done in the library with a sub-object for each piece of data that can be get, set, and saved. Thus, to form a get message, use

client_object.sub_object.get(CommunicationInterface &com)

To form a set message with a value of data type T, use

client_object.sub_object.set(CommunicationInterface &com, T value)

To form a set message with no value, use

client_object.sub_object.set(CommunicationInterface &com)

Finally, to form a save message, use

client_object.sub_object.save(CommunicationInterface &com)

These commands form serialized get/set/save packets and store them into a CommunicationInterface object. We supply a hardware agnostic CommunicationInterface called GenericInterface. Once packets are stored in the GenericInterface object, the user must remove the bytes with the class method

interface_object.GetTxBytes(uint8_t* data_out, uint8_t& length_out)

and send the bytes in data_out over the hardware serial.

Similarly, when bytes are received over the hardware serial they must be transferred into the GenericInterface using the class method

interface_object.SetRxBytes(uint8_t* data_in, uint16_t length_in)

Once transferred, a packet can be peeked using

int8_t interface_object.PeekPacket(uint8_t **packet, uint8_t *length)

which will return 1 if there is a packet, 0 if not. If there is a packet, this packet must be passed to the client objects using

client_object.ReadMsg(CommunicationInterface& com, uint8_t* rx_data, uint8_t rx_length)

Once passed to all objects, drop the packet using interface_object.DropPacket().

You can check for newly received data with

client_object.sub_object.IsFresh()

To retrieve the most recent data, regardless of its freshness, use

data = client_object.sub_object.get_reply()

Full C++ Code Example (Windows)

/*
* This example C++ program uses the Power Monitor client to retrieve the Voltage of a motor.
* The serial port setup for windows is based on the example provided by microsoft:
*      https://learn.microsoft.com/en-us/windows/win32/devio/configuring-a-communications-resource
*
* Name: voltage_test.cpp
* Last update: 9/28/2022 by Ben Quan
* Author: Ben Quan
*/

#include <iostream>
#include <windows.h>
#include <tchar.h>

#include "inc/generic_interface.hpp"
#include "inc/client_communication.hpp"
#include "inc/power_monitor_client.hpp"

using namespace std;

HANDLE com_port; // Handler for COM port
TCHAR *pcCommPort = TEXT("COM4"); // Change COM4 to whichever port your motor is connected to
GenericInterface com; // Interface used by com port to communicate with motor

PowerMonitorClient power(0); // Client endpoint for voltage reading

//  Send out any message data we have over the serial interface
int handle_com_tx(){
    uint8_t packet_buf[64];
    uint8_t length = 0;
    DWORD bytes_written;

    // Get the packet from the com interface and place it into the packet buffer
    if(com.GetTxBytes(packet_buf, length)){
        WriteFile(com_port, packet_buf, length, &bytes_written, NULL);
    }

    return bytes_written;
}

// Grab any received data on the serial interface
int handle_com_rx(){
    uint8_t recv_bytes[64];
    DWORD dwBytesReceived;

    ReadFile(com_port, &recv_bytes, 64, &dwBytesReceived, 0);
    com.SetRxBytes(recv_bytes, dwBytesReceived);

    return dwBytesReceived;
}

// Hand off any received data to each module so they can handle it
void update_modules(){
    // Temporary Pointer to the packet data location
    uint8_t *packet_data;
    uint8_t packet_length;

    // Loads the packet data buffer with data receieved from the motor
    while(com.PeekPacket(&packet_data, &packet_length)){
        power.ReadMsg(packet_data, packet_length);
        com.DropPacket();
    }
}

// Handles communication with motor
void send_message_and_process_reply(){
    handle_com_tx();
    handle_com_rx();
    update_modules();
}

// Sends the command to motor to get Voltage
float get_voltage(){
    power.volts_.get(com);
    send_message_and_process_reply();
    return power.volts_.get_reply();
}

int main(){
    com_port = CreateFile( pcCommPort,
                    GENERIC_READ | GENERIC_WRITE,
                    0,      //  must be opened with exclusive-access
                    NULL,   //  default security attributes
                    OPEN_EXISTING, //  must use OPEN_EXISTING
                    0,      //  not overlapped I/O
                    NULL ); //  hTemplate must be NULL for comm devices

    cout << com_port << endl;

    if (com_port== INVALID_HANDLE_VALUE)
        cout <<"Error in opening serial port" << endl;
    else
        cout << "opening serial port successful" << endl;


    DCB dcb = {0}; // Device-control block used to configure serial communications
    dcb.DCBlength = sizeof(DCB);
    GetCommState (com_port,&dcb);
    dcb.BaudRate  = CBR_115200; // Set baud rate to 115200
    dcb.ByteSize = 8;
    SetCommState (com_port,&dcb);

    //Set up a read timeout
    COMMTIMEOUTS timeouts;
    GetCommTimeouts(com_port, &timeouts);
    timeouts.ReadIntervalTimeout = 5;
    SetCommTimeouts(com_port, &timeouts);

    // Get and print the current Voltage reading of the motor
    float current_voltage = get_voltage();
    cout << "voltage: " << to_string(current_voltage) << endl;

    return 0;
}

Full C++ Code Example (w/ LibSerial)

/*
* Vertiq read motor coil temperature.
*
* This code shows how to use Serial over USB to read the
* motor coil temperature
*
*
* The circuit:
* Connected to FTDI usb to Serial
*
* This example uses:
*   - LibSerial (https://libserial.readthedocs.io/en/latest/index.html)
*
*   This demo works for POSIX supported systems and was ran using Linux Ubuntu 20.04.1 LTS
*
*
* Created 2021/03/31 by Malik B. Parker
*
* This example code is in the public domain.
*/


#include "generic_interface.hpp"
#include "temperature_estimator_client.hpp"

#include "libserial/SerialPort.h"
#include "libserial/SerialStream.h"

#include <string>
#include <iostream>
#include "unistd.h"

using namespace LibSerial;

int main(){

    // Setup the serial interface
    SerialPort my_serial_port("/dev/ttyUSB0");
    my_serial_port.SetBaudRate(BaudRate::BAUD_115200);

    // Make a communication interface object
    // This is what creates and parses packets
    GenericInterface com;

    // Make a Temperature Estimator Client object with obj_id 0
    TemperatureEstimatorClient temp(0);


    while(true){

        /**********************************************************************
         *********************** Sending Get Command **************************
         *********************************************************************/

         // Forms a packet in the com interface with the following:
        // type:        (77) Temperature Estimator ID Number
        // subtype:     ( 0) temp
        // obj/access   ( 0) get
        temp.temp_.get(com);

        uint8_t packet_buf[64];
        uint8_t length = 0;

        // Get the packet from the com interface and place it into the packet buffer
        if(com.GetTxBytes(packet_buf, length)){

            // C is a strong typed language -_-
            // so we need to convert to a string buffer to interface with LibSerial
            std::string string_buf((char*)packet_buf, length);

            // Send the get packet request to the motor
            my_serial_port.Write(string_buf);
        }

        /**********************************************************************
         ************************** Receiving Temp Value **********************
         *********************************************************************/

        // Need to wait for the Motor Controller to Respond
        usleep(5000);

        // Serial Receive Buffer
        std::string read_buf;

        // How many bytes are in the read buffer
        length = my_serial_port.GetNumberOfBytesAvailable();

        // Read the packet from Serial
        my_serial_port.Read(read_buf, length);

        // Again C is strongly types so we have to convert back to byte buffer
        uint8_t * cbuf = (uint8_t *) read_buf.c_str();

        // Transfer the buffer into the com interface
        com.SetRxBytes(cbuf, length);

        /**************************************************************************
        **************************  Reading the Value  ***************************
        *************************************************************************/

        // Temporary Pointer to the packet data location
        uint8_t *packet_data;
        uint8_t packet_length;

        // Loads the packet data buffer with data receieved from the motor
        com.PeekPacket(&packet_data, &packet_length);

        // Loads data into the temperature client
        temp.ReadMsg(packet_data, packet_length);

        com.DropPacket();

        // Reads the data from the temperature client
        float temperature = temp.temp_.get_reply();

        printf("Temperature: %f\n", temperature);
    }

    return 0;
}

Full Arduino Code Example (w/ Arduino Serial)

The below is a complete example of a program using the Arduino programming environment. This example is to demonstrate how to use the clients, the GenericInterface class, and the transfer of data between the classes and the Arduino Serial class. Please note that Vertiq’s dedicated Arduino libraries streamline the data transfer process, thus, actual Arduino programming is simpler than the below example. Please see the Arduino documentation if you intend on using the Arduino programming environment.

/*
* Vertiq spin and report demo.
*
* This code will command a motor to spin at various voltages and
* simultaniously report the motor’s position and velocity over USB
*
*
* The circuit:
* Serial1 RX is directly connected to motor TX (Red)
* Serial1 TX is directly connected to motor RX (White)
*
* Created 2018/10/8 by Matthew Piccoli
*
* This example code is in the public domain.
*/

// USER SETABLE VALUES HERE------------------------------
// Voltage step size
const float kVoltageStep = 0.01f;
// Max voltage
const float kVoltageMax = 0.25f;
// END USER SETABLE VALUES-------------------------------

// Includes required for communication
// Message forming interface
#include <generic_interface.hpp>

// Clients that speaks to module’s objects
#include <brushless_drive_client.hpp>

// Make a communication interface object
GenericInterface com;

// Make a objects that talk to the module
BrushlessDriveClient mot(0);

void setup() {
    // Initialize USB communicaiton
    Serial.begin(115200);
    Serial.print("Program starting");
    Serial.println();

    // Initialize the Serial peripheral for motor controller
    Serial1.begin(115200);
}

void loop() {
    static float voltage_to_set = 0.0f;
    static float voltage_sign = 1.0f;

    // Update voltage command
    if(abs(voltage_to_set) >= kVoltageMax){
        voltage_sign = -1*voltage_sign;
    }
    voltage_to_set += kVoltageStep*voltage_sign;

    SendMessages(voltage_to_set);
    ReceiveMessages();
    DoSomethingWithMessages();

    delay(100);
}

void SendMessages(float voltage_command){
    // This buffer is for passing around messages.
    uint8_t communication_buffer[64];
    // Stores length of message to send or receive
    uint8_t communication_length;

    // Generate the set message
    mot.drive_spin_volts_.set(com, voltage_command);

    // Generate the get message
    mot.obs_angle_.get(com);
    mot.obs_velocity_.get(com);

    // Grab outbound messages in the com queue, store into buffer
    // If it transferred something to communication_buffer...
    if(com.GetTxBytes(communication_buffer,communication_length)){
        // Use Arduino serial hardware to send messages
        Serial1.write(communication_buffer,communication_length);
    }

    Serial.print("Setting voltage: ");
    Serial.print(voltage_command);
    Serial.println();
}

void ReceiveMessages(){
    // This buffer is for passing around messages.
    uint8_t communication_buffer[64];
    // Stores length of message to send or receive
    uint8_t communication_length;
    // Reads however many bytes are currently available
    communication_length = Serial1.readBytes(communication_buffer, Serial1.available());
    // Puts the recently read bytes into coms receive queue
    com.SetRxBytes(communication_buffer,communication_length);

    uint8_t *rx_data; // temporary pointer to received type+data bytes
    uint8_t rx_length; // number of received type+data bytes

    // while we have message packets to parse
    while(com.PeekPacket(&rx_data,&rx_length)){
        // Share that packet with all client objects
        mot.ReadMsg(com,rx_data,rx_length);
        // Once were done with the message packet, drop it
        com.DropPacket();
    }
}

void DoSomethingWithMessages(){
    // Check if we have any fresh data
    // Checking for fresh data is not required, it simply
    // lets you know if you received a message that you
    // have not yet read.

    // Check for a new angle message
    if(mot.obs_angle_.IsFresh()) {
        Serial.print("Angle: ");
        Serial.print(mot.obs_angle_.get_reply());
        Serial.println();
    }

    // Check for a new velocity message
    if(mot.obs_velocity_.IsFresh()) {
        Serial.print("Velocity: ");
        Serial.print(mot.obs_velocity_.get_reply());
        Serial.println();
    }
}