/* * 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; }