Newer
Older
mbed-os / features / frameworks / COMPONENT_FPGA_CI_TEST_SHIELD / source / MbedTester.cpp
@George Psimenos George Psimenos on 13 Jan 2021 67 KB Remove const suffix
/*
 * Copyright (c) 2019, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "MbedTester.h"
#include "fpga_config.h"
#include "BlockDevice.h"
#include "rtos/ThisThread.h"
#include "platform/mbed_wait_api.h"
#include "platform/mbed_error.h"
#include "drivers/MbedCRC.h"
#include "utest/utest_print.h"

#define mbed_tester_printf(...)

namespace {
    const auto physical_pins = 128;
    const auto logical_pins = 8;
    const auto firmware_region_size = 0x220000;
    const auto firmware_header_size = 0x10000;
    const auto flash_sector_size = 0x1000;
    const auto length_size = 0x4;
    const auto fixed_crc_size = 0x4;
    const auto flash_spi_freq_hz = 2000000;
    const auto analog_count = 4;
    
    const auto physical_nc = (MbedTester::PhysicalIndex)0xFF;
    
    const uint8_t key[8] = {
        0x92, 0x9d, 0x9a, 0x9b,
        0x29, 0x35, 0xa2, 0x65
    };
}

template<size_t width>
class MbedTesterBitMap {
public:

    MbedTesterBitMap()
    {
        for (size_t i = 0; i < _count; i++) {
            _bitmap[i] = 0;
        }
    }

    bool get(size_t index)
    {
        if (index >= width) {
            return false;
        }
        return _bitmap[index / 32] & (1 << (index % 32)) ? true : false;
    }

    void set(size_t index)
    {
        if (index >= width) {
            return;
        }
        _bitmap[index / 32] |= 1 << (index % 32);
    }

    void clear(size_t index)
    {
        if (index >= width) {
            return;
        }
        _bitmap[index / 32] &= ~(1 << (index % 32));
    }

private:

    static const size_t _count = (width + 31) / 32;
    uint32_t _bitmap[(width + 31) / 32];
};

static uint8_t spi_transfer(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t data)
{
    uint8_t ret = 0;
    for (int i = 0; i < 8; i++) {
        *clk = 0;
        *mosi = (data >> (7 - i)) & 1;
        wait_ns(100);
        *clk = 1;
        ret |= *miso ? 1 << (7 - i) : 0;
        wait_ns(100);
    }
    return ret;
}

static void mbed_tester_command(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t miso_index, uint32_t addr, bool write_n_read, uint8_t *data, uint8_t size)
{
    // 8 - Start Key
    for (uint32_t i = 0; i < sizeof(key); i++) {
        spi_transfer(clk, mosi, miso, key[i]);
    }

    // 1 - Physical pin index for MISO
    spi_transfer(clk, mosi, miso, miso_index);

    // 1 - Number of SPI transfers which follow (= N + 5)
    spi_transfer(clk, mosi, miso, size + 5);

    // 4 - Little endian address for transfer
    spi_transfer(clk, mosi, miso, (addr >> (8 * 0)) & 0xFF);
    spi_transfer(clk, mosi, miso, (addr >> (8 * 1)) & 0xFF);
    spi_transfer(clk, mosi, miso, (addr >> (8 * 2)) & 0xFF);
    spi_transfer(clk, mosi, miso, (addr >> (8 * 3)) & 0xFF);

    // 1 - direction
    spi_transfer(clk, mosi, miso, write_n_read ? 1 : 0);

    // N - Data to read or write
    if (write_n_read) {//read: false, write: true
        for (int i = 0; i < size; i++) {
            spi_transfer(clk, mosi, miso, data[i]);
        }
    } else {
        for (int i = 0; i < size; i++) {
            data[i] = spi_transfer(clk, mosi, miso, 0);
        }
    }
    *clk = 0;

}

static bool mbed_tester_test(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t miso_index)
{
    uint8_t buf[4];
    memset(buf, 0, sizeof(buf));
    mbed_tester_command(clk, mosi, miso, miso_index, TESTER_CONTROL, false, buf, sizeof(buf));
    return memcmp(buf, "mbed", sizeof(buf)) == 0;
}


class MbedTesterBlockDevice : public BlockDevice {
public:

    MbedTesterBlockDevice(mbed::DigitalInOut &mosi, mbed::DigitalInOut &miso, mbed::DigitalInOut &clk, mbed::DigitalInOut &cs, uint32_t frequency)
        : _mosi(mosi), _miso(miso), _clk(clk), _cs(cs), _wait_ns(1000000000 / frequency / 2), _init(false)
    {

        // Set initial values
        _cs.write(1);
        _clk.write(0);

        // Set direction
        _mosi.output();
        _miso.input();
        _clk.output();
        _cs.output();
    }


    virtual int init()
    {
        if (_check_id()) {
            _init = true;
        }
        return _init ? BD_ERROR_OK : BD_ERROR_DEVICE_ERROR;
    }

    virtual int deinit()
    {
        _init = false;
        return BD_ERROR_OK;
    }

    virtual int read(void *buffer, bd_addr_t addr, bd_size_t size)
    {
        if (!is_valid_read(addr, size) || !_init) {
            return BD_ERROR_DEVICE_ERROR;
        }

        _assert_cs(true);

        uint8_t cmd[] = {
            0x0B,                       // Fast read
            (uint8_t)(addr >> (2 * 8)), // Address
            (uint8_t)(addr >> (1 * 8)),
            (uint8_t)(addr >> (0 * 8)),
            0x00                        // Dummy
        };
        _write((char *)cmd, sizeof(cmd), NULL, 0);
        _write(NULL, 0, (char *)buffer, size);

        _assert_cs(false);

        return BD_ERROR_OK;
    }

    virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size)
    {
        if (!is_valid_program(addr, size) || !_init) {
            return BD_ERROR_DEVICE_ERROR;
        }

        const bd_size_t max_program_size = 256;
        bd_size_t programmed = 0;
        while (programmed < size) {
            const bd_size_t size_left = size - programmed;
            const bd_size_t program_size = size_left < max_program_size ? size_left : max_program_size;

            _write_enable();
            _page_program(addr + programmed, (const uint8_t *)buffer, program_size);
            _wait_ready();
            programmed += program_size;
        }

        return BD_ERROR_OK;
    }

    virtual int erase(bd_addr_t addr, bd_size_t size)
    {
        if (!is_valid_erase(addr, size) || !_init) {
            return BD_ERROR_DEVICE_ERROR;
        }

        if ((addr == 0) && (size == flash_sector_size)) {
            // Allow 4K erase only on the first sector. The flash on the basys3 does
            // not allow sector erases at the higher addresses.
            _write_enable();
            _sector_erase(addr);
            _wait_ready();
            return BD_ERROR_OK;
        }

        if (!is_valid_erase(addr, size)) {
            return BD_ERROR_DEVICE_ERROR;
        }

        const uint32_t erase_size = get_erase_size();
        bd_size_t erased = 0;
        while (erased < erase_size) {
            _write_enable();
            _block_erase(addr + erased);
            _wait_ready();
            erased += erase_size;
        }
        return BD_ERROR_OK;
    }

    virtual bd_size_t get_read_size() const
    {
        return 1;
    }

    virtual bd_size_t get_program_size() const
    {
        return 1;
    }

    virtual bd_size_t get_erase_size() const
    {
        return 0x10000;
    }

    virtual bd_size_t get_erase_size(bd_addr_t addr) const
    {
        return get_erase_size();
    }

    virtual bd_size_t size() const
    {
        return 8 * 1024 * 1024;
    }

    virtual const char *get_type() const
    {
        return "MbedTesterBlockDevice";
    }

protected:

    void _write_enable()
    {
        uint8_t command[1];

        _assert_cs(true);

        command[0] = 0x06;
        _write((char *)command, 1, NULL, 0);

        _assert_cs(false);
    }

    void _sector_erase(uint32_t addr)
    {
        uint8_t command[4];

        _assert_cs(true);

        command[0] = 0x20;
        command[1] = (addr >> (2 * 8)) & 0xFF;
        command[2] = (addr >> (1 * 8)) & 0xFF;
        command[3] = (addr >> (0 * 8)) & 0xFF;
        _write((char *)command, 4, NULL, 0);

        _assert_cs(false);
    }

    void _block_erase(uint32_t addr)
    {
        uint8_t command[4];

        _assert_cs(true);

        command[0] = 0xD8;
        command[1] = (addr >> (2 * 8)) & 0xFF;
        command[2] = (addr >> (1 * 8)) & 0xFF;
        command[3] = (addr >> (0 * 8)) & 0xFF;
        _write((char *)command, 4, NULL, 0);

        _assert_cs(false);
    }

    void _page_program(uint32_t addr, const uint8_t *data, uint32_t size)
    {
        uint8_t command[4];

        _assert_cs(true);

        command[0] = 0x02;
        command[1] = (addr >> (2 * 8)) & 0xFF;
        command[2] = (addr >> (1 * 8)) & 0xFF;
        command[3] = (addr >> (0 * 8)) & 0xFF;
        _write((char *)command, 4, NULL, 0);
        _write((char *)data, size, NULL, 0);

        _assert_cs(false);
    }

    void _wait_ready()
    {
        uint8_t command[2];
        uint8_t response[2];

        // Wait for ready
        response[1] = 0xFF;
        do {
            _assert_cs(true);

            command[0] = 0x05;
            command[1] = 0;
            _write((char *)command, 2, (char *)response, 2);

            _assert_cs(false);

        } while (response[1] & (1 << 0));
    }

    bool _check_id()
    {
        uint8_t command[1];
        char id0[3];
        char id1[3];

        // Read ID twice and verify it is the same

        _assert_cs(true);

        command[0] = 0x9F;
        _write((char *)command, 1, NULL, 0);
        _write(NULL, 0, id0, sizeof(id0));

        _assert_cs(false);

        _assert_cs(true);

        command[0] = 0x9F;
        _write((char *)command, 1, NULL, 0);
        _write(NULL, 0, id1, sizeof(id1));

        _assert_cs(false);

        // Return failure if IDs are not the same
        for (size_t i = 0; i < sizeof(id0); i++) {
            if (id0[i] != id1[i]) {
                return false;
            }
        }

        // If all 0xFF return failure
        if ((id0[0] == 0xFF) && (id0[1] == 0xFF) && (id0[2] == 0xFF)) {
            return false;
        }

        // If all 0x00 return failure
        if ((id0[0] == 0x00) && (id0[1] == 0x00) && (id0[2] == 0x00)) {
            return false;
        }

        return true;
    }

    void _write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length)
    {
        int transfers = 0;
        if (tx_length > transfers) {
            transfers = tx_length;
        }
        if (rx_length > transfers) {
            transfers = rx_length;
        }

        for (int i = 0; i < transfers; i++) {
            uint8_t out = i < tx_length ? tx_buffer[i] : 0;
            uint8_t in = 0;
            for (int j = 0; j < 8; j++) {
                _mosi.write((out >> 7) & 1);
                out = out << 1;
                wait_ns(_wait_ns);

                _clk.write(1);
                in = (in << 1) | (_miso.read() ? 1 : 0);
                wait_ns(_wait_ns);

                _clk.write(0);
            }
            if (i < rx_length) {
                rx_buffer[i] = in;
            }
        }
    }

    void _assert_cs(bool asserted)
    {
        _clk = 0;
        wait_ns(_wait_ns);
        _cs = asserted ? 0 : 1;
        wait_ns(_wait_ns);
    }

    mbed::DigitalInOut &_mosi;
    mbed::DigitalInOut &_miso;
    mbed::DigitalInOut &_clk;
    mbed::DigitalInOut &_cs;
    uint32_t _wait_ns;
    bool _init;
};

static void dummy_progress(uint8_t)
{
    // Stub progress handler for firmware update/dump
}

// Header taken from app note XAPP1081. Information on the commands
// can be found in the 7 Series FPGA configuration user guide - UG470
static const uint8_t BANK_B_SELECT[] = {
    0x20, 0x00, 0x00, 0x00,     // 0x20000000   NOP
    0x30, 0x02, 0x00, 0x01,     // 0x30020001   WRITE to WBSTAR (Warm boot start address register)
    0x00, 0x23, 0x00, 0x00,     // 0x00230000       0x230000 = Second bank start address
    0x30, 0x00, 0x80, 0x01,     // 0x30008001   WRITE to CMD register
    0x00, 0x00, 0x00, 0x0F,     // 0x0000000F       0x0F = IPROG command (starts warm boot)
    0x20, 0x00, 0x00, 0x00,     // 0x20000000   NOP
    0x20, 0x00, 0x00, 0x00,     // 0x20000000   NOP
    0x20, 0x00, 0x00, 0x00      // 0x20000000   NOP
};

static const uint8_t SYNC_WORD[] = {
    0xAA, 0x99, 0x55, 0x66      // 0xAA995566   Sync word
};

static bool _firmware_header_valid(BlockDevice &flash, bool &valid)
{
    uint8_t buf[64];
    size_t pos = 0;
    size_t read_size;

    // Default to invalid
    valid = false;

    // Check that first portion is erased
    while (pos < flash_sector_size - sizeof(SYNC_WORD)) {
        read_size = flash_sector_size - pos;
        if (read_size > sizeof(buf)) {
            read_size = sizeof(buf);
        }
        if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
            return false;
        }
        pos += read_size;
        for (size_t i = 0; i < read_size; i++) {
            if (buf[i] != 0xFF) {
                valid = false;
                return true;
            }
        }
    }

    // Skip the sync word
    pos += sizeof(SYNC_WORD);

    // Check that BANK_B_SELECT is valid
    read_size = sizeof(BANK_B_SELECT);
    if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
        return false;
    }
    pos += read_size;
    if (memcmp(buf, BANK_B_SELECT, sizeof(BANK_B_SELECT)) != 0) {
        valid = false;
        return true;
    }

    // Check if the rest is 0xFF
    while (pos < firmware_header_size) {
        read_size = firmware_header_size - pos;
        if (read_size > sizeof(buf)) {
            read_size = sizeof(buf);
        }
        if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
            return false;
        }
        pos += read_size;
        for (size_t i = 0; i < read_size; i++) {
            if (buf[i] != 0xFF) {
                valid = false;
                return true;
            }
        }
    }

    valid = true;
    return true;
}

static bool _firmware_get_active_bank(BlockDevice &flash, bool &second_bank_active)
{
    uint8_t buf[sizeof(SYNC_WORD)];

    if (flash.read(buf, flash_sector_size - sizeof(SYNC_WORD), sizeof(SYNC_WORD)) != BD_ERROR_OK) {
        return false;
    }

    second_bank_active = memcmp(buf, SYNC_WORD, sizeof(SYNC_WORD)) == 0 ? true : false;
    return true;
}

static bool _firmware_set_active_bank(BlockDevice &flash, bool second_bank)
{
    bool valid = false;
    if (!_firmware_header_valid(flash, valid)) {
        return false;
    }
    if (!valid) {
        if (flash.erase(0, firmware_header_size) != BD_ERROR_OK) {
            return false;
        }
        if (flash.program(BANK_B_SELECT, flash_sector_size, sizeof(BANK_B_SELECT)) != BD_ERROR_OK) {
            return false;
        }
    }
    if (!flash.erase(0, flash_sector_size)) {
        return false;
    }


    if (second_bank) {
        // Write the sync word. Before the sync word is written the FPGA will boot from the first bank.
        // After the sync word is written the FPGA will boot from the second bank.
        if (flash.program(SYNC_WORD, flash_sector_size - sizeof(SYNC_WORD), sizeof(SYNC_WORD)) != BD_ERROR_OK) {
            return false;
        }
    }

    return true;
}

MbedTester::MbedTester(const PinList *form_factor, const PinList *exclude_pins)
    : _form_factor(form_factor), _exclude_pins(exclude_pins), _control_auto(true), _control_valid(false),
      _clk_index(physical_nc), _mosi_index(physical_nc), _miso_index(physical_nc), _aux_index(physical_nc),
      _clk(NULL), _mosi(NULL), _miso(NULL), _aux(NULL)
{
    _reset();
    _init_io_exp_rst_flag = 0;
}

MbedTester::~MbedTester()
{
    _free_control_pins();
}


void MbedTester::set_control_pins_auto()
{
    _control_auto = true;
}

void MbedTester::set_control_pins_manual(PinName clk, PinName mosi, PinName miso, PinName aux)
{
    int index;
    index = _form_factor.index(clk);
    if (index < 0) {
        error("Invalid CLK index");
    }
    PhysicalIndex clk_index = index;

    index = _form_factor.index(mosi);
    if (index < 0) {
        error("Invalid MOSI index");
    }
    PhysicalIndex mosi_index = index;

    index = _form_factor.index(miso);
    if (index < 0) {
        error("Invalid MISO index");
    }
    PhysicalIndex miso_index = index;

    index = _form_factor.index(aux);
    if (index < 0) {
        error("Invalid AUX index");
    }
    PhysicalIndex aux_index = index;

    if (clk_index + 1 != mosi_index) {
        error("MOSI pin index does not follow CLK as required");
    }

    if ((miso_index == clk_index) || (miso_index == mosi_index)) {
        error("MISO conflicts with a control channel");
    }
    if ((aux_index == clk_index) || (aux_index == mosi_index) || (aux_index == miso_index)) {
        error("AUX conflicts with a control channel");
    }

    // All criteria have been met so set the pins
    _control_auto = false;
    _free_control_pins();
    _clk_index = clk_index;
    _mosi_index = mosi_index;
    _miso_index = miso_index;
    _aux_index = aux_index;
    _setup_control_pins();
    _control_valid = true;
}

bool MbedTester::firmware_dump(mbed::FileHandle *dest, mbed::Callback<void(uint8_t)> progress)
{
    _update_control_pins();

    if (!progress) {
        progress = mbed::callback(dummy_progress);
    }

    // Mapping intentionally different from control channel to prevent
    // unintentional activation (clk and mosi flipped)
    MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, flash_spi_freq_hz);
    sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);

    progress(0);

    if (flash.init() != BD_ERROR_OK) {
        sys_pin_mode_disabled();
        return false;
    }

    // Set the start of dump to the active bank
    bool second_bank_active;
    if (!_firmware_get_active_bank(flash, second_bank_active)) {
        // Error determining active bank
        sys_pin_mode_disabled();
        return false;
    }
    const uint32_t start = firmware_header_size + (second_bank_active ? firmware_region_size : 0);

    // Get the firmware size
    uint32_t offset = 0;
    uint8_t buf[256];
    uint32_t prev_percent_done = 0;
    if (flash.read(buf, start + offset, length_size) != BD_ERROR_OK) {
        sys_pin_mode_disabled();
        return false;
    }
    if (dest->write(buf, length_size) != length_size) {
        sys_pin_mode_disabled();
        return false;
    }
    offset += length_size;
    uint32_t data_size = (buf[0] << (0 * 8)) |
                         (buf[1] << (1 * 8)) |
                         (buf[2] << (2 * 8)) |
                         (buf[3] << (3 * 8));
    if (data_size > firmware_region_size - length_size - fixed_crc_size) {
        data_size = firmware_region_size - length_size - fixed_crc_size;
    }
    const uint32_t firmware_size = data_size + length_size + fixed_crc_size;

    // Dump firmware
    while (offset < firmware_size) {
        uint32_t read_size = firmware_size - offset;
        if (read_size > sizeof(buf)) {
            read_size = sizeof(buf);
        }
        if (flash.read(buf, start + offset, read_size) != BD_ERROR_OK) {
            sys_pin_mode_disabled();
            return false;
        }
        ssize_t write_size = dest->write(buf, read_size);
        if ((uint32_t)write_size != read_size) {
            sys_pin_mode_disabled();
            return false;
        }
        offset += read_size;

        const uint8_t percent_done = (offset * 100) / firmware_size;
        if (percent_done != prev_percent_done) {
            progress(percent_done);
            prev_percent_done = percent_done;
        }
    }

    progress(100);

    sys_pin_mode_disabled();
    return true;
}

bool MbedTester::firmware_dump_all(mbed::FileHandle *dest, mbed::Callback<void(uint8_t)> progress)
{
    _update_control_pins();

    if (!progress) {
        progress = mbed::callback(dummy_progress);
    }

    // Mapping intentionally different from control channel to prevent
    // unintentional activation (clk and mosi flipped)
    MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, flash_spi_freq_hz);
    sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);

    progress(0);

    if (flash.init() != BD_ERROR_OK) {
        sys_pin_mode_disabled();
        return false;
    }

    uint32_t pos = 0;
    uint8_t buf[256];
    uint32_t prev_percent_done = 0;
    const uint32_t total_size = flash.size();
    while (pos < total_size) {
        uint32_t read_size = total_size - pos;
        if (read_size > sizeof(buf)) {
            read_size = sizeof(buf);
        }
        if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
            sys_pin_mode_disabled();
            return false;
        }
        ssize_t write_size = dest->write(buf, read_size);
        if ((uint32_t)write_size != read_size) {
            sys_pin_mode_disabled();
            return false;
        }
        pos += read_size;

        const uint8_t percent_done = (pos * 100) / total_size;
        if (percent_done != prev_percent_done) {
            progress(percent_done);
            prev_percent_done = percent_done;
        }
    }

    progress(100);

    sys_pin_mode_disabled();
    return true;
}

bool MbedTester::firmware_update(mbed::FileHandle *src, mbed::Callback<void(uint8_t)> progress)
{
    _update_control_pins();

    if (!progress) {
        progress = mbed::callback(dummy_progress);
    }

    // Mapping intentionally different from control channel to prevent
    // unintentional activation (clk and mosi flipped)
    MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, flash_spi_freq_hz);
    sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);

    progress(0);

    if (flash.init() != BD_ERROR_OK) {
        sys_pin_mode_disabled();
        return false;
    }

    // Validate file size
    const uint32_t file_size = src->size();
    if (file_size > firmware_region_size) {
        // Firmware image too big
        sys_pin_mode_disabled();
        return false;
    }
    if (file_size < length_size + fixed_crc_size) {
        // Firmware image too small
        sys_pin_mode_disabled();
        return false;
    }

    // Set the start of programming to the inactive bank
    bool second_bank_active;
    if (!_firmware_get_active_bank(flash, second_bank_active)) {
        // Error determining active bank
        sys_pin_mode_disabled();
        return false;
    }
    const uint32_t start = firmware_header_size + (second_bank_active ? 0 : firmware_region_size);

    // Setup CRC calculation
    uint32_t crc;
    mbed::MbedCRC<POLY_32BIT_ANSI, 32> ct;
    if (ct.compute_partial_start(&crc) != 0) {
        sys_pin_mode_disabled();
        return false;
    }

    uint8_t buf[256];
    const bd_size_t erase_size = flash.get_erase_size();
    uint32_t offset = 0;
    uint32_t prev_percent_done = 0;
    uint32_t stored_crc = 0;
    bool size_valid = false;
    while (offset < file_size) {

        // Prepare data
        uint32_t program_size = file_size - offset;
        if (program_size > sizeof(buf)) {
            program_size = sizeof(buf);
        }
        ssize_t read_size = src->read(buf, program_size);
        if (read_size < 0) {
            sys_pin_mode_disabled();
            return false;
        } else if (read_size == 0) {
            break;
        }
        program_size = read_size;

        // Record values and calculate checksum
        uint32_t crc_offset = 0;
        uint32_t crc_size = program_size;
        if (offset == 0) {
            // Overlap with the size field

            // Check that the data length is correct
            const size_t data_size = (buf[0] << (0 * 8)) |
                                     (buf[1] << (1 * 8)) |
                                     (buf[2] << (2 * 8)) |
                                     (buf[3] << (3 * 8));
            if (data_size != file_size - length_size - fixed_crc_size) {
                // Invalid data length
                sys_pin_mode_disabled();
                return false;
            }
            size_valid = true;

            // Don't include the length in the checksum
            crc_offset += length_size;
            crc_size -= length_size;
        }
        if (offset + program_size > file_size - fixed_crc_size) {
            // Overlap with the CRC field
            for (uint32_t i = 0; i < fixed_crc_size; i++) {
                uint32_t byte_offset = file_size - fixed_crc_size + i;
                if ((byte_offset >= offset) && (byte_offset < offset + program_size)) {
                    uint32_t buf_pos = byte_offset - offset;
                    stored_crc |= buf[buf_pos] << (i * 8);

                    // Don't include the stored CRC in the CRC
                    crc_size--;
                }
            }
        }
        if (ct.compute_partial(buf + crc_offset, crc_size, &crc) != 0) {
            sys_pin_mode_disabled();
            return false;
        }

        // Write data to file
        const uint32_t addr = start + offset;
        if (addr % erase_size == 0) {
            if (flash.erase(addr, erase_size) != BD_ERROR_OK) {
                sys_pin_mode_disabled();
                return false;
            }
        }
        if (flash.program(buf, addr, read_size) != BD_ERROR_OK) {
            sys_pin_mode_disabled();
            return false;
        }

        offset += program_size;

        const uint8_t percent_done = (offset * 100) / file_size;
        if (percent_done != prev_percent_done) {
            progress(percent_done);
            prev_percent_done = percent_done;
        }
    }

    // Check that everything was good and if so switch active bank
    if (!size_valid) {
        sys_pin_mode_disabled();
        return false;
    }
    if (ct.compute_partial_stop(&crc) != 0) {
        sys_pin_mode_disabled();
        return false;
    }
    if (crc != stored_crc) {
        sys_pin_mode_disabled();
        return false;
    }
    if (!_firmware_set_active_bank(flash, !second_bank_active)) {
        sys_pin_mode_disabled();
        return false;
    }

    progress(100);

    sys_pin_mode_disabled();
    return true;
}

void MbedTester::pin_map_set(PinName physical, LogicalPin logical)
{
    int index = _form_factor.index(physical);
    if (index < 0) {
        error("Pin %i not in form factor", physical);
        return;
    }
    if (logical >= LogicalPinTotal) {
        error("Invalid logical pin %i", logical);
        return;
    }
    utest_printf("(FPGA %u)...", index);
    pin_map_index(index, logical);
}

void MbedTester::pin_map_reset()
{
    for (uint32_t i = 0; i < sizeof(_mapping) / sizeof(_mapping[0]); i++) {
        _mapping[i] = physical_nc;
    }

    uint8_t pin_buf[physical_pins + logical_pins];
    memset(pin_buf, 0xFF, sizeof(pin_buf));
    write(TESTER_REMAP, pin_buf, sizeof(pin_buf));
}

void MbedTester::peripherals_reset()
{
    uint8_t buf = TESTER_CONTROL_RESET_PERIPHERALS;
    write(TESTER_CONTROL_RESET, &buf, sizeof(buf));
}

void MbedTester::reset()
{
    // Reset pullup settings
    pin_pull_reset_all();

    // Reset the FPGA
    uint8_t buf = TESTER_CONTROL_RESET_ALL;
    write(TESTER_CONTROL_RESET, &buf, sizeof(buf));

    // Reset the pinmap
    // NOTE - this is only needed for compatibility with
    //        older firmware which resets the mapping
    //        of all pins to 0x00 rather than 0xFF.
    pin_map_reset();

    // Reset internal state variables
    _reset();
}

void MbedTester::reprogram()
{
    // Trigger reprogramming
    uint8_t buf = TESTER_CONTROL_REPROGRAM;
    write(TESTER_CONTROL_RESET, &buf, sizeof(buf));

    // Reset internal state variables
    _reset();
}

uint32_t MbedTester::version()
{
    uint32_t software_version;

    read(TESTER_CONTROL_VERSION, (uint8_t *)&software_version, sizeof(software_version));

    return software_version;
}

void MbedTester::select_peripheral(Peripheral peripheral)
{
    uint8_t data = peripheral;
    write(TESTER_PERIPHERAL_SELECT, &data, sizeof(data));
}

void MbedTester::pin_pull_reset_all()
{
    _init_io_exp_rst_flag = 1;
    sys_pin_write(I2CReset, 0, true);
    wait_us(1);
    sys_pin_write(I2CReset, 0, false);
}

int MbedTester::pin_set_pull(PinName pin, PullMode mode)
{
    int index = _form_factor.index(pin);
    if ((index < 0) || (index > 127)) {
        error("Pin %i not in form factor", pin);
        return -1;
    }

    return pin_set_pull_index(index, mode);
}

int MbedTester::pin_set_pull_index(int index, PullMode mode)
{
    // Reset IO expanders once after Mbed reset if user attempts
    // to read/write them without explicitly reseting them
    if (_init_io_exp_rst_flag == 0) {
        pin_pull_reset_all();
    }
    uint8_t chip_num;//can be 0-5
    uint16_t dev_addr;//can be 0x44 or 0x46
    uint8_t port_num;//can be 0-23
    uint8_t output_port_reg;//can be 4, 5, or 6
    uint8_t config_reg;//can be 12, 13, or 14
    uint8_t reg_bit;//can be 0-7
    uint8_t cmd0[2];//for writing configuration register
    uint8_t cmd1[2];//for writing output port register
    uint8_t i2c_index;//can be 0, 1, or 2 for TESTER_SYS_IO_MODE_I2C_IO_EXPANDER0/1/2

    chip_num = index / 24;
    if ((chip_num == 0) || (chip_num == 1)) {
        i2c_index = 0;
    } else if ((chip_num == 2) || (chip_num == 3)) {
        i2c_index = 1;
    } else if ((chip_num == 4) || (chip_num == 5)) {
        i2c_index = 2;
    } else {
        error("Corrupt index %i, should be 0-127\r\n", index);
        return -1;
    }
    dev_addr = (chip_num % 2) ? 0x44 : 0x46;
    port_num = index % 24;
    output_port_reg = 4 + (port_num / 8);
    config_reg = 12 + (port_num / 8);
    reg_bit = port_num % 8;

    uint8_t read_config_byte[1];
    uint8_t read_output_byte[1];
    if (io_expander_i2c_read(i2c_index, dev_addr, config_reg, read_config_byte, 1) != 0) {
        return -1;
    }
    if (io_expander_i2c_read(i2c_index, dev_addr, output_port_reg, read_output_byte, 1) != 0) {
        return -1;
    }
    cmd0[0] = config_reg;
    if ((mode == PullDown) || (mode == PullUp)) {
        cmd0[1] = read_config_byte[0] & ~(1 << reg_bit);
        cmd1[0] = output_port_reg;
        if (mode == PullDown) {
            cmd1[1] = read_output_byte[0] & ~(1 << reg_bit);
        } else if (mode == PullUp) {
            cmd1[1] = read_output_byte[0] | (1 << reg_bit);
        }
    } else if (mode == PullNone) {
        cmd0[1] = read_config_byte[0] | (1 << reg_bit);
    }

    //write configuration register for all 3 modes
    if (io_expander_i2c_write(i2c_index, dev_addr, cmd0, 2) != 0) {
        return -1;
    }
    //only write output register for pulldown and pullup
    if ((mode == PullDown) || (mode == PullUp)) {
        if (io_expander_i2c_write(i2c_index, dev_addr, cmd1, 2) != 0) {
            return -1;
        }
    }
    return 0;
}

uint8_t MbedTester::io_expander_read(PinName pin, IOExpanderReg reg_type)
{
    int index = _form_factor.index(pin);

    return io_expander_read_index(index, reg_type);
}

uint8_t MbedTester::io_expander_read_index(int index, IOExpanderReg reg_type)
{
    // Reset IO expanders once after Mbed reset if user attempts
    // to read/write them without explicitly reseting them
    if (_init_io_exp_rst_flag == 0) {
        pin_pull_reset_all();
    }
    uint8_t read_byte[1] = {0};
    uint8_t chip_num;//can be 0-5
    uint16_t dev_addr;//can be 0x44 or 0x46
    uint8_t port_num;//can be 0-23
    uint8_t input_port_reg;//can be 0, 1, or 2
    uint8_t output_port_reg;//can be 4, 5, or 6
    uint8_t config_reg;//can be 12, 13, or 14
    uint8_t reg_bit;//can be 0-7
    uint8_t i2c_index;

    chip_num = index / 24;
    if ((chip_num == 0) || (chip_num == 1)) {
        i2c_index = 0;
    } else if ((chip_num == 2) || (chip_num == 3)) {
        i2c_index = 1;
    } else if ((chip_num == 4) || (chip_num == 5)) {
        i2c_index = 2;
    } else {
        i2c_index = 0xFF;
        error("Invalid pin index, index should be in the range of 0-127");
    }
    dev_addr = (chip_num % 2) ? 0x44 : 0x46;
    port_num = index % 24;
    input_port_reg = (port_num / 8);
    output_port_reg = 4 + (port_num / 8);
    config_reg = 12 + (port_num / 8);
    reg_bit = port_num % 8;
    uint8_t reg;
    if (reg_type == RegInput) {
        reg = input_port_reg;
    } else if (reg_type == RegOutput) {
        reg = output_port_reg;
    } else if (reg_type == RegConfig) {
        reg = config_reg;
    } else {
        reg = 0xFF;
        error("Invalid register type, should be: INPUT, OUTPUT, or RegConfig");
    }

    int read_success = io_expander_i2c_read(i2c_index, dev_addr, reg, read_byte, 1);
    MBED_ASSERT(read_success == 0);
    uint8_t bit = (read_byte[0] & (1 << reg_bit)) >> reg_bit;
    return bit;
}

int MbedTester::io_expander_i2c_read(uint8_t i2c_index, uint8_t dev_addr, uint8_t start_reg, uint8_t *data, int length)
{
    _update_control_pins();
    //sda_in = _miso_index
    //sda_val = _aux_index
    //scl_in = _mosi_index (physical_nc)
    //scl_val = _clk_index
    mbed::DigitalInOut *sda_in = _miso;
    mbed::DigitalInOut *sda_val = _aux;
    mbed::DigitalInOut *scl_val = _clk;
    sda_in->input();
    sda_val->output();
    *sda_val = 1;
    scl_val->output();
    sys_pin_mode_i2c_io_expander(i2c_index, _miso_index, _aux_index, physical_nc, _clk_index);

    //start condition
    *scl_val = 1;
    wait_ns(2500);
    *sda_val = 0;
    wait_ns(2500);

    // begin writing data, dev_addr first
    uint8_t send_bit;
    for (int j = 0; j < 2; j += 1) {
        *scl_val = 0;
        *sda_val = 0;
        wait_ns(2500);
        for (int i = 7; i > -1; i -= 1) {
            if (j == 0) {
                send_bit = (dev_addr & (1 << i)) >> i;
            } else {
                send_bit = (start_reg & (1 << i)) >> i;
            }
            *sda_val = send_bit;
            wait_ns(500);

            *scl_val = 1;
            wait_ns(2500);
            *scl_val = 0;
            wait_ns(1000);
            *sda_val = 0;
            wait_ns(1000);
        }
        // receive ACK from IO extender
        *sda_val = 1;//make sda high z to receive ACK
        //clk the ACK
        *scl_val = 1;
        //read sda to check for ACK or NACK
        if (*sda_in) {
            return -1;//NACK - write failed
        }
        wait_ns(2500);
        *scl_val = 0;
        wait_ns(2500);
    }

    //start condition
    *sda_val = 1;
    *scl_val = 1;
    wait_ns(2500);
    *sda_val = 0;
    wait_ns(2500);

    // begin reading data, write (dev_addr | 1) first
    dev_addr |= 1;
    for (int j = -1; j < length; j += 1) {
        uint8_t read_byte = 0;
        for (int i = 7; i > -1; i -= 1) {
            if (j == -1) {
                *scl_val = 0;
                *sda_val = 0;
                send_bit = (dev_addr & (1 << i)) >> i;
                *sda_val = send_bit;
                wait_ns(500);

                *scl_val = 1;
                wait_ns(2500);
                *scl_val = 0;
                wait_ns(1000);
                *sda_val = 0;
                wait_ns(1000);
            } else {
                *scl_val = 1;
                read_byte |= (*sda_in << i);
                wait_ns(2500);
                *scl_val = 0;
                wait_ns(2500);
            }
        }
        if (j > -1) {
            data[j] = read_byte;
        }
        if (j == -1) {
            // receive ACK from IO extender
            *sda_val = 1;//make sda high z to receive ACK
            //clk the ACK
            *scl_val = 1;
            //read sda to check for ACK or NACK
            if (*sda_in) {
                return -1;//NACK - write failed
            }
            wait_ns(2500);
            *scl_val = 0;
            wait_ns(2500);
        } else {
            if (j == (length - 1)) { //NACK to signal end of read
                *sda_val = 1;
                wait_ns(1000);
                *scl_val = 1;
                wait_ns(2500);
                *scl_val = 0;
                wait_ns(1500);
            } else {//ACK to signal read will continue
                *sda_val = 0;
                wait_ns(1000);
                *scl_val = 1;
                wait_ns(2500);
                *scl_val = 0;
                wait_ns(500);
                *sda_val = 1;
                wait_ns(1000);
            }
        }
    }

    //stop condition
    *sda_val = 0;
    wait_ns(2500);
    *scl_val = 1;
    wait_ns(2500);
    *sda_val = 1;
    wait_ns(2500);

    sys_pin_mode_disabled();

    return 0;
}

int MbedTester::io_expander_i2c_write(uint8_t i2c_index, uint8_t dev_addr, uint8_t *data, int length)
{
    _update_control_pins();
    //sda_in = _miso_index
    //sda_val = _aux_index
    //scl_in = _mosi_index (physical_nc)
    //scl_val = _clk_index
    mbed::DigitalInOut *sda_in = _miso;
    mbed::DigitalInOut *sda_val = _aux;
    mbed::DigitalInOut *scl_val = _clk;
    sda_in->input();
    sda_val->output();
    *sda_val = 1;
    scl_val->output();
    sys_pin_mode_i2c_io_expander(i2c_index, _miso_index, _aux_index, physical_nc, _clk_index);

    //start condition
    *scl_val = 1;
    wait_ns(2500);
    *sda_val = 0;
    wait_ns(2500);

    // begin writing data, dev_addr first
    uint8_t send_bit;
    for (int j = -1; j < length; j += 1) {
        *scl_val = 0;
        *sda_val = 0;
        for (int i = 7; i > -1; i -= 1) {
            if (j == -1) {
                send_bit = (dev_addr & (1 << i)) >> i;
            } else {
                send_bit = (data[j] & (1 << i)) >> i;
            }

            *sda_val = send_bit;
            wait_ns(500);

            *scl_val = 1;
            wait_ns(2500);
            *scl_val = 0;
            wait_ns(1000);
            *sda_val = 0;
            wait_ns(1000);
        }
        // receive ACK from IO extender
        *sda_val = 1;//make sda high z to receive ACK
        //clk the ACK
        *scl_val = 1;
        //read sda to check for ACK or NACK
        if (*sda_in) {
            return -1;//NACK - write failed
        }
        wait_ns(2500);
        *scl_val = 0;
        wait_ns(2500);
    }

    //stop condition
    *sda_val = 0;
    wait_ns(2500);
    *scl_val = 1;
    wait_ns(2500);
    *sda_val = 1;
    wait_ns(2500);

    sys_pin_mode_disabled();

    return 0;
}

int MbedTester::pin_set_pull_bb(PinName pin, PullMode mode)
{
    int index = _form_factor.index(pin);
    if ((index < 0) || (index > 127)) {
        error("Pin %i not in form factor", pin);
        return -1;
    }
    uint8_t chip_num;//can be 0-5
    SystemPin sda;//can be I2CSda0, I2CSda1, or I2CSda2
    SystemPin scl;//can be I2CScl0, I2CScl1, or I2CScl2
    uint16_t dev_addr;//can be 0x44 or 0x46
    uint8_t port_num;//can be 0-23
    uint8_t output_port_reg;//can be 4, 5, or 6
    uint8_t config_reg;//can be 12, 13, or 14
    uint8_t reg_bit;//can be 0-7
    uint8_t cmd0[2];//for writing configuration register
    uint8_t cmd1[2];//for writing output port register

    chip_num = index / 24;
    if ((chip_num == 0) || (chip_num == 1)) {
        sda = I2CSda0;
        scl = I2CScl0;
    } else if ((chip_num == 2) || (chip_num == 3)) {
        sda = I2CSda1;
        scl = I2CScl1;
    } else if ((chip_num == 4) || (chip_num == 5)) {
        sda = I2CSda2;
        scl = I2CScl2;
    } else {
        error("Pin %i not in form factor", pin);
        return -1;
    }
    dev_addr = (chip_num % 2) ? 0x44 : 0x46;
    port_num = index % 24;
    output_port_reg = 4 + (port_num / 8);
    config_reg = 12 + (port_num / 8);
    reg_bit = port_num % 8;

    uint8_t read_config_byte[1];
    uint8_t read_output_byte[1];
    if (io_expander_i2c_read_bb(sda, scl, dev_addr, config_reg, read_config_byte, 1) != 0) {
        return -1;
    }
    if (io_expander_i2c_read_bb(sda, scl, dev_addr, output_port_reg, read_output_byte, 1) != 0) {
        return -1;
    }
    cmd0[0] = config_reg;
    if ((mode == PullDown) || (mode == PullUp)) {
        cmd0[1] = read_config_byte[0] & ~(1 << reg_bit);
        cmd1[0] = output_port_reg;
        if (mode == PullDown) {
            cmd1[1] = read_output_byte[0] & ~(1 << reg_bit);
        } else if (mode == PullUp) {
            cmd1[1] = read_output_byte[0] | (1 << reg_bit);
        }
    } else if (mode == PullNone) {
        cmd0[1] = read_config_byte[0] | (1 << reg_bit);
    }

    //write configuration register for all 3 modes
    if (io_expander_i2c_write_bb(sda, scl, dev_addr, cmd0, 2) != 0) {
        return -1;
    }
    //only write output register for pulldown and pullup
    if ((mode == PullDown) || (mode == PullUp)) {
        if (io_expander_i2c_write_bb(sda, scl, dev_addr, cmd1, 2) != 0) {
            return -1;
        }
    }
    return 0;
}

uint8_t MbedTester::io_expander_read_bb(PinName pin, IOExpanderReg reg_type)
{
    int index = _form_factor.index(pin);
    uint8_t read_byte[1] = {0};
    uint8_t chip_num;//can be 0-5
    SystemPin sda;//can be I2CSda0, I2CSda1, or I2CSda2
    SystemPin scl;//can be I2CScl0, I2CScl1, or I2CScl2
    uint16_t dev_addr;//can be 0x44 or 0x46
    uint8_t port_num;//can be 0-23
    uint8_t input_port_reg;//can be 0, 1, or 2
    uint8_t output_port_reg;//can be 4, 5, or 6
    uint8_t config_reg;//can be 12, 13, or 14
    uint8_t reg_bit;//can be 0-7

    chip_num = index / 24;
    if ((chip_num == 0) || (chip_num == 1)) {
        sda = I2CSda0;
        scl = I2CScl0;
    } else if ((chip_num == 2) || (chip_num == 3)) {
        sda = I2CSda1;
        scl = I2CScl1;
    } else if ((chip_num == 4) || (chip_num == 5)) {
        sda = I2CSda2;
        scl = I2CScl2;
    } else {
        sda = (SystemPin) - 1;
        scl = (SystemPin) - 1;
        error("Invalid pin index, index should be in the range of 0-127");
    }

    dev_addr = (chip_num % 2) ? 0x44 : 0x46;
    port_num = index % 24;
    input_port_reg = (port_num / 8);
    output_port_reg = 4 + (port_num / 8);
    config_reg = 12 + (port_num / 8);
    reg_bit = port_num % 8;
    uint8_t reg;
    if (reg_type == RegInput) {
        reg = input_port_reg;
    } else if (reg_type == RegOutput) {
        reg = output_port_reg;
    } else if (reg_type == RegConfig) {
        reg = config_reg;
    } else {
        reg = 0xFF;
        error("Invalid register type, should be: INPUT, OUTPUT, or CONFIG");
    }

    int read_success = io_expander_i2c_read_bb(sda, scl, dev_addr, reg, read_byte, 1);
    MBED_ASSERT(read_success == 0);
    uint8_t bit = (read_byte[0] & (1 << reg_bit)) >> reg_bit;
    return bit;
}

int MbedTester::io_expander_i2c_read_bb(SystemPin sda, SystemPin scl, uint8_t dev_addr, uint8_t start_reg, uint8_t *data, int length)
{
    //start condition
    sys_pin_write(sda, 0, false);
    sys_pin_write(scl, 0, false);
    sys_pin_write(sda, 0, true);

    // begin writing data, dev_addr first
    uint8_t send_bit;
    for (int j = 0; j < 2; j += 1) {
        sys_pin_write(scl, 0, true);
        sys_pin_write(sda, 0, true);
        for (int i = 7; i > -1; i -= 1) {
            if (j == 0) {
                send_bit = (dev_addr & (1 << i)) >> i;
            } else {
                send_bit = (start_reg & (1 << i)) >> i;
            }
            if (send_bit == 1) {
                sys_pin_write(sda, 0, false);
            } else if (send_bit == 0) {
                sys_pin_write(sda, 0, true);
            }
            sys_pin_write(scl, 0, false);
            sys_pin_write(scl, 0, true);
            sys_pin_write(sda, 0, true);
        }
        // receive ACK from IO extender
        sys_pin_write(sda, 0, false);//make sda high z to receive ACK
        //clk the ACK
        sys_pin_write(scl, 0, false);
        //read sda to check for ACK or NACK
        if (sys_pin_read(sda)) {
            return -1;//NACK - write failed
        }
        sys_pin_write(scl, 0, true);
    }

    //start condition
    sys_pin_write(sda, 0, false);
    sys_pin_write(scl, 0, false);
    sys_pin_write(sda, 0, true);

    // begin reading data, write (dev_addr | 1) first
    dev_addr |= 1;
    for (int j = -1; j < length; j += 1) {
        uint8_t read_byte = 0;
        for (int i = 7; i > -1; i -= 1) {
            if (j == -1) {
                sys_pin_write(scl, 0, true);
                sys_pin_write(sda, 0, true);
                send_bit = (dev_addr & (1 << i)) >> i;
                if (send_bit == 1) {
                    sys_pin_write(sda, 0, false);
                } else if (send_bit == 0) {
                    sys_pin_write(sda, 0, true);
                }
                sys_pin_write(scl, 0, false);
                sys_pin_write(scl, 0, true);
                sys_pin_write(sda, 0, true);
            } else {
                sys_pin_write(scl, 0, false);
                read_byte |= (sys_pin_read(sda) << i);
                sys_pin_write(scl, 0, true);
            }
        }
        if (j > -1) {
            data[j] = read_byte;
        }
        if (j == -1) {
            // receive ACK from IO extender
            sys_pin_write(sda, 0, false);//make sda high z to receive ACK
            //clk the ACK
            sys_pin_write(scl, 0, false);
            //read sda to check for ACK or NACK
            if (sys_pin_read(sda)) {
                return -1;//NACK - write failed
            }
            sys_pin_write(scl, 0, true);
        } else {
            if (j == (length - 1)) { //NACK to signal end of read
                sys_pin_write(sda, 0, false);
                sys_pin_write(scl, 0, false);
                sys_pin_write(scl, 0, true);
            } else {//ACK to signal read will continue
                sys_pin_write(sda, 0, true);
                sys_pin_write(scl, 0, false);
                sys_pin_write(scl, 0, true);
                sys_pin_write(sda, 0, false);
            }
        }
    }

    //stop condition
    sys_pin_write(sda, 0, true);
    sys_pin_write(scl, 0, false);
    sys_pin_write(sda, 0, false);
    return 0;
}

int MbedTester::io_expander_i2c_write_bb(SystemPin sda, SystemPin scl, uint8_t dev_addr, uint8_t *data, int length)
{
    //start condition
    sys_pin_write(sda, 0, false);
    sys_pin_write(scl, 0, false);
    sys_pin_write(sda, 0, true);

    // begin writing data, dev_addr first
    uint8_t send_bit;
    for (int j = -1; j < length; j += 1) {
        sys_pin_write(scl, 0, true);
        sys_pin_write(sda, 0, true);
        for (int i = 7; i > -1; i -= 1) {
            if (j == -1) {
                send_bit = (dev_addr & (1 << i)) >> i;
            } else {
                send_bit = (data[j] & (1 << i)) >> i;
            }
            if (send_bit == 1) {
                sys_pin_write(sda, 0, false);
            } else if (send_bit == 0) {
                sys_pin_write(sda, 0, true);
            }
            sys_pin_write(scl, 0, false);
            sys_pin_write(scl, 0, true);
            sys_pin_write(sda, 0, true);
        }
        // receive ACK from IO extender
        sys_pin_write(sda, 0, false);//make sda high z to receive ACK
        //clk the ACK
        sys_pin_write(scl, 0, false);
        //read sda to check for ACK or NACK
        if (sys_pin_read(sda)) {
            return -1;//NACK - write failed
        }
        sys_pin_write(scl, 0, true);
    }

    //stop condition
    sys_pin_write(sda, 0, true);
    sys_pin_write(scl, 0, false);
    sys_pin_write(sda, 0, false);
    return 0;
}

void MbedTester::set_analog_out(bool enable, float voltage)
{
    uint32_t cycles_high = (int)(100 * voltage);
    uint32_t period = 100;
    set_pwm_period_and_cycles_high(period, cycles_high);
    set_pwm_enable(enable);
}

int MbedTester::set_mux_addr(PinName pin)
{
    int index = _form_factor.index(pin);
    if ((index < 0) || (index > 127)) {
        error("Pin %i not in form factor", pin);
        return -1;
    }

    return set_mux_addr_index(index);
}

int MbedTester::set_mux_addr_index(int index)
{
    sys_pin_write(AnalogMuxAddr0, index & 0x01, true);
    sys_pin_write(AnalogMuxAddr1, (index & 0x02) >> 1, true);
    sys_pin_write(AnalogMuxAddr2, (index & 0x04) >> 2, true);
    sys_pin_write(AnalogMuxAddr3, (index & 0x08) >> 3, true);
    sys_pin_write(AnalogMuxAddr4, (index & 0x10) >> 4, true);
    sys_pin_write(AnalogMuxAddr5, (index & 0x20) >> 5, true);
    sys_pin_write(AnalogMuxAddr6, (index & 0x40) >> 6, true);
    sys_pin_write(AnalogMuxAddr7, (index & 0x80) >> 7, true);

    return 0;
}

void MbedTester::set_mux_enable(bool val)
{
    if (val == true) {
        sys_pin_write(AnalogMuxEnable, 0, true);//enable analog MUXes
    } else if (val == false) {
        sys_pin_write(AnalogMuxEnable, 1, true);//disable analog MUXes
    }
    wait_us(10);
}

void MbedTester::set_pwm_enable(bool val)
{
    uint8_t data;
    if (val == true) {
        data = 1;
    } else if (val == false) {
        data = 0;
    }
    write(TESTER_SYS_IO_PWM_ENABLE, &data, sizeof(data));
}

bool MbedTester::get_pwm_enable()
{
    uint8_t val = 0;
    read(TESTER_SYS_IO_PWM_ENABLE, &val, sizeof(val));
    if (val == 1) {
        return true;
    } else if (val == 0) {
        return false;
    } else {
        error("Corrupt pwm enable value");
        return false;
    }
}

void MbedTester::set_pwm_period_and_cycles_high(uint32_t period, uint32_t cycles_high)
{
    set_pwm_enable(false);
    uint32_t p = period - 1;//period in cycles
    uint32_t d = cycles_high;//number of cycles pwm out is high
    write(TESTER_SYS_IO_PWM_PERIOD, (uint8_t *)&p, sizeof(p));
    write(TESTER_SYS_IO_PWM_CYCLES_HIGH, (uint8_t *)&d, sizeof(d));
    set_pwm_enable(true);
}

uint32_t MbedTester::get_pwm_period()
{
    uint32_t period = 0;
    read(TESTER_SYS_IO_PWM_PERIOD, (uint8_t *)&period, sizeof(period));
    return period + 1;//clk cycles
}

uint8_t MbedTester::get_pwm_cycles_high()
{
    uint8_t cycles_high = 0;
    read(TESTER_SYS_IO_PWM_CYCLES_HIGH, &cycles_high, sizeof(cycles_high));
    return cycles_high;
}

uint16_t MbedTester::get_analogmuxin_measurement()
{
    rtos::ThisThread::sleep_for(1);//wait for value to stabalize
    //take snapshot of conversion value to make safe for reading
    set_snapshot();
    uint16_t an_mux_analogin_measurement = 0;
    read(TESTER_SYS_IO_AN_MUX_ANALOGIN_MEASUREMENT, (uint8_t *)&an_mux_analogin_measurement, sizeof(an_mux_analogin_measurement));
    return an_mux_analogin_measurement;
}

uint16_t MbedTester::get_anin_measurement(int index)
{
    //check index is in bounds
    if ((index < 0) || (index >= analog_count)) {
        error("AnalogIn index is out of bounds");
    }
    //take snapshot of conversion value to make safe for reading
    set_snapshot();
    uint16_t anin_measurement = 0;
    read((TESTER_SYS_IO_ANIN0_MEASUREMENT + (index * 10)), (uint8_t *)&anin_measurement, sizeof(anin_measurement)); //10 because sizeof measurement + sizeof measurements_sum = 10
    return anin_measurement;
}

void MbedTester::get_anin_sum_samples_cycles(int index, uint64_t *sum, uint32_t *samples, uint64_t *cycles)
{
    //check index is in bounds
    if ((index < 0) || (index >= analog_count)) {
        error("AnalogIn index is out of bounds");
    }
    //take snapshot of the sum/samples/cycles so that all 3 values are correct in relation to each other
    set_snapshot();
    read((TESTER_SYS_IO_ANIN0_MEASUREMENTS_SUM + (index * 10)), (uint8_t *)sum, sizeof(*sum)); //10 because sizeof measurement + sizeof measurements_sum = 10
    read(TESTER_SYS_IO_NUM_POWER_SAMPLES, (uint8_t *)samples, sizeof(*samples));
    read(TESTER_SYS_IO_NUM_POWER_CYCLES, (uint8_t *)cycles, sizeof(*cycles));
}

void MbedTester::set_snapshot()
{
    uint8_t data = 1;
    write(TESTER_SYS_IO_ADC_SNAPSHOT, &data, sizeof(data));
    wait_us(1);
}

void MbedTester::set_sample_adc(bool val)
{
    uint8_t data;
    if (val == true) {
        data = 1;
    } else if (val == false) {
        data = 0;
    }
    write(TESTER_SYS_IO_SAMPLE_ADC, &data, sizeof(data));
}

float MbedTester::get_analog_in()
{
    uint16_t data = get_analogmuxin_measurement();
    float data_f = (float)data / 4095.0f;
    return data_f;
}

float MbedTester::get_anin_voltage(int index)
{
    uint16_t data = get_anin_measurement(index);
    float data_f = (float)data / 4095.0f;
    return data_f;
}

int MbedTester::gpio_read(LogicalPin gpio)
{
    if (gpio >= LogicalPinCount) {
        error("Invalid pin for gpio_read");
        return 0;
    }
    uint8_t data = 0;
    read(TESTER_GPIO + gpio, &data, sizeof(data));
    return data;
}

void MbedTester::gpio_write(LogicalPin gpio, int value, bool drive)
{
    if (gpio >= LogicalPinCount) {
        error("Invalid pin for gpio_write");
        return;
    }
    uint8_t data = 0;
    data |= value ? (1 << 0) : 0;
    data |= drive ? (1 << 1) : 0;
    write(TESTER_GPIO + gpio, &data, sizeof(data));
}

void MbedTester::io_metrics_start()
{
    uint8_t data = TESTER_IO_METRICS_CTRL_RESET_BIT;
    write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));

    data = TESTER_IO_METRICS_CTRL_ACTIVE_BIT;
    write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
}

void MbedTester::io_metrics_stop()
{
    uint8_t data = 0;
    write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
}

void MbedTester::io_metrics_continue()
{
    uint8_t data = TESTER_IO_METRICS_CTRL_ACTIVE_BIT;
    write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
}

uint32_t MbedTester::io_metrics_min_pulse_low(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_MIN_PULSE_LOW(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

uint32_t MbedTester::io_metrics_min_pulse_high(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_MIN_PULSE_HIGH(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

uint32_t MbedTester::io_metrics_max_pulse_low(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_MAX_PULSE_LOW(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

uint32_t MbedTester::io_metrics_max_pulse_high(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_MAX_PULSE_HIGH(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

uint32_t MbedTester::io_metrics_rising_edges(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_RISING_EDGES(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

uint32_t MbedTester::io_metrics_falling_edges(LogicalPin pin)
{
    if (pin >= LogicalPinCount) {
        error("Invalid pin for io_metrics");
        return 0;
    }

    uint32_t data = 0;
    read(TESTER_IO_METRICS_FALLING_EDGES(pin), (uint8_t *)&data, sizeof(data));
    return data;
}

bool MbedTester::sys_pin_read(SystemPin pin)
{

    if (pin >= SystemPinCount) {
        error("Invalid pin for gpio_read");
        return 0;
    }
    uint8_t data = 0;
    read(TESTER_SYS_IO + pin, &data, sizeof(data));
    return data;
}

void MbedTester::sys_pin_write(SystemPin pin, int value, bool drive)
{
    if (pin >= SystemPinCount) {
        error("Invalid pin for gpio_write");
        return;
    }
    uint8_t data = 0;
    data |= value ? (1 << 0) : 0;
    data |= drive ? (1 << 1) : 0;
    write(TESTER_SYS_IO + pin, &data, sizeof(data));
}

void MbedTester::sys_pin_mode_disabled()
{
    const uint32_t base = LogicalPinTotal;

    pin_map_index(physical_nc, (LogicalPin)(base + 0));
    pin_map_index(physical_nc, (LogicalPin)(base + 1));
    pin_map_index(physical_nc, (LogicalPin)(base + 2));
    pin_map_index(physical_nc, (LogicalPin)(base + 3));

    uint8_t mode = TESTER_SYS_IO_MODE_DISABLED;
    write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
}

void MbedTester::sys_pin_mode_spi_serial_flash(PhysicalIndex mosi, PhysicalIndex miso, PhysicalIndex clk, PhysicalIndex ssel)
{
    const uint32_t base = LogicalPinTotal;

    pin_map_index(mosi, (LogicalPin)(base + 0));
    pin_map_index(miso, (LogicalPin)(base + 1));
    pin_map_index(clk, (LogicalPin)(base + 2));
    pin_map_index(ssel, (LogicalPin)(base + 3));

    uint8_t mode = TESTER_SYS_IO_MODE_SPI_SERIAL_FLASH;
    write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
}

void MbedTester::sys_pin_mode_i2c_io_expander(int index, PhysicalIndex sda_in, PhysicalIndex sda_val, PhysicalIndex scl_in, PhysicalIndex scl_val)
{
    const uint32_t base = LogicalPinTotal;

    pin_map_index(sda_in, (LogicalPin)(base + 0));
    pin_map_index(sda_val, (LogicalPin)(base + 1));
    pin_map_index(scl_in, (LogicalPin)(base + 2));
    pin_map_index(scl_val, (LogicalPin)(base + 3));

    uint8_t mode = 0;
    if (index == 0) {
        mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER0;
    } else if (index == 1) {
        mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER1;
    } else if (index == 2) {
        mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER2;
    } else {
        error("Invalid index for sys_pin_mode_i2c_io_expander");
    }

    write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
}

void MbedTester::pin_map_index(PhysicalIndex physical_index, LogicalPin logical)
{
    uint8_t remap;
    if ((physical_index >= physical_pins) && (physical_index != physical_nc)) {
        error("Invalid physical pin index %i", physical_index);
        return;
    }
    if (logical >= sizeof(_mapping) / sizeof(_mapping[0])) {
        error("Invalid logical pin %i", logical);
        return;
    }

    // Unmap the previous pin if it had been mapped
    if (_mapping[logical] < physical_pins) {
        remap = physical_nc;
        write(TESTER_REMAP + _mapping[logical], &remap, sizeof(remap));
    }
    _mapping[logical] = physical_index;

    // Remap physical pin if it is not physical_nc
    if (physical_index < physical_pins) {
        remap = logical;
        write(TESTER_REMAP + physical_index, &remap, sizeof(remap));
    }
    // Remap logical pin
    remap = physical_index;
    write(TESTER_REMAP + physical_pins + logical, &remap, sizeof(remap));
}

void MbedTester::write(uint32_t addr, const uint8_t *data, uint32_t size)
{
    _update_control_pins();

    mbed_tester_command(_clk, _mosi, _miso, _miso_index, addr, true, (uint8_t *)data, size);
}

void MbedTester::read(uint32_t addr, uint8_t *data, uint32_t size)
{
    _update_control_pins();

    mbed_tester_command(_clk, _mosi, _miso, _miso_index, addr, false, data, size);
}

bool MbedTester::self_test_all()
{
    return self_test_control_channels() && self_test_control_miso();
}

bool MbedTester::self_test_control_channels()
{
    for (uint32_t i = 0; i < _form_factor.count() / 2; i++) {
        const int clk_index = i * 2 + 0;
        const int mosi_index = i * 2 + 1;
        const PinName clk = _form_factor.get(clk_index);
        const PinName mosi = _form_factor.get(mosi_index);

        // Check if the control pair is allowed and skip if it is not
        if (_exclude_pins.has_pin(clk) || _exclude_pins.has_pin(mosi)) {
            mbed_tester_printf("Skipping pin indexes clk=%i, mosi=%i\r\n", i * 2 + 0, i * 2 + 1);
            continue;
        }

        // Find a pin to use as miso
        int miso_index = 0;
        PinName miso = NC;
        DynamicPinList more_restricted(_exclude_pins);
        more_restricted.add(clk);
        more_restricted.add(mosi);
        for (uint32_t j = 0; j < _form_factor.count(); j++) {
            miso_index = j;
            const PinName temp = _form_factor.get(miso_index);
            if (!more_restricted.has_pin(temp)) {
                miso = temp;
                break;
            }
        }
        if (miso == NC) {
            set_control_pins_auto();
            return false;
        }

        // Find a pin to use as aux
        int aux_index = 0;
        PinName aux = NC;
        more_restricted.add(miso);
        for (uint32_t j = 0; j < _form_factor.count(); j++) {
            aux_index = j;
            const PinName temp = _form_factor.get(aux_index);
            if (!more_restricted.has_pin(temp)) {
                aux = temp;
                break;
            }
        }
        if (aux == NC) {
            set_control_pins_auto();
            return false;
        }

        // Write and read back a value
        mbed_tester_printf("Testing clk_index=%2i, mosi_index=%2i, miso_index=%2i, aux_index=%2i\r\n", clk_index, mosi_index, miso_index, aux_index);
        set_control_pins_manual(clk, mosi, miso, aux);
        if (!self_test_control_current()) {
            mbed_tester_printf("  Fail\r\n");
            set_control_pins_auto();
            return false;
        }
        mbed_tester_printf("  Pass\r\n");
    }

    set_control_pins_auto();
    return true;
}

bool MbedTester::self_test_control_miso()
{
    for (uint32_t i = 0; i < _form_factor.count(); i++) {
        const int miso_index = i;
        const PinName miso = _form_factor.get(miso_index);

        if (_exclude_pins.has_pin(miso)) {
            mbed_tester_printf("Skipping miso index %i\r\n", i);
            continue;
        }

        // Find pins to use as clk and mosi
        int clk_index = 0;
        int mosi_index = 0;
        PinName clk = NC;
        PinName mosi = NC;
        DynamicPinList more_restricted(_exclude_pins);
        more_restricted.add(miso);
        for (uint32_t j = 0; j < _form_factor.count() / 2; j++) {
            clk_index = j * 2 + 0;
            mosi_index = j * 2 + 1;
            const PinName possible_clk = _form_factor.get(clk_index);
            const PinName possible_mosi = _form_factor.get(mosi_index);

            // Check if the control pair is allowed and skip if it is not
            if (!more_restricted.has_pin(possible_clk) && !more_restricted.has_pin(possible_mosi)) {
                clk = possible_clk;
                mosi = possible_mosi;
                break;
            }
        }

        if ((clk == NC) && (mosi == NC)) {
            set_control_pins_auto();
            return false;
        }

        // Find aux pin
        int aux_index = 0;
        PinName aux = NC;
        more_restricted.add(clk);
        more_restricted.add(mosi);
        for (uint32_t j = 0; j < _form_factor.count(); j++) {
            aux_index = j;
            const PinName possible_aux = _form_factor.get(aux_index);

            // Check if the control pair is allowed and skip if it is not
            if (!more_restricted.has_pin(possible_aux)) {
                aux = possible_aux;
                break;
            }
        }
        if (aux == NC) {
            set_control_pins_auto();
            return false;
        }


        mbed_tester_printf("Testing clk_index=%2i, mosi_index=%2i, miso_index=%2i, aux_index=%2i\r\n", clk_index, mosi_index, miso_index, aux_index);
        set_control_pins_manual(clk, mosi, miso, aux);
        if (!self_test_control_current()) {
            mbed_tester_printf("  Fail\r\n");
            set_control_pins_auto();
            return false;
        }
        mbed_tester_printf("  Pass\r\n");
    }
    set_control_pins_auto();
    return true;
}

bool MbedTester::self_test_control_current()
{
    uint8_t buf[4];

    read(TESTER_CONTROL, buf, sizeof(buf));

    return memcmp(buf, "mbed", sizeof(buf)) == 0;
}

bool MbedTester::_find_control_indexes(PhysicalIndex &clk_out, PhysicalIndex &mosi_out, PhysicalIndex &miso_out, PhysicalIndex &aux_out)
{
    MbedTesterBitMap<physical_pins> allowed;
    const size_t max_pins = _form_factor.count();
    const size_t max_controls = max_pins / 2;

    for (size_t i = 0; i < max_pins; i++) {
        PinName pin = _form_factor.get(i);
        if ((pin == NC) || _exclude_pins.has_pin(pin)) {
            // Skip these pins
            continue;
        }

        // Pin is allowed
        allowed.set(i);
    }
    for (size_t i = 0; i < LogicalPinTotal; i++) {
        PhysicalIndex index = _mapping[i];
        if (index < physical_pins) {
            allowed.clear(index);
        }
    }

    for (size_t i = 0; i < max_controls; i++) {
        uint8_t clk_index = i * 2 + 0;
        uint8_t mosi_index = i * 2 + 1;
        if (!allowed.get(clk_index) || !allowed.get(mosi_index)) {
            continue;
        }

        // Free CLK and MOSI pins found. Mark them as unavailable
        allowed.clear(clk_index);
        allowed.clear(mosi_index);
        mbed::DigitalInOut clk(_form_factor.get(clk_index), PIN_OUTPUT, ::PullNone, 0);
        mbed::DigitalInOut mosi(_form_factor.get(mosi_index), PIN_OUTPUT, ::PullNone, 0);

        for (uint8_t miso_index = 0; miso_index < max_pins; miso_index++) {
            if (!allowed.get(miso_index)) {
                continue;
            }

            mbed::DigitalInOut miso(_form_factor.get(miso_index));
            if (!mbed_tester_test(&clk, &mosi, &miso, miso_index)) {
                // Pin doesn't work
                continue;
            }

            // MISO found so find AUX starting from where miso left off
            for (uint8_t aux_index = miso_index + 1; aux_index < max_pins; aux_index++) {
                if (!allowed.get(aux_index)) {
                    continue;
                }

                mbed::DigitalInOut aux(_form_factor.get(aux_index));
                if (!mbed_tester_test(&clk, &mosi, &aux, aux_index)) {
                    // Pin doesn't work
                    continue;
                }

                // Found all 4 pins
                clk_out = clk_index;
                mosi_out = mosi_index;
                miso_out = miso_index;
                aux_out = aux_index;
                clk.input();
                mosi.input();
                return true;
            }

            // A valid control channel was found but the system
            // does not have enough working pins.
            clk.input();
            mosi.input();
            return false;
        }

        // Set CLK and MOSI pins don't work so set them back to available
        clk.input();
        mosi.input();
        allowed.set(clk_index);
        allowed.set(mosi_index);
    }
    return false;
}

void MbedTester::_setup_control_pins()
{
    _clk = new  mbed::DigitalInOut(_form_factor.get(_clk_index), PIN_OUTPUT, ::PullNone, 0);
    _mosi = new mbed::DigitalInOut(_form_factor.get(_mosi_index), PIN_OUTPUT, ::PullNone, 0);
    _miso = new  mbed::DigitalInOut(_form_factor.get(_miso_index));
    _aux = new  mbed::DigitalInOut(_form_factor.get(_aux_index));
}

void MbedTester::_free_control_pins()
{
    if (_clk) {
        _clk->input();
        delete _clk;
    }
    _clk = NULL;
    _clk_index = physical_nc;
    if (_mosi) {
        _mosi->input();
        delete _mosi;
    }
    _mosi = NULL;
    _mosi_index = physical_nc;
    if (_miso) {
        _miso->input();
        delete _miso;
    }
    _miso = NULL;
    _miso_index = physical_nc;
    if (_aux) {
        _aux->input();
        delete _aux;
    }
    _aux = NULL;
    _aux_index = physical_nc;
    _control_valid = false;
}

void MbedTester::_update_control_pins()
{
    if (_update_needed()) {

        if (!_control_auto) {
            error("Invalid control channel configuration");
        }

        _free_control_pins();
        if (_find_control_indexes(_clk_index, _mosi_index, _miso_index, _aux_index)) {
            mbed_tester_printf("Updating control pins to clk=%i, mosi=%i, miso=%i, aux=%i\r\n", _clk_index, _mosi_index, _miso_index, _aux_index);
            _setup_control_pins();
            _control_valid = true;
            return;
        } else {
            error("An MbedTester communication channel could not be created");
        }
    }
}

bool MbedTester::_update_needed()
{
    if (!_control_valid) {
        return true;
    }
    // Note - mappings beyond the the first two logical banks are allowed to overlap
    //        with the control channels. These mappings are used for firmware updates and
    //        IO expander control.
    for (size_t i = 0; i < LogicalPinTotal; i++) {
        PhysicalIndex pin = _mapping[i];
        if ((pin == _clk_index) || (pin == _mosi_index) || (pin == _miso_index) || (pin == _aux_index)) {
            return true;
        }
    }
    return false;
}

void MbedTester::_reset()
{
    for (uint32_t i = 0; i < sizeof(_mapping) / sizeof(_mapping[0]); i++) {
        _mapping[i] = physical_nc;
    }
    _free_control_pins();
    _control_auto = true;
}