/* 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 printf("use LV to set pull-up voltage\n"); } led_off(); // disable 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='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; i1) { // send binary value for (uint32_t i=1; i='1' && action[0]<='9') { // send decimal value for (uint32_t i=1; 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=2 && ('"'==action[0] || '\''==action[0]) && (action[length-1]==action[0])) { // send ASCII character if (!perform) { return true; } for (uint32_t r=0; r7) ? "0x%03x " : "0x%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 i2c_slaves = -2; // 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 i2c_slaves = -1; // 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"); i2c_slaves = -2; // remember error break; default: // unexpected error printf("error occurred\n"); i2c_slaves = -1; // remember error break; } if (i2c_slaves<0) { // error happened led_blink(0.5, 0.5); // show error on LEDs if (-1==i2c_slaves) { // just end communication i2c_master_stop(BUSVOODOO_I2C); // send stop condition } else if (-2==i2c_slaves) { // reset peripheral busvoodoo_i2c_command_reset(NULL); } break; // stop scan } else { if (I2C_MASTER_RC_NONE!=i2c_master_stop(BUSVOODOO_I2C)) { // stop stop condition i2c_master_reset(BUSVOODOO_I2C); // reset the I2C peripheral since it might be stuck printf("stop condition failed\n"); // show error to user led_blink(0.5, 0.5); // show error on LEDs i2c_slaves = -1; // remember error break; } } } if (i2c_slaves>0) { printf("\n"); } if (i2c_slaves>=0) { printf("%u slave(s) found\n", i2c_slaves); // show summary } } /** 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, }, }; 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, };