SLCAN/CAN-USB implementation for mbed targets

Dependencies:   USBDevice mbed

slcan.cpp

Committer:
devanlai
Date:
2017-02-04
Revision:
3:bc163d555ddc
Parent:
2:1327e61cc56b

File content as of revision 3:bc163d555ddc:

#include "slcan.h"

// Helper methods for parsing commands
static bool parse_hex_digits(const char* input, uint8_t num_digits, uint32_t* value_out) {
    bool success = true;
    uint32_t value = 0;

    uint8_t i;
    for (i=0; i < num_digits; i++) {
        uint32_t nibble = 0;
        if (input[i] >= '0' && input[i] <= '9') {
            nibble = 0x0 + (input[i] - '0');
        } else if (input[i] >= 'a' && input[i] <= 'f') {
            nibble = 0xA + (input[i] - 'a');
        } else if (input[i] >= 'A' && input[i] <= 'F') {
            nibble = 0xA + (input[i] - 'A');
        } else {
            success = false;
            break;
        }
        uint8_t offset = 4*(num_digits-i-1);
        value |= (nibble << offset);
    }

    if (success) {
        *value_out = value;
    }

    return success;
}

static bool parse_hex_values(const char* input, uint8_t num_values, uint8_t* values_out) {
    uint8_t i;
    for (i=0; i < num_values; i++) {
        uint32_t value;
        if (parse_hex_digits(input, 2, &value)) {
            values_out[i] = (uint8_t)value;
        } else {
            return false;
        }
        input += 2;
    }

    return true;
}

static bool parse_dec_digit(const char* input, uint8_t* value_out) {
    if (input[0] >= '0' && input[0] <= '9') {
        *value_out = 0 + (input[0] - '0');
        return true;
    } else {
        return false;
    }
}

static inline char format_nibble(uint8_t x) {
    uint8_t nibble = x & 0x0F;
    return (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
}

static inline char format_digit(uint8_t d) {
    return '0' + d;
}

SLCANBase::SLCANBase() {
    
}

SLCANBase::~SLCANBase() {

}

uint8_t SLCANBase::getFirmwareVersion() {
    // firmware version in BCD
    return 0x10;
}

uint8_t SLCANBase::getHardwareVersion() {
    // hardware version in BCD
    return 0x10;
}

const char* SLCANBase::getSerialString() {
    // 4 character serial number
    return "C254";
}

bool SLCANBase::update() {
    bool active = false;
    if (processCommands()) {
        active = true;
    }
    if (processCANMessages()) {
        active = true;
    }
    
    if (active) {
        flush();
    }
    
    return active;
}

size_t SLCANBase::formattedCANMessageLength(const CANMessage& msg) {
    size_t len;
    if (msg.format == CANStandard) {
        len = 1 + 3 + 1 + (2 * msg.len) + 1;
    } else {
        len = 1 + 8 + 1 + (2 * msg.len) + 1;
    }
    
    return len;
}

size_t SLCANBase::formatCANMessage(const CANMessage& msg, char* buf, size_t max_len) {
    size_t len = formattedCANMessageLength(msg);
    if (len > max_len) {
        return 0;
    }
    
    if (msg.format == CANStandard) {
        *buf++ = (msg.type == CANData) ? 't' : 'r';
        *buf++ = format_nibble((uint8_t)(msg.id >> 8));
        *buf++ = format_nibble((uint8_t)(msg.id >> 4));
        *buf++ = format_nibble((uint8_t)(msg.id >> 0));
        *buf++ = format_digit(msg.len);
    } else {
        *buf++ = (msg.type == CANData) ? 'T' : 'R';
        *buf++ = format_nibble((uint8_t)(msg.id >> 28));
        *buf++ = format_nibble((uint8_t)(msg.id >> 24));
        *buf++ = format_nibble((uint8_t)(msg.id >> 20));
        *buf++ = format_nibble((uint8_t)(msg.id >> 16));
        *buf++ = format_nibble((uint8_t)(msg.id >> 12));
        *buf++ = format_nibble((uint8_t)(msg.id >>  8));
        *buf++ = format_nibble((uint8_t)(msg.id >>  4));
        *buf++ = format_nibble((uint8_t)(msg.id >>  0));
        *buf++ = format_digit(msg.len);
    }
    
    for (unsigned char i=0; i < msg.len; i++) {
        *buf++ = format_nibble((uint8_t)(msg.data[i] >> 4));
        *buf++ = format_nibble((uint8_t)(msg.data[i] >> 0));
    }
    
    *buf++ = '\r';
    
    return len;
}

size_t SLCANBase::commandResponseLength(const char* command) {
    switch (command[0]) {
        case 'N':
        case 'V': {
            return 6;
        }
        case 'v':
        case 'F': {
            return 4;
        }
        case 'T':
        case 't':
        case 'R':
        case 'r': {
            return 2;
        }
        default: {
            return 1;
        }
    }
}

bool SLCANBase::execCommand(const char* command, char* response) {
    bool success = false;
    if (response) {
        response[0] = '\0';
    }

    switch (command[0]) {
        // Configuration commands
        case 'S':
        case 's':
        case 'O':
        case 'L':
        case 'l':
        case 'C':
        case 'Z':
        case 'M':
        case 'm': {
            success = execConfigCommand(command);
            break;
        }
        // Transmission commands
        case 't':
        case 'T':
        case 'r':
        case 'R': {
            success = execTransmitCommand(command, response);
            break;
        }
        // Diagnostic commands
        case 'V':
        case 'v':
        case 'N':
        case 'W':
        case 'F':
            success = execDiagnosticCommand(command, response);
            break;
        default: {
            success = false;
            break;
        }
    }

    return success;
}

bool SLCANBase::execConfigCommand(const char* command) {
    bool success = false;
    size_t len = strlen(command);
    
    // Validate command length
    if (command[0] == 'M' || command[0] == 'm') {
        if (len != 9) {
            return false;
        }
    } else if (command[0] == 's') {
        if (!((len == 5) || (len == 7))) {
            return false;
        }
    } else if (command[0] == 'S' || command[0] == 'Z') {
        if (len != 2) {
            return false;
        }
    } else if (len != 1) {
        return false;
    }

    switch (command[0]) {
        case 'S': {
            bool known = true;
            int baudrate;
            switch (command[1]) {
                case '0': baudrate =   10000; break;
                case '1': baudrate =   20000; break;
                case '2': baudrate =   50000; break;
                case '3': baudrate =  100000; break;
                case '4': baudrate =  125000; break;
                case '5': baudrate =  250000; break;
                case '6': baudrate =  500000; break;
                case '7': baudrate =  800000; break;
                case '8': baudrate = 1000000; break;
                default:  known = false; break;
            }

            if (known) {
                success = setBaudrate(baudrate);
            }

            break;
        }
        case 'O': {
            success = setMode(CAN::Normal);
            break;
        }
        case 'L': {
            success = setMode(CAN::Silent);
            break;
        }
        case 'l': {
            success = setMode(CAN::SilentTest);
            break;
        }
        case 'C': {
            success = setMode(CAN::Reset);
            break;
        }
        case 's': {
            // TODO: implement direct BTR control
            success = true;
            break;
        }
        case 'M':
        case 'm': {
            // TODO: implement filtering
            success = true;
            break;
        }
        case 'Z': {
            // TODO: implement timestamping
            success = true;
            break;
        }
        default: {
            success = false;
            break;
        }
    }

    return success;
}

bool SLCANBase::execTransmitCommand(const char* command, char* response) {
    bool success = false;

    size_t len = strlen(command);

    bool validMessage = false;
    CANMessage msg;

    if (command[0] == 't' || command[0] == 'T') {
        msg.type = CANData;
        msg.format = (command[0] == 't') ? CANStandard : CANExtended;
        size_t idLen = msg.format == CANStandard ? 3 : 8;
        if ((len >= idLen + 2) &&
            parse_hex_digits(&command[1], idLen, (uint32_t*)&msg.id) &&
            parse_dec_digit(&command[idLen + 1], &msg.len)) {
            if ((len == idLen + 2 + 2*msg.len) &&
                (msg.len <= 8) &&
                parse_hex_values(&command[idLen + 2], msg.len, msg.data)) {
                validMessage = true;
            }
        }
    } else if (command[0] == 'r' || command[0] == 'R') {
        msg.type = CANRemote;
        msg.format = (command[0] == 'r') ? CANStandard : CANExtended;
        size_t idLen = msg.format == CANStandard ? 3 : 8;
        if ((len == idLen + 2) &&
            parse_hex_digits(&command[1], idLen, (uint32_t*)(&msg.id)) &&
            parse_dec_digit(&command[idLen + 1], &msg.len)) {
            if (msg.len <= 8) {
                validMessage = true;
            }
        }
    }
    
    if (validMessage) {
        if (command[0] == 'T' || command[0] == 'R') {
            response[0] = 'Z';
            response[1] = '\0';
        } else if (command[0] == 't' || command[0] == 'r') {
            response[0] = 'z';
            response[1] = '\0';
        }
        success = transmitMessage(msg);
    }

    return success;
}

bool SLCANBase::execDiagnosticCommand(const char* command, char* response) {
    bool success = false;
    size_t len = strlen(command);
    
    // Validate command length
    if (command[0] == 'W') {
        if (len != 5) {
            return false;
        }
    } else if (len != 1) {
        return false;
    }
    
    if (!response) {
        return false;
    }

    switch (command[0]) {
        case 'V': {
            success = true;
            uint8_t hwVersion = getHardwareVersion();
            uint8_t fwVersion = getFirmwareVersion();
            
            response[0] = 'V';
            response[1] = format_nibble(hwVersion >> 4);
            response[2] = format_nibble(hwVersion >> 0);
            response[3] = format_nibble(fwVersion >> 4);
            response[4] = format_nibble(fwVersion >> 0);
            response[5] = '\0';
            break;
        }
        case 'v': {
            success = true;
            uint8_t fwVersion = getFirmwareVersion();
            response[0] = 'v';
            response[1] = format_nibble(fwVersion >> 4);
            response[2] = format_nibble(fwVersion >> 0);
            response[3] = '\0';
            break;
        }
        case 'N': {
            success = true;
            const char* serial = getSerialString();
            size_t index = 0;
            response[index++] = 'N';
            for (int i=0; i < 4; i++) {
                char c = serial[i];
                if (c == '\0') {
                    break;
                } else {
                    response[index++] = c;
                }
            }
            response[index] = '\0';
            break;
        }
        case 'F': {
            success = true;
            uint8_t status = 0x00;
            response[0] = 'F';
            response[1] = format_nibble(status >> 4);
            response[2] = format_nibble(status >> 0);
            response[3] = '\0';
        }
        case 'W': {
            // Just swallow the MCP2515 register write command
            success = true;
            break;
        }
        default: {
            success = false;
            break;
        }
    }
    
    return success;
}

USBSLCAN::USBSLCAN(USBSerial& stream, CAN& can)
    : stream(stream),
      can(can),
      messageQueued(false),
      commandQueued(false),
      commandOverflow(false),
      inputCommandLen(0),
      outputPacketLen(0) {
    
}

bool USBSLCAN::setBaudrate(int baudrate) {
    return (can.frequency(baudrate) == 1);
}

bool USBSLCAN::setMode(CAN::Mode mode) {
    return (can.mode(mode) == 1);
}

bool USBSLCAN::transmitMessage(CANMessage& msg) {
    return (can.write(msg) == 1);
}

bool USBSLCAN::getNextCANMessage(CANMessage& msg) {
    return (can.read(msg) == 1);
}

/* Parse and execute a single SLCAN command and enqueue the response */
bool USBSLCAN::processCommands() {
    bool active = false;
    
    // Buffer an entire command
    while (!commandQueued && stream.readable()) {
        char c = (char)stream.getc();
        if (c == '\r') {
            if (commandOverflow) {
                // Replace with a dummy invalid command so we return an error
                inputCommandBuffer[0] = '!';
                inputCommandBuffer[1] = '\0';
                inputCommandLen = 0;
                commandOverflow = false;
                active = true;
            } else {
                // Null-terminate the buffered command
                inputCommandBuffer[inputCommandLen] = '\0';
                inputCommandLen = 0;
                active = true;
            }
            commandQueued = true;
        } else if (c == '\n' && inputCommandLen == 0) {
            // Ignore line feeds immediately after a carriage return
        } else if (commandOverflow) {
            // Swallow the rest of the command when overflow occurs
        } else {
            // Append to the end of the command
            inputCommandBuffer[inputCommandLen++] = c;

            if (inputCommandLen >= sizeof(inputCommandBuffer)) {
                commandOverflow = true;
            }
        }
    }
    
    // Process the current command if there's space to send the response
    if (commandQueued) {
        size_t responseLength = commandResponseLength(inputCommandBuffer);
        if ((outputPacketLen + responseLength) <= sizeof(outputPacketBuffer)) {
            char outputResponseBuffer[32];
            outputResponseBuffer[0] = '\0';
            if (execCommand(inputCommandBuffer, outputResponseBuffer)) {
                // Success
                for (char* s = outputResponseBuffer; *s != '\0'; s++) {
                    outputPacketBuffer[outputPacketLen++] = *s;
                }
                outputPacketBuffer[outputPacketLen++] = '\r';
            } else {
                // Failure
                outputPacketBuffer[outputPacketLen++] = '\a';
            }
            commandQueued = false;
            active = true;
        }
    }
    
    return active;
}

/* Read and enqueue as many received CAN messages as will fit */
bool USBSLCAN::processCANMessages() {
    bool active = false;
    
    size_t bytesAvailable = sizeof(outputPacketBuffer) - outputPacketLen;
    char* packetTail = &outputPacketBuffer[outputPacketLen];
    
    if (messageQueued) {
        size_t bytesConsumed = formatCANMessage(queuedMessage, packetTail, bytesAvailable);
        if (bytesConsumed > 0) {
            active = true;
            messageQueued = false;
            bytesAvailable -= bytesConsumed;
            packetTail += bytesConsumed;
            outputPacketLen += bytesConsumed;
        }
    }
    
    if (!messageQueued) {
        while (getNextCANMessage(queuedMessage)) {
            size_t bytesConsumed = formatCANMessage(queuedMessage, packetTail, bytesAvailable);
            if (bytesConsumed > 0) {
                active = true;
                bytesAvailable -= bytesConsumed;
                packetTail += bytesConsumed;
                outputPacketLen += bytesConsumed;
            } else {
                messageQueued = true;
                break;
            }
        }
    }
    
    return active;
}

/* Attempt to transmit the output queue */
bool USBSLCAN::flush() {
    bool active = false;
    if (outputPacketLen > 0) {
        bool sent = stream.writeBlock((uint8_t*)(outputPacketBuffer),
                                        (uint16_t)(outputPacketLen));
        if (sent) {
            active = true;
            outputPacketLen = 0;
        }
    }
    return active;
}

SerialSLCAN::SerialSLCAN(Serial& stream, CAN& can)
    : stream(stream),
      can(can),
      commandQueued(false),
      commandOverflow(false),
      inputCommandLen(0) {    
}

bool SerialSLCAN::setBaudrate(int baudrate) {
    return (can.frequency(baudrate) == 1);
}

bool SerialSLCAN::setMode(CAN::Mode mode) {
    return (can.mode(mode) == 1);
}

bool SerialSLCAN::transmitMessage(CANMessage& msg) {
    return (can.write(msg) == 1);    
}

bool SerialSLCAN::getNextCANMessage(CANMessage& msg) {
    return (can.read(msg) == 1);
}

/* Parse and execute a single SLCAN command and enqueue the response */
bool SerialSLCAN::processCommands() {
    bool active = false;
    
    // Buffer an entire command
    while (!commandQueued && stream.readable()) {
        char c = (char)stream.getc();
        if (c == '\r') {
            if (commandOverflow) {
                // Replace with a dummy invalid command so we return an error
                inputCommandBuffer[0] = '!';
                inputCommandBuffer[1] = '\0';
                inputCommandLen = 0;
                commandOverflow = false;
                active = true;
            } else {
                // Null-terminate the buffered command
                inputCommandBuffer[inputCommandLen] = '\0';
                inputCommandLen = 0;
                active = true;
            }
            commandQueued = true;
        } else if (c == '\n' && inputCommandLen == 0) {
            // Ignore line feeds immediately after a carriage return
        } else if (commandOverflow) {
            // Swallow the rest of the command when overflow occurs
        } else {
            // Append to the end of the command
            inputCommandBuffer[inputCommandLen++] = c;

            if (inputCommandLen >= sizeof(inputCommandBuffer)) {
                commandOverflow = true;
            }
        }
    }
    
    // Process the current command
    if (commandQueued) {
        char outputResponseBuffer[32];
        outputResponseBuffer[0] = '\0';
        if (execCommand(inputCommandBuffer, outputResponseBuffer)) {
            // Success
            stream.puts(outputResponseBuffer);
            stream.putc('\r');
        } else {
            // Failure
            stream.putc('\a');
        }
        commandQueued = false;
        active = true;
    }
    
    return active;
}

/* Read and enqueue as many received CAN messages as will fit */
bool SerialSLCAN::processCANMessages() {
    bool active = false;
    CANMessage msg;
    while (getNextCANMessage(msg)) {
        char buffer[32];
        size_t len = formatCANMessage(msg, buffer, sizeof(buffer));
        buffer[len] = '\0';
        stream.puts(buffer);
        active = true;
    }
    
    return active;
}

/* Attempt to transmit the output queue */
bool SerialSLCAN::flush() {
    return false;
}