Serial half duplex library, Dynamixel, AX12

Hello community,

I am very surprised that Mbed does not have a proper library for controlling Dynamixel actuators . But the most surprising thing is that there is no half duplex series library that conforms to these strange “Unbuffered/Buffered Serial” libraries…

Am I the only one looking for such libraries ? :roll_eyes:

1 Like

Hello Mickaël,

You can find an AX12 library in the Mbed Components. Although it’s for Mbed OS 2 it might help get started.

Best regards, Zoltan

Thank you very much for your quick response !
I’ve already spent a lot of time trying to adapt it for mbedOS 6 … The only problem is the half duplex serial communication…

To establish a half duplex communication we have to make sure all bytes have been sent before switching to read mode. That can be implemented by calling a callback function on such event signalling the transmission is complete. For example, as in the snippet below.

...
// Flag that transmission is complete/finished
void AX12::onTxComplete(int)
{
    txComplete = true;
}

int AX12::read(int ID, int start, int bytes, char* data)
{
...
    // Transmit data from a TxBuffer
    txComplete = false;
    _ax12.write((uint8_t*)TxBuffer, length, callback(onTxComplete));

    // Wait until all bytes have been transmitted
    while (!txComplete);

    // Now we can read the response from the AX12
...
}

I don’t have an AX12 to test it but the library code below modified for Mbed OS 6 might work:

AX12.h

/* mbed AX-12+ Servo Library
 *
 * Copyright (c) 2010, cstyles (http://mbed.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef MBED_AX12_H
#define MBED_AX12_H

#include "mbed.h"

//#define AX12_WRITE_DEBUG 0
//#define AX12_READ_DEBUG 0
//#define AX12_TRIGGER_DEBUG 0
//#define AX12_DEBUG 0

#define AX12_REG_ID 0x3
#define AX12_REG_BAUD 0x4
#define AX12_REG_CW_LIMIT 0x06
#define AX12_REG_CCW_LIMIT 0x08
#define AX12_REG_GOAL_POSITION 0x1E
#define AX12_REG_MOVING_SPEED 0x20
#define AX12_REG_VOLTS 0x2A
#define AX12_REG_TEMP 0x2B
#define AX12_REG_MOVING 0x2E
#define AX12_REG_POSITION 0x24

#define AX12_MODE_POSITION  0
#define AX12_MODE_ROTATION  1

#define AX12_CW 1
#define AX12_CCW 0

/** Servo control class, based on a PwmOut
 *
 * Example:
 * @code
 * #include "mbed.h"
 * #include "AX12.h"
 *
 * int main() {
 *
 *   AX12 myax12 (p9, p10, 1);
 *
 *   while (1) {
 *       myax12.SetGoal(0);    // go to 0 degrees
 *       ThisThread::sleep_for(2s);
 *       myax12.SetGoal(300);  // go to 300 degrees
 *       ThisThread::sleep_for(2s);
 *   }
 * }
 * @endcode
 */

class AX12
{

public:

    /** Create an AX12 servo object connected to the specified serial port, with the specified ID
     *
     * @param pin tx pin
     * @param pin rx pin
     * @param int ID, the Bus ID of the servo 1-255
     */
    AX12(PinName tx, PinName rx, int ID, int baud=1000000);

    /** Set the mode of the servo
     * @param mode
     *    0 = Positional, default
     *    1 = Continuous rotation
     */
    int SetMode(int mode);

    /** Set baud rate of all attached servos
     * @param mode
     *    0x01 = 1,000,000 bps
     *    0x03 =   500,000 bps
     *    0x04 =   400,000 bps
     *    0x07 =   250,000 bps
     *    0x09 =   200,000 bps
     *    0x10 =   115,200 bps
     *    0x22 =    57,600 bps
     *    0x67 =    19,200 bps
     *    0xCF =     9,600 bp
     */
    int SetBaud(int baud);


    /** Set goal angle in integer degrees, in positional mode
     *
     * @param degrees 0-300
     * @param flags, defaults to 0
     *    flags[0] = blocking, return when goal position reached
     *    flags[1] = register, activate with a broadcast trigger
     *
     */
    int SetGoal(int degrees, int flags = 0);


    /** Set the speed of the servo in continuous rotation mode
     *
     * @param speed, -1.0 to 1.0
     *   -1.0 = full speed counter clock wise
     *    1.0 = full speed clock wise
     */
    int SetCRSpeed(float speed);


    /** Set the clockwise limit of the servo
     *
     * @param degrees, 0-300
     */
    int SetCWLimit(int degrees);

    /** Set the counter-clockwise limit of the servo
     *
     * @param degrees, 0-300
     */
    int SetCCWLimit(int degrees);

    // Change the ID

    /** Change the ID of a servo
     *
     * @param CurentID 1-255
     * @param NewID 1-255
     *
     * If a servo ID is not know, the broadcast address of 0 can be used for CurrentID.
     * In this situation, only one servo should be connected to the bus
     */
    int SetID(int CurrentID, int NewID);


    /** Poll to see if the servo is moving
     *
     * @returns true is the servo is moving
     */
    int isMoving(void);

    /** Send the broadcast "trigger" command, to activate any outstanding registered commands
     */
    void trigger(void);

    /** Read the current angle of the servo
     *
     * @returns float in the range 0.0-300.0
     */
    float GetPosition();

    /** Read the temperature of the servo
     *
     * @returns float temperature
     */
    float GetTemp(void);

    /** Read the supply voltage of the servo
     *
     * @returns float voltage
     */
    float GetVolts(void);

    int read(int ID, int start, int length, char* data);
    int write(int ID, int start, int length, char* data, int flag=0);

    static void onTxComplete(int);
    static bool txComplete;

private :

    class Serial : public SerialBase
    {
    public:
        Serial(PinName tx, PinName rx, int baud=1000000) : SerialBase(tx, rx, baud) {}
        int getc() { return _base_getc(); }
    };

    Serial  _ax12;
    int     _ID;
    int     _baud;
};

#endif

AX12.cpp

/* mbed AX-12+ Servo Library
 *
 * Copyright (c) 2010, cstyles (http://mbed.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "AX12.h"
#include "mbed.h"

bool AX12::txComplete = false;

AX12::AX12(PinName tx, PinName rx, int ID, int baud) :
    _ax12(tx, rx, baud),
    _ID(ID),
    _baud(baud)
{ }

// Flag that transmission is complete/finished
void AX12::onTxComplete(int)
{
    txComplete = true;
}

// Set the mode of the servo
//  0 = Positional (0-300 degrees)
//  1 = Rotational -1 to 1 speed
int AX12::SetMode(int mode)
{
    if (mode == 1) {
        // set CR
        SetCWLimit(0);
        SetCCWLimit(0);
        SetCRSpeed(0.0);
    }
    else {
        SetCWLimit(0);
        SetCCWLimit(300);
        SetCRSpeed(0.0);
    }

    return(0);
}

// if flag[0] is set, were blocking
// if flag[1] is set, we're registering
// they are mutually exclusive operations
int AX12::SetGoal(int degrees, int flags)
{
    char    reg_flag = 0;
    char    data[2];

    // set the flag is only the register bit is set in the flag

    if (flags == 0x2) {
        reg_flag = 1;
    }

    // 1023 / 300 * degrees
    short   goal = (1023 * degrees) / 300;
#ifdef AX12_DEBUG
    printf("SetGoal to 0x%x\n", goal);
#endif
    data[0] = goal & 0xff;  // bottom 8 bits
    data[1] = goal >> 8;    // top 8 bits

    // write the packet, return the error code
    int rVal = write(_ID, AX12_REG_GOAL_POSITION, 2, data, reg_flag);

    if (flags == 1) {
        // block until it comes to a halt
        while (isMoving()) { }
    }

    return(rVal);
}

// Set continuous rotation speed from -1 to 1
int AX12::SetCRSpeed(float speed)
{
    // bit 10     = direction, 0 = CCW, 1=CW
    // bits 9-0   = Speed
    char    data[2];

    int     goal = (0x3ff * abs(speed));

    // Set direction CW if we have a negative speed

    if (speed < 0) {
        goal |= (0x1 << 10);
    }

    data[0] = goal & 0xff;  // bottom 8 bits
    data[1] = goal >> 8;    // top 8 bits

    // write the packet, return the error code
    int rVal = write(_ID, 0x20, 2, data);

    return(rVal);
}

int AX12::SetCWLimit(int degrees)
{
    char    data[2];

    // 1023 / 300 * degrees
    short   limit = (1023 * degrees) / 300;

#ifdef AX12_DEBUG
    printf("SetCWLimit to 0x%x\n", limit);
#endif
    data[0] = limit & 0xff; // bottom 8 bits
    data[1] = limit >> 8;   // top 8 bits

    // write the packet, return the error code
    return(write(_ID, AX12_REG_CW_LIMIT, 2, data));
}

int AX12::SetCCWLimit(int degrees)
{
    char    data[2];

    // 1023 / 300 * degrees
    short   limit = (1023 * degrees) / 300;

#ifdef AX12_DEBUG
    printf("SetCCWLimit to 0x%x\n", limit);
#endif
    data[0] = limit & 0xff; // bottom 8 bits
    data[1] = limit >> 8;   // top 8 bits

    // write the packet, return the error code
    return(write(_ID, AX12_REG_CCW_LIMIT, 2, data));
}

int AX12::SetID(int CurrentID, int NewID)
{
    char    data[1];
    data[0] = NewID;

#ifdef AX12_DEBUG
    printf("Setting ID from 0x%x to 0x%x\n", CurrentID, NewID);
#endif
    return(write(CurrentID, AX12_REG_ID, 1, data));
}

int AX12::SetBaud(int baud)
{
    char    data[1];
    data[0] = baud;

#ifdef AX12_DEBUG
    printf("Setting Baud rate to %d\n", baud);
#endif
    return(write(0xFE, AX12_REG_BAUD, 1, data));
}

// return 1 is the servo is still in flight
int AX12::isMoving(void)
{
    char    data[1];
    read(_ID, AX12_REG_MOVING, 1, data);
    return(data[0]);
}

void AX12::trigger(void)
{
    char    TxBuf[16];
    char    sum = 0;

#ifdef AX12_TRIGGER_DEBUG
    // Build the TxPacket first in RAM, then we'll send in one go

    printf("\nTriggered\n");
    printf("\nTrigger Packet\n  Header : 0xFF, 0xFF\n");
#endif
    TxBuf[0] = 0xFF;
    TxBuf[1] = 0xFF;

    // ID - Broadcast
    TxBuf[2] = 0xFE;
    sum += TxBuf[2];

#ifdef AX12_TRIGGER_DEBUG
    printf("  ID : %d\n", TxBuf[2]);
#endif
    // Length

    TxBuf[3] = 0x02;
    sum += TxBuf[3];

#ifdef AX12_TRIGGER_DEBUG
    printf("  Length %d\n", TxBuf[3]);
#endif
    // Instruction - ACTION

    TxBuf[4] = 0x04;
    sum += TxBuf[4];

#ifdef AX12_TRIGGER_DEBUG
    printf("  Instruction 0x%X\n", TxBuf[5]);
#endif
    // Checksum

    TxBuf[5] = 0xFF - sum;
#ifdef AX12_TRIGGER_DEBUG
    printf("  Checksum 0x%X\n", TxBuf[5]);
#endif
    // Transmit the packet in one burst with no pausing
    txComplete = false;
    _ax12.write((uint8_t*)TxBuf, 6, callback(onTxComplete));

    // Wait for the bytes to be transmitted
    while (!txComplete);

    // This is a broadcast packet, so there will be no reply
    return;
}

float AX12::GetPosition(void)
{
#ifdef AX12_DEBUG
    printf("\nGetPosition(%d)", _ID);
#endif

    char    data[2];

    int     ErrorCode = read(_ID, AX12_REG_POSITION, 2, data);
    short   position = data[0] + (data[1] << 8);
    float   angle = (position * 300) / 1024;

    return(angle);
}

float AX12::GetTemp(void)
{
#ifdef AX12_DEBUG
    printf("\nGetTemp(%d)", _ID);
#endif

    char    data[1];
    int     ErrorCode = read(_ID, AX12_REG_TEMP, 1, data);
    float   temp = data[0];
    return(temp);
}

float AX12::GetVolts(void)
{
#ifdef AX12_DEBUG
    printf("\nGetVolts(%d)", _ID);
#endif

    char    data[1];
    int     ErrorCode = read(_ID, AX12_REG_VOLTS, 1, data);
    float   volts = data[0] / 10.0;
    return(volts);
}

int AX12::read(int ID, int start, int bytes, char* data)
{
    char    PacketLength = 0x4;
    char    TxBuf[16];
    char    sum = 0;
    char    Status[16];

    Status[4] = 0xFE;           // return code
#ifdef AX12_READ_DEBUG
    printf("\nread(%d,0x%x,%d,data)\n", ID, start, bytes);
#endif
    // Build the TxPacket first in RAM, then we'll send in one go
#ifdef AX12_READ_DEBUG
    printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
#endif
    TxBuf[0] = 0xff;
    TxBuf[1] = 0xff;

    // ID
    TxBuf[2] = ID;
    sum += TxBuf[2];

#ifdef AX12_READ_DEBUG
    printf("  ID : %d\n", TxBuf[2]);
#endif
    // Packet Length

    TxBuf[3] = PacketLength;    // Length = 4 ; 2 + 1 (start) = 1 (bytes)
    sum += TxBuf[3];            // Accululate the packet sum
#ifdef AX12_READ_DEBUG
    printf("  Length : 0x%x\n", TxBuf[3]);
#endif
    // Instruction - Read

    TxBuf[4] = 0x2;
    sum += TxBuf[4];

#ifdef AX12_READ_DEBUG
    printf("  Instruction : 0x%x\n", TxBuf[4]);
#endif
    // Start Address

    TxBuf[5] = start;
    sum += TxBuf[5];

#ifdef AX12_READ_DEBUG
    printf("  Start Address : 0x%x\n", TxBuf[5]);
#endif
    // Bytes to read

    TxBuf[6] = bytes;
    sum += TxBuf[6];

#ifdef AX12_READ_DEBUG
    printf("  No bytes : 0x%x\n", TxBuf[6]);
#endif
    // Checksum

    TxBuf[7] = 0xFF - sum;
#ifdef AX12_READ_DEBUG
    printf("  Checksum : 0x%x\n", TxBuf[7]);
#endif
    // Transmit the packet in one burst with no pausing
    txComplete = false;
    _ax12.write((uint8_t*)TxBuf, 8, callback(onTxComplete));

    // Wait for the bytes to be transmitted
    while (!txComplete);

    // Skip if the read was to the broadcast address
    if (_ID != 0xFE) {
        // response packet is always 6 + bytes
        // 0xFF, 0xFF, ID, Length Error, Param(s) Checksum
        // timeout is a little more than the time to transmit
        // the packet back, i.e. (6+bytes)*10 bit periods
        int timeout = 0;
        int plen = 0;
        while ((timeout < ((6 + bytes) * 10)) && (plen < (6 + bytes))) {
            if (_ax12.readable()) {
                Status[plen] = _ax12.getc();
                plen++;
                timeout = 0;
            }

            // wait for the bit period
            //wait(1.0 / _baud);
            wait_us((1.0 / float(_baud)) * 1000000);
            timeout++;
        }

        if (timeout == ((6 + bytes) * 10)) {
            return(-1);
        }

        // Copy the data from Status into data for return
        for (int i = 0; i < Status[3] - 2; i++) {
            data[i] = Status[5 + i];
        }

#ifdef AX12_READ_DEBUG
        printf("\nStatus Packet\n");
        printf("  Header : 0x%x\n", Status[0]);
        printf("  Header : 0x%x\n", Status[1]);
        printf("  ID : 0x%x\n", Status[2]);
        printf("  Length : 0x%x\n", Status[3]);
        printf("  Error Code : 0x%x\n", Status[4]);

        for (int i = 0; i < Status[3] - 2; i++) {
            printf("  Data : 0x%x\n", Status[5 + i]);
        }

        printf("  Checksum : 0x%x\n", Status[5 + (Status[3] - 2)]);
#endif
    }                           // if (ID!=0xFE)

    return(Status[4]);
}

int AX12::write(int ID, int start, int bytes, char* data, int flag)
{
    // 0xff, 0xff, ID, Length, Intruction(write), Address, Param(s), Checksum
    char    TxBuf[16];
    char    sum = 0;
    char    Status[6];

#ifdef AX12_WRITE_DEBUG
    printf("\nwrite(%d,0x%x,%d,data,%d)\n", ID, start, bytes, flag);
#endif
    // Build the TxPacket first in RAM, then we'll send in one go
#ifdef AX12_WRITE_DEBUG
    printf("\nInstruction Packet\n  Header : 0xFF, 0xFF\n");
#endif
    TxBuf[0] = 0xff;
    TxBuf[1] = 0xff;

    // ID
    TxBuf[2] = ID;
    sum += TxBuf[2];

#ifdef AX12_WRITE_DEBUG
    printf("  ID : %d\n", TxBuf[2]);
#endif
    // packet Length

    TxBuf[3] = 3 + bytes;
    sum += TxBuf[3];

#ifdef AX12_WRITE_DEBUG
    printf("  Length : %d\n", TxBuf[3]);
#endif
    // Instruction

    if (flag == 1) {
        TxBuf[4] = 0x04;
        sum += TxBuf[4];
    }
    else {
        TxBuf[4] = 0x03;
        sum += TxBuf[4];
    }

#ifdef AX12_WRITE_DEBUG
    printf("  Instruction : 0x%x\n", TxBuf[4]);
#endif
    // Start Address

    TxBuf[5] = start;
    sum += TxBuf[5];

#ifdef AX12_WRITE_DEBUG
    printf("  Start : 0x%x\n", TxBuf[5]);
#endif
    // data

    for (char i = 0; i < bytes; i++) {
        TxBuf[6 + i] = data[i];
        sum += TxBuf[6 + i];

#ifdef AX12_WRITE_DEBUG
        printf("  Data : 0x%x\n", TxBuf[6 + i]);
#endif
    }

    // checksum
    TxBuf[6 + bytes] = 0xFF - sum;

#ifdef AX12_WRITE_DEBUG
    printf("  Checksum : 0x%x\n", TxBuf[6 + bytes]);
#endif
    // Transmit the packet in one burst with no pausing
    txComplete = false;
    _ax12.write((uint8_t*)TxBuf, 7 + bytes, callback(onTxComplete));

    // Wait for all the bytes to be transmitted
    while (!txComplete);

    // make sure we have a valid return
    Status[4] = 0x00;

    // we'll only get a reply if it was not broadcast
    if (_ID != 0xFE) {
        // response packet is always 6 bytes
        // 0xFF, 0xFF, ID, Length Error, Param(s) Checksum
        // timeout is a little more than the time to transmit
        // the packet back, i.e. 60 bit periods, round up to 100
        int timeout = 0;
        int plen = 0;
        while ((timeout < 100) && (plen < 6)) {
            if (_ax12.readable()) {
                Status[plen] = _ax12.getc();
                plen++;
                timeout = 0;
            }

            // wait for the bit period
            //wait(1.0 / _baud);
            wait_us((1.0 / float(_baud)) * 1000000 );
            timeout++;
        }

        // Build the TxPacket first in RAM, then we'll send in one go
#ifdef AX12_WRITE_DEBUG
        printf("\nStatus Packet\n  Header : 0x%X, 0x%X\n", Status[0], Status[1]);
        printf("  ID : %d\n", Status[2]);
        printf("  Length : %d\n", Status[3]);
        printf("  Error : 0x%x\n", Status[4]);
        printf("  Checksum : 0x%x\n", Status[5]);
#endif
    }

    return(Status[4]);  // return error code
}

Thank you very much, I will try this afternoon and I will keep you informed.
Have a nice day!

EDIT :
I tried, but it doesn’t work… The program freezes as soon as I use a command (like SetMode)… I used printf for debugging and I’m stuck in the _ax12.write func… Any suggestions ?

Thanks a lot !

I “tested” the following example program (published in the Mbed Componets - AX12) built with Mbed OS 6.6.0. on a NUCLEO_F411RE board. No real AX12 was connected the Mbed board (unfortunately I don’t have such device):

#include "mbed.h"
#include "AX12.h"

int main() {

    AX12 myax12 (PA_9, PA_10, 1, 9600);

    while (1) {
        myax12.SetGoal(0);    // go to 0 degrees
        ThisThread::sleep_for(2s);
        myax12.SetGoal(300);  // go to 300 degrees
        ThisThread::sleep_for(2s);
    }
}

I let the program run for several minutes. No freeze. Below is a sample printout from the serial terminal:

00000000 FF FF 01 05 03 1E FF 03   D6 FF FF 01 05 03 1E 00  ................
00000010 00 D8 FF FF 01 05 03 1E   FF 03 D6 FF FF 01 05 03  ................
00000020 1E 00 00 D8 FF FF 01 05   03 1E FF 03 D6 FF FF 01  ................
00000030 05 03 1E 00 00 D8 FF FF   01 05 03 1E FF 03 D6 FF  ................
00000040 FF 01 05 03 1E 00 00 D8   FF FF 01 05 03 1E FF 03  ................
00000050 D6 FF FF 01 05 03 1E 00   00 D8 FF FF 01 05 03 1E  ................
00000060 FF 03 D6 FF FF 01 05 03   1E 00 00 D8 FF FF 01 05  ................
00000070 03 1E FF 03 D6 FF FF 01   05 03 1E 00 00 D8        ..............

I’ll try this right now ! (I’m on F446RE and using last version of the Mbed Framework on VScode PlatformIO) If this try turn to be a fail, I’ll try on the online compiler.

EDIT :
Stuck at the first instruction :frowning:
#include “mbed.h”
#include “AX12.h”

int main() {

AX12 myax12 (PC_6, PC_7, 2);
UnbufferedSerial pc(USBTX,USBRX,115200);
while (1) {
    printf("1\n");
    myax12.SetGoal(0);    // go to 0 degrees
    printf("2\n");
    ThisThread::sleep_for(2s);
    printf("3\n");
    myax12.SetGoal(300);  // go to 300 degrees
    printf("4\n");
    ThisThread::sleep_for(2s);
}

}

I only get the “1” (Even if servos are connected or not)

I tested the same sample program on my NUCLEO_F446RE but I set

AX12 myax12 (USBTX, USBRX, 1, 9600);

Results were same. No freeze.
I’m using Mbed-CLI and GCC ARM for building.

Let’s try with the online compiler

→ Same results

When built with the online compiler the same code:

include "mbed.h"
#include "AX12.h"

int main() {

    AX12 myax12 (USBTX, USBTX, 1, 9600);

    while (1) {
        myax12.SetGoal(0);    // go to 0 degrees
        ThisThread::sleep_for(2s);
        myax12.SetGoal(300);  // go to 300 degrees
        ThisThread::sleep_for(2s);
    }
}

on a NUCLEO_F446RE reports a runtime error:

++ MbedOS Error Info ++
Error Status: 0x80010130 Code: 304 Module: 1
Error Message: pinmap not found for peripheral
Location: 0x8006491
Error Value: 0x2
Current Thread: main Id: 0x20001AAC Entry: 0x8005235 StackSize: 0x1000 StackMem: 0x20000518 SP: 0x20001364 
For more info, visit: https://mbed.com/s/error?error=0x80010130&tgt=NUCLEO_F446RE
-- MbedOS Error Info --

I don’t know yet why.

Strange…

Sorry, I made a typo:

AX12 myax12 (USBTX, USBTX, 1, 9600);

After changing to

AX12 myax12 (USBTX, USBRX, 1, 9600);

the serial terminal printed:

00000000 FF 01 00 D8                                        ....

and then the program freeze. No runtime error was reported.

Well I don’t know what to do… Maybe using _base_putc into the Serial Class you created would be a solution ?

I’d recommend you to build the program offline with the Mbed Studio and GCC ARM. You can switch to GCC ARM as explained here.

Not stuck at all, the _ax12.write func returns a 0 and the while loop runs, but the servo isn’t running at all

(NB: I’m sure of the ID and that the servo works…)

(Mbed OS 6 ARMC6 (I get an error trying to setup GCC_ARM))

The Mbed OS 2 AX12 library inserts delays to make sure all bytes have been sent but probably to give also some additional time for the AX12 to process the received packet before responding, like:

...
    // Wait for data to transmit
    wait (0.00002);
...

Search the AX12 Mbed OS 2 library for such delays and insert similar ones to the Mbed OS 6 library:

...
     // Wait for data to transmit
    while (!txComplete);

    wait_us(20);    // wait while the AX12 processes the serial data received

    // Now we can read the response from the AX12
...

If it doesn’t work try to tune (reduce/increase) these delays.

I tried [15;100] with +/- 5us steps… but nothing

SerialHalfDuplex.cpp


/* mbed Microcontroller Library
 * Copyright (c) 2006-2012 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * NOTE: This is an unsupported legacy untested library.
 */
#include "SerialHalfDuplex.h"
 
#if 1
#include "mbed.h"
#include "pinmap.h"
#include "serial_api.h"
#include "gpio_api.h"
 
namespace mbed {
 
SerialHalfDuplex::SerialHalfDuplex(PinName tx, PinName rx, const char *name)
    : Serial(tx, rx, name) {
    _txpin = tx;
    
    DigitalIn TXPIN(_txpin);         // set as input 
    pin_mode(_txpin, PullNone); // no pull
    pin_function(_txpin, 0);    // set as gpio
}
 
// To transmit a byte in half duplex mode:
// 1. Disable interrupts, so we don't trigger on loopback byte
// 2. Set tx pin to UART out
// 3. Transmit byte as normal
// 4. Read back byte from looped back tx pin - this both confirms that the
//    transmit has occurred, and also clears the byte from the buffer.
// 5. Return pin to input mode
// 6. Re-enable interrupts
 
int SerialHalfDuplex::_putc(int c) {
    int retc;
    
    // TODO: We should not disable all interrupts
    __disable_irq();
    
    serial_pinout_tx(_txpin);
    
    Serial::_putc(c);
    retc = Serial::getc();       // reading also clears any interrupt
    
    pin_function(_txpin, 0);
    
    __enable_irq();
    
    return retc;
}
 
int SerialHalfDuplex::_getc(void) {
    return Serial::_getc();
}
 
} // End namespace
 
#endif



SerialHalfDuplex.h

/* mbed Microcontroller Library
 * Copyright (c) 2006-2012 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * NOTE: This is an unsupported legacy untested library.
 */
#ifndef MBED_SERIALHALFDUPLEX_H
#define MBED_SERIALHALFDUPLEX_H
 
#include "device.h"
 
#if DEVICE_SERIAL
 
#include "Serial.h"
#include "PinNames.h"
#include "PeripheralNames.h"
 
namespace mbed {
 
/* Class: SerialHalfDuplex
 * A serial port (UART) for communication with other devices using  
 * Half-Duplex, allowing transmit and receive on a single
 * shared transmit and receive line. Only one end should be transmitting 
 * at a time.
 * 
 * Both the tx and rx pin should be defined, and wired together. 
 * This is in addition to them being wired to the other serial 
 * device to allow both read and write functions to operate.
 *
 *  Example:
 *  > // Send a byte to a second HalfDuplex device, and read the response
 *  >
 *  > #include "mbed.h"
 *  >
 *  > // p9 and p10 should be wired together to form "a"
 *  > // p28 and p27 should be wired together to form "b"
 *  > // p9/p10 should be wired to p28/p27 as the Half Duplex connection
 *  >
 *  > SerialHalfDuplex a(p9, p10);
 *  > SerialHalfDuplex b(p28, p27);
 *  >
 *  > void b_rx() { // second device response
 *  >     b.putc(b.getc() + 4);
 *  > }
 *  >   
 *  > int main() {
 *  >     b.attach(&b_rx);
 *  >     for(int c = 'A'; c < 'Z'; c++) {
 *  >         a.putc(c);
 *  >         printf("sent [%c]\n", c);
 *  >         wait(0.5);   // b should respond
 *  >         if(a.readable()) {
 *  >             printf("received [%c]\n", a.getc());
 *  >         }
 *  >     }
 *  > }
 * 
 * For Simplex and Full-Duplex Serial communication, see <Serial>
 */
class SerialHalfDuplex : public Serial {
 
public:
    /* Constructor: SerialHalfDuplex
     * Create a half-duplex serial port, connected to the specified transmit
     * and receive pins.
     *
     * These pins should be wired together, as well as to the target device
     *
     * Variables:
     *  tx - Transmit pin
     *  rx - Receive pin
     */
    SerialHalfDuplex(PinName tx, PinName rx, const char *name = NULL);
 
#if 0       // Inherited from Serial class, for documentation
    /* Function: baud
     *  Set the baud rate of the serial port
     *
     * Variables:
     *  baudrate - The baudrate of the serial port (default = 9600).
     */
    void baud(int baudrate);
 
    enum Parity {
        None = 0
        , Odd
        , Even
        , Forced1
        , Forced0
    };
 
    /* Function: format
     *  Set the transmission format used by the Serial port
     *
     * Variables:
     *  bits - The number of bits in a word (5-8; default = 8)
     *  parity - The parity used (Serial::None, Serial::Odd, 
Serial::Even, Serial::Forced1, Serial::Forced0; default = Serial::None)
     *  stop - The number of stop bits (1 or 2; default = 1)
     */
    void format(int bits = 8, Parity parity = Serial::None, int stop_bits 
= 1);
 
    /* Function: putc
     *  Write a character
     *
     * Variables:
     *  c - The character to write to the serial port
     */
    int putc(int c);
 
    /* Function: getc
     *  Read a character
     *
     * Read a character from the serial port. This call will block
     * until a character is available. For testing if a character is
     * available for reading, see <readable>.
     *
     * Variables:
     *  returns - The character read from the serial port
     */
    int getc();
 
    /* Function: printf
     *  Write a formated string
     *
     * Variables:
     *  format - A printf-style format string, followed by the
     *      variables to use in formating the string.
     */
    int printf(const char* format, ...);
 
    /* Function: scanf
     *  Read a formated string
     *
     * Variables:
     *  format - A scanf-style format string,
     *      followed by the pointers to variables to store the results.
     */
    int scanf(const char* format, ...);
 
    /* Function: readable
     *  Determine if there is a character available to read
     *
     * Variables:
     *  returns - 1 if there is a character available to read, else 0
     */
    int readable();
 
    /* Function: writeable
     *  Determine if there is space available to write a character
     *
     * Variables:
     *  returns - 1 if there is space to write a character, else 0
     */
    int writeable();
 
    /* Function: attach
     *  Attach a function to call whenever a serial interrupt is generated
     *
     * Variables:
     *  fptr - A pointer to a void function, or 0 to set as none
     */
    void attach(void (*fptr)(void));
 
    /* Function: attach
     *  Attach a member function to call whenever a serial interrupt is generated
     *
     * Variables:
     *  tptr - pointer to the object to call the member function on
     *  mptr - pointer to the member function to be called
     */
    template<typename T>
    void attach(T* tptr, void (T::*mptr)(void));
 
#endif
 
protected:
    PinName     _txpin;
 
    virtual int _putc(int c);
    virtual int _getc(void);
 
}; // End class SerialHalfDuplex
 
} // End namespace
 
#endif
 
#endif

These files in combination with AX12 lib & Mbed OS2 work well, isn’t there a way to translate it to meet Mbed OS 6 ?

Below is an attempt to port the SerialHalfDuplex class you provided above to Mbed OS 6.

SerialHalfDuplex.h:

/* mbed Microcontroller Library
 * Copyright (c) 2006-2012 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * NOTE: This is an unsupported legacy untested library.
 */
#ifndef MBED_SERIALHALFDUPLEX_H
#define MBED_SERIALHALFDUPLEX_H

#include "device.h"

#if DEVICE_SERIAL

#include "SerialBase.h"
#include "PinNames.h"
#include "PeripheralNames.h"

namespace mbed {

/* Class: SerialHalfDuplex
 * A serial port (UART) for communication with other devices using
 * Half-Duplex, allowing transmit and receive on a single
 * shared transmit and receive line. Only one end should be transmitting
 * at a time.
 *
 * Both the tx and rx pin should be defined, and wired together.
 * This is in addition to them being wired to the other serial
 * device to allow both read and write functions to operate.
 *
 *  Example:
 *  > // Send a byte to a second HalfDuplex device, and read the response
 *  >
 *  > #include "mbed.h"
 *  >
 *  > // p9 and p10 should be wired together to form "a"
 *  > // p28 and p27 should be wired together to form "b"
 *  > // p9/p10 should be wired to p28/p27 as the Half Duplex connection
 *  >
 *  > SerialHalfDuplex a(p9, p10);
 *  > SerialHalfDuplex b(p28, p27);
 *  >
 *  > void b_rx() { // second device response
 *  >     b.putc(b.getc() + 4);
 *  > }
 *  >
 *  > int main() {
 *  >     b.attach(&b_rx);
 *  >     for(int c = 'A'; c < 'Z'; c++) {
 *  >         a.putc(c);
 *  >         printf("sent [%c]\n", c);
 *  >         wait(0.5);   // b should respond
 *  >         if(a.readable()) {
 *  >             printf("received [%c]\n", a.getc());
 *  >         }
 *  >     }
 *  > }
 *
 * For Simplex and Full-Duplex Serial communication, see <Serial>
 */
class SerialHalfDuplex : public SerialBase {

public:
    /* Constructor: SerialHalfDuplex
     * Create a half-duplex serial port, connected to the specified transmit
     * and receive pins.
     *
     * These pins should be wired together, as well as to the target device
     *
     * Variables:
     *  tx - Transmit pin
     *  rx - Receive pin
     */
    SerialHalfDuplex(PinName tx, PinName rx, int baud);

#if 0       // Inherited from Serial class, for documentation
    /* Function: baud
     *  Set the baud rate of the serial port
     *
     * Variables:
     *  baudrate - The baudrate of the serial port (default = 9600).
     */
    void baud(int baudrate);

    enum Parity {
        None = 0
        , Odd
        , Even
        , Forced1
        , Forced0
    };

    /* Function: format
     *  Set the transmission format used by the Serial port
     *
     * Variables:
     *  bits - The number of bits in a word (5-8; default = 8)
     *  parity - The parity used (Serial::None, Serial::Odd,
Serial::Even, Serial::Forced1, Serial::Forced0; default = Serial::None)
     *  stop - The number of stop bits (1 or 2; default = 1)
     */
    void format(int bits = 8, Parity parity = Serial::None, int stop_bits
= 1);

    /* Function: putc
     *  Write a character
     *
     * Variables:
     *  c - The character to write to the serial port
     */
    int putc(int c);

    /* Function: getc
     *  Read a character
     *
     * Read a character from the serial port. This call will block
     * until a character is available. For testing if a character is
     * available for reading, see <readable>.
     *
     * Variables:
     *  returns - The character read from the serial port
     */
    int getc();

    /* Function: printf
     *  Write a formated string
     *
     * Variables:
     *  format - A printf-style format string, followed by the
     *      variables to use in formating the string.
     */
    int printf(const char* format, ...);

    /* Function: scanf
     *  Read a formated string
     *
     * Variables:
     *  format - A scanf-style format string,
     *      followed by the pointers to variables to store the results.
     */
    int scanf(const char* format, ...);

    /* Function: readable
     *  Determine if there is a character available to read
     *
     * Variables:
     *  returns - 1 if there is a character available to read, else 0
     */
    int readable();

    /* Function: writeable
     *  Determine if there is space available to write a character
     *
     * Variables:
     *  returns - 1 if there is space to write a character, else 0
     */
    int writeable();

    /* Function: attach
     *  Attach a function to call whenever a serial interrupt is generated
     *
     * Variables:
     *  fptr - A pointer to a void function, or 0 to set as none
     */
    void attach(void (*fptr)(void));

    /* Function: attach
     *  Attach a member function to call whenever a serial interrupt is generated
     *
     * Variables:
     *  tptr - pointer to the object to call the member function on
     *  mptr - pointer to the member function to be called
     */
    template<typename T>
    void attach(T* tptr, void (T::*mptr)(void));

#endif

protected:
    PinName     _txpin;

    virtual int _putc(int c);
    virtual int _getc(void);

}; // End class SerialHalfDuplex

} // End namespace

#endif

#endif

SerialHalfDuplex.cpp:

/*
 * mbed Microcontroller Library
 * Copyright (c) 2006-2012 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * NOTE: This is an unsupported legacy untested library.
 */
#include "SerialHalfDuplex.h"

#if 1
#include "mbed.h"
#include "pinmap.h"
#include "serial_api.h"
#include "gpio_api.h"

namespace mbed {

SerialHalfDuplex::SerialHalfDuplex(PinName tx, PinName rx, int baud)
    : SerialBase(tx, rx, baud)
{
    _txpin = tx;

    DigitalIn TXPIN(_txpin);    // set as input
    pin_mode(_txpin, PullNone); // no pull
    pin_function(_txpin, 0);    // set as gpio
}

// To transmit a byte in half duplex mode:
// 1. Disable interrupts, so we don't trigger on loopback byte
// 2. Set tx pin to UART out
// 3. Transmit byte as normal
// 4. Read back byte from looped back tx pin - this both confirms that the
//    transmit has occurred, and also clears the byte from the buffer.
// 5. Return pin to input mode
// 6. Re-enable interrupts

int SerialHalfDuplex::_putc(int c)
{
    int retc;

    // TODO: We should not disable all interrupts
    //__disable_irq();
    core_util_critical_section_enter();

    serial_pinout_tx(_txpin);

    SerialBase::_base_putc(c);
    retc = SerialBase::_base_getc();       // reading also clears any interrupt

    pin_function(_txpin, 0);

    //__enable_irq();
    core_util_critical_section_exit();
    return retc;
}

int SerialHalfDuplex::_getc(void)
{
    return SerialBase::_base_getc();
}

} // End namespace

#endif

I had compilation errors with protected members of the SerialHalfDuplex class, so I unprotected them but it didn’t work at all.
It seems that bytes are being transferred (retc variable OK) but no response is being received. (stuck in getc)

EDIT :
Something very strange… but using the __disable_irq and __enable_irq version, I can read the data on the AX12! !! It returns the right voltage value for example… but if I ask for a movement, it doesn’t work…

Yes, that’s strange because the core_util_critical_section_enter() function calls hal_critical_section_enter(), which in turn calls __disable_irq(). But it’s also a good sign that it can be made to work.

The original AX12 library (published on the mbed components site) doesn’t use a SerialHalfDuplex class. What changes did you made to it to use the SerialHalfDuplex class rather than the Serial one?