From 6d1e23796fb10bf32eefcaf8f65e8ab89adbf2f4 Mon Sep 17 00:00:00 2001 From: morris Date: Mon, 15 Oct 2018 10:19:02 +0800 Subject: [PATCH] i2c/i2c-tools: add i2c-tools example 1. add command i2cconfig 2. add command i2cdetect 3. add command i2cget 4. add command i2cset 5. add command i2cdump 6. add README.md --- .../peripherals/i2c/i2c_tools/CMakeLists.txt | 8 + examples/peripherals/i2c/i2c_tools/Makefile | 11 + examples/peripherals/i2c/i2c_tools/README.md | 209 +++++++++ .../i2c/i2c_tools/main/CMakeLists.txt | 5 + .../i2c/i2c_tools/main/Kconfig.projbuild | 11 + .../peripherals/i2c/i2c_tools/main/cmd_decl.h | 21 + .../i2c/i2c_tools/main/cmd_i2ctools.c | 409 ++++++++++++++++++ .../i2c/i2c_tools/main/cmd_i2ctools.h | 20 + .../i2c/i2c_tools/main/component.mk | 3 + .../i2c_tools/main/i2ctools_example_main.c | 194 +++++++++ .../i2c/i2c_tools/partitions_example.csv | 6 + .../i2c/i2c_tools/sdkconfig.defaults | 17 + 12 files changed, 914 insertions(+) create mode 100644 examples/peripherals/i2c/i2c_tools/CMakeLists.txt create mode 100644 examples/peripherals/i2c/i2c_tools/Makefile create mode 100644 examples/peripherals/i2c/i2c_tools/README.md create mode 100644 examples/peripherals/i2c/i2c_tools/main/CMakeLists.txt create mode 100644 examples/peripherals/i2c/i2c_tools/main/Kconfig.projbuild create mode 100644 examples/peripherals/i2c/i2c_tools/main/cmd_decl.h create mode 100644 examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.c create mode 100644 examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.h create mode 100644 examples/peripherals/i2c/i2c_tools/main/component.mk create mode 100644 examples/peripherals/i2c/i2c_tools/main/i2ctools_example_main.c create mode 100644 examples/peripherals/i2c/i2c_tools/partitions_example.csv create mode 100644 examples/peripherals/i2c/i2c_tools/sdkconfig.defaults diff --git a/examples/peripherals/i2c/i2c_tools/CMakeLists.txt b/examples/peripherals/i2c/i2c_tools/CMakeLists.txt new file mode 100644 index 000000000..53f2a254f --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2c-tools) diff --git a/examples/peripherals/i2c/i2c_tools/Makefile b/examples/peripherals/i2c/i2c_tools/Makefile new file mode 100644 index 000000000..bf73cbd8c --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := i2c-tools + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/components + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/i2c/i2c_tools/README.md b/examples/peripherals/i2c/i2c_tools/README.md new file mode 100644 index 000000000..0f8a7b5ef --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/README.md @@ -0,0 +1,209 @@ +# I2C Tools Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +[I2C Tools](https://i2c.wiki.kernel.org/index.php/I2C_Tools) is a simple but very useful tool for developing I2C related applications, which is also famous in Linux platform. This example just implements some of basic features of [I2C Tools](https://i2c.wiki.kernel.org/index.php/I2C_Tools) based on [esp32 console component](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html). As follows, this example supports five command-line tools: + +1. `i2cconfig`: It will configure the I2C bus with specific GPIO number, port number and frequency. +2. `i2cdetect`: It will scan an I2C bus for devices and output a table with the list of detected devices on the bus. +3. `i2cget`: It will read registers visible through the I2C bus. +4. `i2cset`: It will set registers visible through the I2C bus. +5. `i2cdump`: It will examine registers visible through the I2C bus. + +If you have some trouble in developing I2C related applications, or just want to test some functions of one I2C device, you can play with this example first. + +## How to use example + +### Hardware Required + +To run this example, you should have one ESP32 dev board (e.g. ESP32-WROVER Kit) or ESP32 core board (e.g. ESP32-DevKitC). For test purpose, you should have a kind of device with I2C interface as well. Here we will take the CCS811 sensor as an example to show how to test the function of this sensor without writing any code (just use the command-line tools supported by this example). For more information about CCS811, you can consult the [online datasheet](http://ams.com/ccs811). + +#### Pin Assignment: + +**Note:** The following pin assignments are used by default, you can change them with `i2cconfig` command at any time. + +| | SDA | SCL | +| ---------------- | ------ | ------ | +| ESP32 I2C Master | GPIO18 | GPIO19 | +| Sensor | SDA | SCL | + +- master: + - GPIO18 is assigned as the data signal of I2C master port + - GPIO19 is assigned as the clock signal of I2C master port + +- connection: + - connect SDA/SCL of CCS811 sensor with GPIO18/GPIO19 + +**Note: ** There’s no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors itself. + +### Configure the project + +Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you are using CMake based build system. Then go into `Example Configuration` menu. + +- You can choose whether or not to save command history into flash in `Store command history in flash` option. + +### Build and Flash + +Enter `make -j4 flash monitor` if you are using GNU Make based build system or enter `idf.py build flash monitor` if you are using CMake based build system. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +### Check all supported commands and their usages + +```bash + ============================================================== + | Steps to Use i2c-tools on ESP32 | + | | + | 1. Try 'help', check all supported commands | + | 2. Try 'i2cconfig' to configure your I2C bus | + | 3. Try 'i2cdetect' to scan devices on the bus | + | 4. Try 'i2cget' to get the content of specific register | + | 5. Try 'i2cset' to set the value of specific register | + | 6. Try 'i2cdump' to dump all the register (Experiment) | + | | + ============================================================== + +esp32> help +help + Print the list of registered commands + +i2cconfig [--port=<0|1>] [--freq=] --sda= --scl= + Config I2C bus + --port=<0|1> Set the I2C bus port number + --freq= Set the frequency(Hz) of I2C bus + --sda= Set the gpio for I2C SDA + --scl= Set the gpio for I2C SCL + +i2cdetect + Scan I2C bus for devices + +i2cget -c [-r ] [-l ] + Read registers visible through the I2C bus + -c, --chip= Specify the address of the chip on that bus + -r, --register= Specify the address on that chip to read from + -l, --length= Specify the length to read from that data address + +i2cset -c [-r ] []... + Set registers visible through the I2C bus + -c, --chip= Specify the address of the chip on that bus + -r, --register= Specify the address on that chip to read from + Specify the data to write to that data address + +i2cdump -c [-s ] + Examine registers visible through the I2C bus + -c, --chip= Specify the address of the chip on that bus + -s, --size= Specify the size of each read + +free + Get the current size of free heap memory + +heap + Get minimum size of free heap memory that was available during program execu + tion + +version + Get version of chip and SDK + +restart + Software reset of the chip + +deep_sleep [-t ] [--io=] [--io_level=<0|1>] + Enter deep sleep mode. Two wakeup modes are supported: timer and GPIO. If no + wakeup option is specified, will sleep indefinitely. + -t, --time= Wake up time, ms + --io= If specified, wakeup using GPIO with given number + --io_level=<0|1> GPIO level to trigger wakeup + +light_sleep [-t ] [--io=]... [--io_level=<0|1>]... + Enter light sleep mode. Two wakeup modes are supported: timer and GPIO. Mult + iple GPIO pins can be specified using pairs of 'io' and 'io_level' arguments + . Will also wake up on UART input. + -t, --time= Wake up time, ms + --io= If specified, wakeup using GPIO with given number + --io_level=<0|1> GPIO level to trigger wakeup + +tasks + Get information about running tasks +``` + +### Configure the I2C bus + +```bash +esp32> i2cconfig --port=0 --sda=18 --scl=19 --freq=100000 +``` + +* `--port` option to specify the port of I2C, here we choose port 0 for test. +* `--sda` and `--scl` options to specify the gpio number used by I2C bus, here we choose GPIO18 as the SDA and GPIO19 as the SCL. +* `--freq` option to specify the frequency of I2C bus, here we set to 100KHz. + +### Check the I2C address (7 bits) on the I2C bus + +```bash +esp32> i2cdetect + 0 1 2 3 4 5 6 7 8 9 a b c d e f +00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +50: -- -- -- -- -- -- -- -- -- -- -- 5b -- -- -- -- +60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +``` + +* Here we found the address of CCS811 is 0x5b. + +### Get the value of status register + +```bash +esp32> i2cget -c 0x5b -r 0x00 -l 1 +0x10 +``` + +* `-c` option to specify the address of I2C device (acquired from `i2cetect` command). +* `-r` option to specify the register address you want to inspect. +* `-l` option to specify the length of the content. +* Here the returned value 0x10 means that the sensor is just in the boot mode and is ready to go into application mode. For more information about CCS811 you should consult the [official website](http://ams.com/ccs811). + +### Change the working mode + +```bash +esp32> i2cset -c 0x5b -r 0xF4 +I (734717) cmd_i2ctools: Write OK +esp32> i2cset -c 0x5b -r 0x01 0x10 +I (1072047) cmd_i2ctools: Write OK +esp32> i2cget -c 0x5b -r 0x00 -l 1 +0x98 +``` + +* Here we change the mode from boot to application and set a proper measure mode (by writing 0x10 to register 0x01) +* Now the status value of the sensor is 0x98, which means a valid data is ready to read + +### Read the sensor data + +```bash +esp32> i2cget -c 0x5b -r 0x02 -l 8 +0x01 0xb0 0x00 0x04 0x98 0x00 0x19 0x8f +``` + +* The register 0x02 will output 8 bytes result, mainly including value of eCO~2~、TVOC and there raw value. So the value of eCO~2~ is 0x01b0 ppm and value of TVOC is 0x04 ppb. + +## Troubleshooting + +* I don’t find any available address when running `i2cdetect` command. + * Make sure your wiring connection is right. + * Some sensor will have a “wake up” pin, via which user can put the sensor into a sleep mode. So make sure your sensor in **not** in the sleep state. + * Have a try resetting you I2C device, and then re-run `i2cdetect`. +* I can’t get the right content when running `i2cdump` command. + * Currently the `i2cdump` only support those who have the same content length of registers inside the I2C device. For example, if a device have three legal addresses, and the content length at these address are 1 byte, 2 bytes and 4 bytes. Then you should not expect this command to dump the register correctly. + + + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) + diff --git a/examples/peripherals/i2c/i2c_tools/main/CMakeLists.txt b/examples/peripherals/i2c/i2c_tools/main/CMakeLists.txt new file mode 100644 index 000000000..50cdc0982 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCS "i2ctools_example_main.c" + "cmd_i2ctools.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/peripherals/i2c/i2c_tools/main/Kconfig.projbuild b/examples/peripherals/i2c/i2c_tools/main/Kconfig.projbuild new file mode 100644 index 000000000..1b1b9632d --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/Kconfig.projbuild @@ -0,0 +1,11 @@ +menu "Example Configuration" + +config STORE_HISTORY + bool "Store command history in flash" + default y + help + Linenoise line editing library provides functions to save and load + command history. If this option is enabled, initalizes a FAT filesystem + and uses it to store command history. + +endmenu diff --git a/examples/peripherals/i2c/i2c_tools/main/cmd_decl.h b/examples/peripherals/i2c/i2c_tools/main/cmd_decl.h new file mode 100644 index 000000000..5d28c6b26 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/cmd_decl.h @@ -0,0 +1,21 @@ +/* cmd_decl.h + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "cmd_system.h" +#include "cmd_i2ctools.h" + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.c b/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.c new file mode 100644 index 000000000..eae93a9d1 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.c @@ -0,0 +1,409 @@ +/* cmd_i2ctools.c + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "argtable3/argtable3.h" +#include "driver/i2c.h" +#include "esp_console.h" +#include "esp_log.h" + +#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ +#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ + +static const char *TAG = "cmd_i2ctools"; + +static gpio_num_t i2c_gpio_sda = 18; +static gpio_num_t i2c_gpio_scl = 19; +static uint32_t i2c_frequency = 100000; +static i2c_port_t i2c_port = I2C_NUM_0; + +static esp_err_t i2c_get_port(int port, i2c_port_t *i2c_port) +{ + if (port >= I2C_NUM_MAX) { + ESP_LOGE(TAG, "Wrong port number: %d", port); + return ESP_FAIL; + } + switch (port) { + case 0: + *i2c_port = I2C_NUM_0; + break; + case 1: + *i2c_port = I2C_NUM_1; + break; + default: + *i2c_port = I2C_NUM_0; + break; + } + return ESP_OK; +} + +static esp_err_t i2c_master_driver_initialize() +{ + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = i2c_gpio_sda, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = i2c_gpio_scl, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = i2c_frequency + }; + return i2c_param_config(i2c_port, &conf); +} + +static struct { + struct arg_int *port; + struct arg_int *freq; + struct arg_int *sda; + struct arg_int *scl; + struct arg_end *end; +} i2cconfig_args; + +static int do_i2cconfig_cmd(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&i2cconfig_args); + if (nerrors != 0) { + arg_print_errors(stderr, i2cconfig_args.end, argv[0]); + return 0; + } + + /* Check "--port" option */ + if (i2cconfig_args.port->count) { + if (i2c_get_port(i2cconfig_args.port->ival[0], &i2c_port) != ESP_OK) { + return 1; + } + } + /* Check "--freq" option */ + if (i2cconfig_args.freq->count) { + i2c_frequency = i2cconfig_args.freq->ival[0]; + } + /* Check "--sda" option */ + i2c_gpio_sda = i2cconfig_args.sda->ival[0]; + /* Check "--scl" option */ + i2c_gpio_scl = i2cconfig_args.scl->ival[0]; + return 0; +} + +static void register_i2cconfig(void) +{ + i2cconfig_args.port = arg_int0(NULL, "port", "<0|1>", "Set the I2C bus port number"); + i2cconfig_args.freq = arg_int0(NULL, "freq", "", "Set the frequency(Hz) of I2C bus"); + i2cconfig_args.sda = arg_int1(NULL, "sda", "", "Set the gpio for I2C SDA"); + i2cconfig_args.scl = arg_int1(NULL, "scl", "", "Set the gpio for I2C SCL"); + i2cconfig_args.end = arg_end(2); + const esp_console_cmd_t i2cconfig_cmd = { + .command = "i2cconfig", + .help = "Config I2C bus", + .hint = NULL, + .func = &do_i2cconfig_cmd, + .argtable = &i2cconfig_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&i2cconfig_cmd)); +} + +static int do_i2cdetect_cmd(int argc, char **argv) +{ + i2c_master_driver_initialize(); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (address << 1) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 50 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + + i2c_driver_delete(i2c_port); + return 0; +} + +static void register_i2cdectect(void) +{ + const esp_console_cmd_t i2cdetect_cmd = { + .command = "i2cdetect", + .help = "Scan I2C bus for devices", + .hint = NULL, + .func = &do_i2cdetect_cmd, + .argtable = NULL + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&i2cdetect_cmd)); +} + +static struct { + struct arg_int *chip_address; + struct arg_int *register_address; + struct arg_int *data_length; + struct arg_end *end; +} i2cget_args; + +static int do_i2cget_cmd(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&i2cget_args); + if (nerrors != 0) { + arg_print_errors(stderr, i2cget_args.end, argv[0]); + return 0; + } + + /* Check chip address: "-c" option */ + int chip_addr = i2cget_args.chip_address->ival[0]; + /* Check register address: "-r" option */ + int data_addr = -1; + if (i2cget_args.register_address->count) { + data_addr = i2cget_args.register_address->ival[0]; + } + /* Check data length: "-l" option */ + int len = 1; + if (i2cget_args.data_length->count) { + len = i2cget_args.data_length->ival[0]; + } + uint8_t *data = malloc(len); + + i2c_master_driver_initialize(); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + if (data_addr != -1) { + i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN); + i2c_master_start(cmd); + } + i2c_master_write_byte(cmd, chip_addr << 1 | READ_BIT, ACK_CHECK_EN); + if (len > 1) { + i2c_master_read(cmd, data, len - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data + len - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) { + for (int i = 0; i < len; i++) { + printf("0x%02x ", data[i]); + if ((i + 1) % 16 == 0) { + printf("\r\n"); + } + } + if (len % 16) { + printf("\r\n"); + } + } else if (ret == ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "Bus is busy"); + } else { + ESP_LOGW(TAG, "Read failed"); + } + free(data); + i2c_driver_delete(i2c_port); + return 0; +} + +static void register_i2cget(void) +{ + i2cget_args.chip_address = arg_int1("c", "chip", "", "Specify the address of the chip on that bus"); + i2cget_args.register_address = arg_int0("r", "register", "", "Specify the address on that chip to read from"); + i2cget_args.data_length = arg_int0("l", "length", "", "Specify the length to read from that data address"); + i2cget_args.end = arg_end(1); + const esp_console_cmd_t i2cget_cmd = { + .command = "i2cget", + .help = "Read registers visible through the I2C bus", + .hint = NULL, + .func = &do_i2cget_cmd, + .argtable = &i2cget_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&i2cget_cmd)); +} + +static struct { + struct arg_int *chip_address; + struct arg_int *register_address; + struct arg_int *data; + struct arg_end *end; +} i2cset_args; + +static int do_i2cset_cmd(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&i2cset_args); + if (nerrors != 0) { + arg_print_errors(stderr, i2cset_args.end, argv[0]); + return 0; + } + + /* Check chip address: "-c" option */ + int chip_addr = i2cset_args.chip_address->ival[0]; + /* Check register address: "-r" option */ + int data_addr = -1; + if (i2cset_args.register_address->count) { + data_addr = i2cset_args.register_address->ival[0]; + } + /* Check data: "-d" option */ + int len = i2cset_args.data->count; + + i2c_master_driver_initialize(); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN); + if (data_addr != -1) { + i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN); + } + for (int i = 0; i < len; i++) { + i2c_master_write_byte(cmd, i2cset_args.data->ival[i], ACK_CHECK_EN); + } + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Write OK"); + } else if (ret == ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "Bus is busy"); + } else { + ESP_LOGW(TAG, "Write Failed"); + } + i2c_driver_delete(i2c_port); + return 0; +} + +static void register_i2cset(void) +{ + i2cset_args.chip_address = arg_int1("c", "chip", "", "Specify the address of the chip on that bus"); + i2cset_args.register_address = arg_int0("r", "register", "", "Specify the address on that chip to read from"); + i2cset_args.data = arg_intn(NULL, NULL, "", 0, 256, "Specify the data to write to that data address"); + i2cset_args.end = arg_end(2); + const esp_console_cmd_t i2cset_cmd = { + .command = "i2cset", + .help = "Set registers visible through the I2C bus", + .hint = NULL, + .func = &do_i2cset_cmd, + .argtable = &i2cset_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&i2cset_cmd)); +} + +static struct { + struct arg_int *chip_address; + struct arg_int *size; + struct arg_end *end; +} i2cdump_args; + +static int do_i2cdump_cmd(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&i2cdump_args); + if (nerrors != 0) { + arg_print_errors(stderr, i2cdump_args.end, argv[0]); + return 0; + } + + /* Check chip address: "-c" option */ + int chip_addr = i2cdump_args.chip_address->ival[0]; + /* Check read size: "-s" option */ + int size = 1; + if (i2cdump_args.size->count) { + size = i2cdump_args.size->ival[0]; + } + if (size != 1 && size != 2 && size != 4) { + ESP_LOGE(TAG, "Wrong read size. Only support 1,2,4"); + return 1; + } + i2c_master_driver_initialize(); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + uint8_t data_addr; + uint8_t data[4]; + int32_t block[16]; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f" + " 0123456789abcdef\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j += size) { + fflush(stdout); + data_addr = i + j; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, chip_addr << 1 | READ_BIT, ACK_CHECK_EN); + if (size > 1) { + i2c_master_read(cmd, data, size - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data + size - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 50 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) { + for (int k = 0; k < size; k++) { + printf("%02x ", data[k]); + block[j + k] = data[k]; + } + } else { + for (int k = 0; k < size; k++) { + printf("XX "); + block[j + k] = -1; + } + } + } + printf(" "); + for (int k = 0; k < 16; k++) { + if (block[k] < 0) { + printf("X"); + } + if ((block[k] & 0xff) == 0x00 || (block[k] & 0xff) == 0xff) { + printf("."); + } else if ((block[k] & 0xff) < 32 || (block[k] & 0xff) >= 127) { + printf("?"); + } else { + printf("%c", block[k] & 0xff); + } + } + printf("\r\n"); + } + i2c_driver_delete(i2c_port); + return 0; +} + +static void register_i2cdump(void) +{ + i2cdump_args.chip_address = arg_int1("c", "chip", "", "Specify the address of the chip on that bus"); + i2cdump_args.size = arg_int0("s", "size", "", "Specify the size of each read"); + i2cdump_args.end = arg_end(1); + const esp_console_cmd_t i2cdump_cmd = { + .command = "i2cdump", + .help = "Examine registers visible through the I2C bus", + .hint = NULL, + .func = &do_i2cdump_cmd, + .argtable = &i2cdump_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&i2cdump_cmd)); +} + +void register_i2ctools(void) +{ + register_i2cconfig(); + register_i2cdectect(); + register_i2cget(); + register_i2cset(); + register_i2cdump(); +} diff --git a/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.h b/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.h new file mode 100644 index 000000000..2bd395320 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/cmd_i2ctools.h @@ -0,0 +1,20 @@ +/* cmd_i2ctools.h + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void register_i2ctools(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/i2c/i2c_tools/main/component.mk b/examples/peripherals/i2c/i2c_tools/main/component.mk new file mode 100644 index 000000000..44bd2b527 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/i2c/i2c_tools/main/i2ctools_example_main.c b/examples/peripherals/i2c/i2c_tools/main/i2ctools_example_main.c new file mode 100644 index 000000000..302a1d387 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/main/i2ctools_example_main.c @@ -0,0 +1,194 @@ +/* i2c-tools example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" +#include "cmd_decl.h" +#include "esp_vfs_fat.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "sdkconfig.h" + +static const char *TAG = "i2c-tools"; + +#if CONFIG_STORE_HISTORY + +#define MOUNT_PATH "/data" +#define HISTORY_PATH MOUNT_PATH "/history.txt" + +static void initialize_filesystem() +{ + static wl_handle_t wl_handle; + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = true + }; + esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); + return; + } +} +#endif // CONFIG_STORE_HISTORY + +static void initialize_nvs() +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); +} + +static void initialize_console() +{ + /* Disable buffering on stdin and stdout */ + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + /* Configure UART. Note that REF_TICK is used so that the baud rate remains + * correct while APB frequency is changing in light sleep mode. + */ + const uart_config_t uart_config = { + .baud_rate = CONFIG_CONSOLE_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .use_ref_tick = true + }; + ESP_ERROR_CHECK(uart_param_config(CONFIG_CONSOLE_UART_NUM, &uart_config)); + + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK(uart_driver_install(CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0)); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 8, + .max_cmdline_length = 256, +#if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) +#endif + }; + ESP_ERROR_CHECK(esp_console_init(&console_config)); + + /* Configure linenoise line completion library */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint); + + /* Set command history size */ + linenoiseHistorySetMaxLen(100); + +#if CONFIG_STORE_HISTORY + /* Load command history from filesystem */ + linenoiseHistoryLoad(HISTORY_PATH); +#endif +} + +void app_main() +{ + initialize_nvs(); + +#if CONFIG_STORE_HISTORY + initialize_filesystem(); +#endif + + initialize_console(); + + /* Register commands */ + esp_console_register_help_command(); + register_i2ctools(); + register_system(); + + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR; + + printf("\n ==============================================================\n"); + printf(" | Steps to Use i2c-tools on ESP32 |\n"); + printf(" | |\n"); + printf(" | 1. Try 'help', check all supported commands |\n"); + printf(" | 2. Try 'i2cconfig' to configure your I2C bus |\n"); + printf(" | 3. Try 'i2cdetect' to scan devices on the bus |\n"); + printf(" | 4. Try 'i2cget' to get the content of specific register |\n"); + printf(" | 5. Try 'i2cset' to set the value of specific register |\n"); + printf(" | 6. Try 'i2cdump' to dump all the register (Experiment) |\n"); + printf(" | |\n"); + printf(" ==============================================================\n\n"); + + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { + /* zero indicates success */ + printf("\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n"); + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = "esp32> "; +#endif //CONFIG_LOG_COLORS + } + + /* Main loop */ + while (true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char *line = linenoise(prompt); + if (line == NULL) { + /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); +#if CONFIG_STORE_HISTORY + /* Save command history to filesystem */ + linenoiseHistorySave(HISTORY_PATH); +#endif + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(err)); + } else if (err != ESP_OK) { + printf("Internal error: %s\n", esp_err_to_name(err)); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } +} diff --git a/examples/peripherals/i2c/i2c_tools/partitions_example.csv b/examples/peripherals/i2c/i2c_tools/partitions_example.csv new file mode 100644 index 000000000..7e28b5668 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/examples/peripherals/i2c/i2c_tools/sdkconfig.defaults b/examples/peripherals/i2c/i2c_tools/sdkconfig.defaults new file mode 100644 index 000000000..44b2010a0 --- /dev/null +++ b/examples/peripherals/i2c/i2c_tools/sdkconfig.defaults @@ -0,0 +1,17 @@ +# Reduce bootloader log verbosity +CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y +CONFIG_LOG_BOOTLOADER_LEVEL=2 + +# Increase main task stack size +CONFIG_MAIN_TASK_STACK_SIZE=7168 + +# Enable filesystem +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" + +# Enable FreeRTOS stats formatting functions, needed for 'tasks' command +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y