/* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** BusVoodoo I²C mode (code) * @file busvoodoo_i2c.c * @author King Kévin * @date 2018 * @note peripherals used: I2C @ref busvoodoo_i2c */ /* standard libraries */ #include // standard integer types #include // standard utilities #include // string utilities /* STM32 (including CM3) libraries */ #include // I2C library /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "menu.h" // menu definitions #include "i2c_master.h" // I2C utilities #include "busvoodoo_global.h" // BusVoodoo definitions #include "busvoodoo_oled.h" // OLED utilities #include "busvoodoo_i2c.h" // own definitions /** @defgroup busvoodoo_i2c I2C peripheral to communicate with I2C devices * @{ */ #define BUSVOODOO_I2C I2C2 /**< I2C peripheral */ /** @} */ /** mode setup stage */ static enum busvoodoo_i2c_setting_t { BUSVOODOO_I2C_SETTING_NONE, BUSVOODOO_I2C_SETTING_SPEED, BUSVOODOO_I2C_SETTING_ADDRESSBITS, BUSVOODOO_I2C_SETTING_PULLUP, BUSVOODOO_I2C_SETTING_DONE, } busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_NONE; /**< current mode setup stage */ /** I2C speed (in kHz) */ uint16_t busvoodoo_i2c_speed = 100; /** I2C address bits (7 or 10) */ uint8_t busvoodoo_i2c_addressbits = 7; /** if embedded or external pull-up resistors are use */ bool busvoodoo_i2c_embedded_pullup = true; /** setup I2C mode * @param[out] prefix terminal prompt prefix * @param[in] line terminal prompt line to configure mode * @return if setup is complete */ static bool busvoodoo_i2c_setup(char** prefix, const char* line) { bool complete = false; // is the setup complete if (NULL==line) { // first call busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_NONE; // re-start configuration } switch (busvoodoo_i2c_setting) { case BUSVOODOO_I2C_SETTING_NONE: snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "speed in kHz (1-400) [%u]", busvoodoo_i2c_speed); *prefix = busvoodoo_global_string; // ask for speed busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_SPEED; break; case BUSVOODOO_I2C_SETTING_SPEED: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_ADDRESSBITS; // go to next setting } else { // setting provided uint32_t speed = atoi(line); // parse setting if (speed > 0 && speed <= 400) { // check setting busvoodoo_i2c_speed = speed; // remember setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_ADDRESSBITS; // go to next setting } } if (BUSVOODOO_I2C_SETTING_ADDRESSBITS == busvoodoo_i2c_setting) { // if next setting snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "address size in bits (7,10) [%u]", busvoodoo_i2c_addressbits); // prepare next setting *prefix = busvoodoo_global_string; // display next setting } break; case BUSVOODOO_I2C_SETTING_ADDRESSBITS: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_PULLUP; // go to next setting } else { // setting provided uint8_t addressbits = atoi(line); // parse setting if (7 == addressbits || 10 == addressbits) { // check setting busvoodoo_i2c_addressbits = addressbits; // remember setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_PULLUP; // go to next setting } } if (BUSVOODOO_I2C_SETTING_PULLUP == busvoodoo_i2c_setting) { // if next setting printf("1) open-drain, with embedded pull-up resistors (2kO)\n"); printf("2) open-drain, with external pull-up resistors\n"); snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "drive mode (1,2) [%c]", busvoodoo_i2c_embedded_pullup ? '1' : '2'); // show drive mode *prefix = busvoodoo_global_string; // display next setting } break; case BUSVOODOO_I2C_SETTING_PULLUP: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_DONE; // go to next setting } else if (1 == strlen(line)) { // setting provided uint8_t drive = atoi(line); // parse setting if (1 == drive || 2 == drive) { // check setting busvoodoo_i2c_embedded_pullup = (1 == drive); // remember setting busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_DONE; // go to next setting } } if (BUSVOODOO_I2C_SETTING_DONE == busvoodoo_i2c_setting) { // we have all settings, configure I2C i2c_master_setup(BUSVOODOO_I2C, busvoodoo_i2c_speed); // setup I2C if (busvoodoo_i2c_embedded_pullup) { busvoodoo_embedded_pullup(true); // set pull-up busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_WARNING); printf("use LV to set pull-up voltage\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); } busvoodoo_led_blue_off(); // disable blue LED because there is no activity busvoodoo_i2c_setting = BUSVOODOO_I2C_SETTING_NONE; // restart settings next time *prefix = "I2C"; // display mode busvoodoo_oled_text_left(*prefix); // set mode title on OLED display const char* pinout_io[10] = {"GND", "5V", "3V3", "LV", "SDA", "SCL", NULL, NULL}; // HiZ mode pinout for (uint8_t i=0; i= '0' && action[1] <= '9') { // send decimal return busvoodoo_i2c_action(action + 1, repetition, perform); // just retry without leading 0 } else if ('r' == action[1] && 2 == length) { // select slave to read busvoodoo_i2c_select(0, false); // select slave 0 to read } else if ('w' == action[1] && 2 == length) { // select slave to write busvoodoo_i2c_select(0, true); // select slave 0 to write } else { // malformed action return false; } } else if ('x' == action[0] && length > 1) { // send hexadecimal value for (uint32_t i = 1; i < length; i++) { // check string if (!((action[i] >= '0' && action[i] <= '9') || (action[i] >= 'a' && action[i] <= 'f') || (action[i] >= 'A' && action[i] <= 'F') || ('r' == action[i] && i == length - 1) || ('w' == action[i] && i == length - 1))) { // check for hexadecimal character or r/w flag return false; // not an hexadecimal string } } if (!perform) { return true; } uint32_t value = strtol(&action[1], NULL, 16); // get hex value for (uint32_t i = 0; i < repetition; i++) { if ('r' == action[length - 1]) { busvoodoo_i2c_select(value, false); // select slave to read } else if ('w' == action[length - 1]) { busvoodoo_i2c_select(value, true); // select slave to write } else { busvoodoo_i2c_write(value); // write to I2C } } } else if ('b' == action[0] && length > 1) { // send binary value for (uint32_t i = 1; i < length; i++) { // check string if (!('0' == action[i] || '1' == action[i] || ('r' == action[i] && i == length - 1) || ('w' == action[i] && i == length - 1))) { // check for binary character or r/w flag return false; // not a binary string } } if (!perform) { return true; } uint32_t value = strtol(&action[1], NULL, 2); // get binary value for (uint32_t i = 0; i < repetition; i++) { if ('r' == action[length - 1]) { busvoodoo_i2c_select(value, false); // select slave to read } else if ('w' == action[length - 1]) { busvoodoo_i2c_select(value, true); // select slave to write } else { busvoodoo_i2c_write(value); // write to I2C } } } else if (action[0] >= '1' && action[0] <= '9') { // send decimal value for (uint32_t i = 1; i < length; i++) { // check string if (!((action[i] >= '0' && action[i] <= '9') || ('w' == action[i] && i == length - 1))) { // check for decimal character or r/w flag return false; // not a decimal string } } if (!perform) { return true; } uint32_t value = strtol(&action[0], NULL, 10); // get decimal value for (uint32_t i = 0; i < repetition; i++) { if ('r' == action[length - 1]) { busvoodoo_i2c_select(value, false); // select slave to read } else if ('w' == action[length - 1]) { busvoodoo_i2c_select(value, true); // select slave to write } else { busvoodoo_i2c_write(value); // write to I2C } } } else if (length >= 2 && ('"' == action[0] || '\'' == action[0]) && (action[length - 1] == action[0])) { // send ASCII character if (!perform) { return true; } for (uint32_t r = 0; r < repetition; r++) { for (uint32_t i = 1; i < length - 1; i++) { // go through string busvoodoo_i2c_write(action[i]); // write to I2C } } } else { // malformed action return false; } return true; // all went well } // command handlers /** command to reset I2C peripheral * @param[in] argument no argument required */ static void busvoodoo_i2c_command_reset(void* argument) { (void)argument; // we won't use the argument i2c_master_reset(BUSVOODOO_I2C); // reset the I2C peripheral since it might be stuck i2c_master_setup(BUSVOODOO_I2C, busvoodoo_i2c_speed); // re-setup I2C printf("I2C peripheral reset\n"); } /** command to perform actions * @param[in] argument actions to perform */ static void busvoodoo_i2c_command_actions(void* argument) { if (NULL == argument || 0 == strlen(argument)) { printf("available actions (separated by space or ,):\n"); printf("[/]\tsend start/stop conditions\n"); printf("0x00r\tselect slave with I2C address to read\n"); printf("0x00w\tselect slave with I2C address to write\n"); printf("0\twrite decimal value\n"); printf("0x00\twrite hexadecimal value\n"); printf("0b00000000\twrite binary value\n"); printf("\"a\"/'a'\twrite ASCII characters\n"); printf("r\tread value (last repetition NAKed)\n"); printf("u/m\twait 1 us/ms\n"); printf(":n\trepeat action n times\n"); return; } // copy argument since it will be modified char* copy = calloc(strlen(argument) + 1, sizeof(char)); if (!copy) { while (true); } strncpy(copy, argument, strlen(argument) + 1); // verify and perform actions if (!busvoodoo_global_actions(copy, false, &busvoodoo_i2c_action)) { // verify actions printf("malformed action(s)\n"); } else { // action are ok busvoodoo_global_actions(argument, true, &busvoodoo_i2c_action); // perform action } free(copy); // release memory } /** command to scan for slave devices * @param[in] argument no argument required */ static void busvoodoo_i2c_command_scan(void* argument) { (void)argument; // we won't use the argument if (!i2c_master_check_signals(BUSVOODOO_I2C)) { // ensure SCL and SDA are high printf("SCL or/and SDA are low. The signals need to be pulled up\n"); if (busvoodoo_i2c_embedded_pullup) { printf("set pull-up voltage with LV\n"); } busvoodoo_leds_blink(0.5, 0.5); // show error on LEDs return; // stop here } else { busvoodoo_led_blue_off(); // clear blinking } printf("scanning for I2C slaves\n"); uint16_t i2c_slaves = 0; // number of slaves found bool bus_error = false; // to remember bus error bool periph_error = false; // to remember I2C peripheral error (which need peripheral reset) // check for I2C slaves by going through the address space printf("I2C slaves found: "); for (uint16_t address = 0; address < (1 << busvoodoo_i2c_addressbits); address++) { busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // pulse blue LED to show transmission switch (i2c_master_select_slave(BUSVOODOO_I2C, address, 10 == busvoodoo_i2c_addressbits, false)) { // try to select slave (includes start condition) case I2C_MASTER_RC_NONE: // slave found busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // pulse blue LED to show we found a slave printf((busvoodoo_i2c_addressbits > 7) ? "%+03x " : "%+02x ", address); // display address i2c_slaves++; // increase slave count break; case I2C_MASTER_RC_START_STOP_IN_PROGESS: // I2C peripheral error printf("start condition failed\n"); // show error to user periph_error = true; // remember error break; // stop scanning case I2C_MASTER_RC_NOT_MASTER: // start condition failed i2c_master_reset(BUSVOODOO_I2C); // reset the I2C peripheral since it might be stuck printf("start condition failed\n"); // show error to user bus_error = true; // remember error break; // stop scanning case I2C_MASTER_RC_NAK: // slave did not respond break; // nothing to do case I2C_MASTER_RC_BUS_ERROR: printf("error detected on bus\n"); periph_error = true; // remember error break; default: // unexpected error printf("error occurred\n"); bus_error = true; // remember error break; } if (bus_error) { busvoodoo_leds_blink(0.5, 0.5); // show error on LEDs i2c_master_stop(BUSVOODOO_I2C); // send stop condition break; // stop scan } if (periph_error) { busvoodoo_leds_blink(0.5, 0.5); // show error on LEDs busvoodoo_i2c_command_reset(NULL); break; // stop scan } if (I2C_MASTER_RC_NONE != i2c_master_stop(BUSVOODOO_I2C)) { // send stop condition i2c_master_reset(BUSVOODOO_I2C); // reset the I2C peripheral since it might be stuck busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_WARNING); printf("stop condition failed\n"); // show error to user busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); busvoodoo_leds_blink(0.5, 0.5); // show error on LEDs break; } } printf("\n%u slave(s) found\n", i2c_slaves); // show summary } /** read all bytes from an 8-bit memory * @param[in] argument address of I2C slave memory device */ static void busvoodoo_i2c_command_dump8(void* argument) { uint16_t address = *(uint32_t*)argument; // use argument as device address printf("reading bytes from 8-bit memory device at I2C address "); // display explanation printf((busvoodoo_i2c_addressbits > 7) ? "%+03x\n" : "%+02x\n", address); // display address bool rc = (I2C_MASTER_RC_NONE == i2c_master_start(BUSVOODOO_I2C)); // send start condition printf("start condition: %s\n", rc ? "sent" : "error"); if (!rc) { return; } rc = busvoodoo_i2c_select(address, true); // select device to write address if (!rc) { goto error; } rc = busvoodoo_i2c_write(0); // write start address if (!rc) { goto error; } rc = (I2C_MASTER_RC_NONE == i2c_master_start(BUSVOODOO_I2C)); // send start condition printf("re-start condition: %s\n", rc ? "sent" : "error"); if (!rc) { goto error; } rc = busvoodoo_i2c_select(address, false); // select device to write address if (!rc) { goto error; } rc = busvoodoo_i2c_read(256); // read bytes if (!rc) { goto error; } rc = (I2C_MASTER_RC_NONE == i2c_master_stop(BUSVOODOO_I2C)); // send start condition printf("stop condition: %s\n", rc ? "sent" : "error"); return; error: i2c_master_stop(BUSVOODOO_I2C); // send transaction } /** I2C menu commands */ static const struct menu_command_t busvoodoo_i2c_commands[] = { { .shortcut = 'r', .name = "reset_i2c", .command_description = "reset I2C peripheral", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &busvoodoo_i2c_command_reset, }, { .shortcut = 'a', .name = "action", .command_description = "perform protocol actions", .argument = MENU_ARGUMENT_STRING, .argument_description = "[actions]", .command_handler = &busvoodoo_i2c_command_actions, }, { .shortcut = 's', .name = "scan", .command_description = "scan for slave devices", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &busvoodoo_i2c_command_scan, }, { .shortcut = 'd', .name = "dump8", .command_description = "dump 8-bit address space memory", .argument = MENU_ARGUMENT_HEX, .argument_description = "0xaddr", .command_handler = &busvoodoo_i2c_command_dump8, }, }; const struct busvoodoo_mode_t busvoodoo_i2c_mode = { .name = "i2c", .description = "Inter-Integrated Circuit", .full_only = false, .setup = &busvoodoo_i2c_setup, .commands = busvoodoo_i2c_commands, .commands_nb = LENGTH(busvoodoo_i2c_commands), .exit = &busvoodoo_i2c_exit, };