Skip to content

change portTICK_RATE_MS to portTICK_PERIOD_MS #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -2,3 +2,4 @@ build/
sdkconfig
sdkconfig.old

.vscode/
6 changes: 4 additions & 2 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
set(COMPONENT_SRCDIRS ".")
set(COMPONENT_ADD_INCLUDEDIRS ".")

register_component()

idf_component_register(SRCS "app_main.c"
"components/esp32-smbus/smbus.c"
"components/esp32-i2c-lcd1602/i2c-lcd1602.c"
INCLUDE_DIRS ".")
83 changes: 42 additions & 41 deletions main/app_main.c
Original file line number Diff line number Diff line change
@@ -37,41 +37,41 @@
#include "sdkconfig.h"
#include "rom/uart.h"

#include "smbus.h"
#include "i2c-lcd1602.h"
#include "components/esp32-smbus/smbus.h"
#include "components/esp32-i2c-lcd1602/i2c-lcd1602.h"

#define TAG "app"

// LCD1602
#define LCD_NUM_ROWS 2
#define LCD_NUM_COLUMNS 32
#define LCD_NUM_VISIBLE_COLUMNS 16
#define LCD_NUM_ROWS 2
#define LCD_NUM_COLUMNS 32
#define LCD_NUM_VISIBLE_COLUMNS 16

// LCD2004
//#define LCD_NUM_ROWS 4
//#define LCD_NUM_COLUMNS 40
//#define LCD_NUM_VISIBLE_COLUMNS 20
// #define LCD_NUM_ROWS 4
// #define LCD_NUM_COLUMNS 40
// #define LCD_NUM_VISIBLE_COLUMNS 20

// Undefine USE_STDIN if no stdin is available (e.g. no USB UART) - a fixed delay will occur instead of a wait for a keypress.
#define USE_STDIN 1
//#undef USE_STDIN
#define USE_STDIN 1
// #undef USE_STDIN

#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_TX_BUF_LEN 0 // disabled
#define I2C_MASTER_RX_BUF_LEN 0 // disabled
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA
#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_TX_BUF_LEN 0 // disabled
#define I2C_MASTER_RX_BUF_LEN 0 // disabled
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA
#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL

static void i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = I2C_MASTER_SDA_IO;
conf.sda_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups
conf.sda_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups
conf.scl_io_num = I2C_MASTER_SCL_IO;
conf.scl_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups
conf.scl_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
i2c_param_config(i2c_master_port, &conf);
i2c_driver_install(i2c_master_port, conf.mode,
@@ -88,32 +88,33 @@ static uint8_t _wait_for_user(void)
#ifdef USE_STDIN
while (!c)
{
STATUS s = uart_rx_one_char(&c);
if (s == OK) {
printf("%c", c);
}
vTaskDelay(1);
STATUS s = uart_rx_one_char(&c);
if (s == OK)
{
printf("%c", c);
}
vTaskDelay(1);
}
#else
vTaskDelay(1000 / portTICK_RATE_MS);
#endif
return c;
}

void lcd1602_task(void * pvParameter)
void lcd1602_task(void *pvParameter)
{
// Set up I2C
i2c_master_init();
i2c_port_t i2c_num = I2C_MASTER_NUM;
uint8_t address = CONFIG_LCD1602_I2C_ADDRESS;

// Set up the SMBus
smbus_info_t * smbus_info = smbus_malloc();
smbus_info_t *smbus_info = smbus_malloc();
ESP_ERROR_CHECK(smbus_init(smbus_info, i2c_num, address));
ESP_ERROR_CHECK(smbus_set_timeout(smbus_info, 1000 / portTICK_RATE_MS));
ESP_ERROR_CHECK(smbus_set_timeout(smbus_info, 1000 / portTICK_PERIOD_MS));

// Set up the LCD1602 device with backlight off
i2c_lcd1602_info_t * lcd_info = i2c_lcd1602_malloc();
i2c_lcd1602_info_t *lcd_info = i2c_lcd1602_malloc();
ESP_ERROR_CHECK(i2c_lcd1602_init(lcd_info, smbus_info, true,
LCD_NUM_ROWS, LCD_NUM_COLUMNS, LCD_NUM_VISIBLE_COLUMNS));

@@ -148,7 +149,7 @@ void lcd1602_task(void * pvParameter)
i2c_lcd1602_move_cursor(lcd_info, 15, 1);
i2c_lcd1602_write_char(lcd_info, 'C');

ESP_LOGI(TAG, "move to 0,1 and blink"); // cursor should still be on
ESP_LOGI(TAG, "move to 0,1 and blink"); // cursor should still be on
_wait_for_user();
i2c_lcd1602_move_cursor(lcd_info, 0, 1);
i2c_lcd1602_set_blink(lcd_info, true);
@@ -175,13 +176,13 @@ void lcd1602_task(void * pvParameter)

ESP_LOGI(TAG, "disable blink");
_wait_for_user();
i2c_lcd1602_set_blink(lcd_info, false); // cursor should still be on
i2c_lcd1602_set_blink(lcd_info, false); // cursor should still be on

ESP_LOGI(TAG, "disable cursor");
_wait_for_user();
i2c_lcd1602_set_cursor(lcd_info, false);

ESP_LOGI(TAG, "display alphabet from 0,0"); // should overflow to second line at "ABC..."
ESP_LOGI(TAG, "display alphabet from 0,0"); // should overflow to second line at "ABC..."
_wait_for_user();
i2c_lcd1602_home(lcd_info);
i2c_lcd1602_write_string(lcd_info, "abcdefghijklmnopqrstuvwxyz0123456789.,-+ABCDEFGHIJKLMNOPQRSTUVWXYZ");
@@ -191,7 +192,7 @@ void lcd1602_task(void * pvParameter)
for (int i = 0; i < 8; ++i)
{
i2c_lcd1602_scroll_display_left(lcd_info);
vTaskDelay(200 / portTICK_RATE_MS);
vTaskDelay(200 / portTICK_PERIOD_MS);
}

ESP_LOGI(TAG, "scroll display right 8 places quickly");
@@ -226,7 +227,7 @@ void lcd1602_task(void * pvParameter)
for (int i = 0; i < 5; ++i)
{
i2c_lcd1602_write_char(lcd_info, '>');
vTaskDelay(200 / portTICK_RATE_MS);
vTaskDelay(200 / portTICK_PERIOD_MS);
}

ESP_LOGI(TAG, "change address counter to decrement (right to left) and display <<<<<");
@@ -235,7 +236,7 @@ void lcd1602_task(void * pvParameter)
for (int i = 0; i < 5; ++i)
{
i2c_lcd1602_write_char(lcd_info, '<');
vTaskDelay(200 / portTICK_RATE_MS);
vTaskDelay(200 / portTICK_PERIOD_MS);
}

ESP_LOGI(TAG, "disable auto-scroll and display +++++");
@@ -244,7 +245,7 @@ void lcd1602_task(void * pvParameter)
for (int i = 0; i < 5; ++i)
{
i2c_lcd1602_write_char(lcd_info, '+');
vTaskDelay(200 / portTICK_RATE_MS);
vTaskDelay(200 / portTICK_PERIOD_MS);
}

ESP_LOGI(TAG, "set left_to_right and display >>>>>");
@@ -253,7 +254,7 @@ void lcd1602_task(void * pvParameter)
for (int i = 0; i < 5; ++i)
{
i2c_lcd1602_write_char(lcd_info, '>');
vTaskDelay(200 / portTICK_RATE_MS);
vTaskDelay(200 / portTICK_PERIOD_MS);
}

ESP_LOGI(TAG, "clear display and disable cursor");
@@ -264,14 +265,14 @@ void lcd1602_task(void * pvParameter)
ESP_LOGI(TAG, "create and display custom characters");
_wait_for_user();
// https://github.com/agnunez/ESP8266-I2C-LCD1602/blob/master/examples/CustomChars/CustomChars.ino
uint8_t bell[8] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4};
uint8_t note[8] = {0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0};
uint8_t bell[8] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4};
uint8_t note[8] = {0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0};
uint8_t clock[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0};
uint8_t heart[8] = {0x0, 0xa, 0x1f, 0x1f, 0xe, 0x4, 0x0};
uint8_t duck[8] = {0x0, 0xc, 0x1d, 0xf, 0xf, 0x6, 0x0};
uint8_t check[8] = {0x0, 0x1 ,0x3, 0x16, 0x1c, 0x8, 0x0};
uint8_t duck[8] = {0x0, 0xc, 0x1d, 0xf, 0xf, 0x6, 0x0};
uint8_t check[8] = {0x0, 0x1, 0x3, 0x16, 0x1c, 0x8, 0x0};
uint8_t cross[8] = {0x0, 0x1b, 0xe, 0x4, 0xe, 0x1b, 0x0};
uint8_t retarrow[8] = { 0x1, 0x1, 0x5, 0x9, 0x1f, 0x8, 0x4};
uint8_t retarrow[8] = {0x1, 0x1, 0x5, 0x9, 0x1f, 0x8, 0x4};
i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_0, bell);
i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_1, note);
i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_2, clock);
@@ -320,7 +321,7 @@ void lcd1602_task(void * pvParameter)
while (1)
{
i2c_lcd1602_write_char(lcd_info, c);
vTaskDelay(100 / portTICK_RATE_MS);
vTaskDelay(100 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "col %d, row %d, char 0x%02x", col, row, c);
++c;
++col;
624 changes: 624 additions & 0 deletions main/components/esp32-i2c-lcd1602/i2c-lcd1602.c

Large diffs are not rendered by default.

336 changes: 336 additions & 0 deletions main/components/esp32-i2c-lcd1602/i2c-lcd1602.h

Large diffs are not rendered by default.

401 changes: 401 additions & 0 deletions main/components/esp32-smbus/smbus.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,401 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/**
* @file smbus.c
*
* SMBus diagrams are represented as a chain of "piped" symbols, using the following symbols:
*
* - ADDR : the 7-bit I2C address of a bus slave.
* - S : the START condition sent by a bus master.
* - Sr : the REPEATED START condition sent by a master.
* - P : the STOP condition sent by a master.
* - Wr : bit 0 of the address byte indicating a write operation. Value is 0.
* - Rd : bit 0 of the address byte indicating a read operation. Value is 1.
* - R/W : bit 0 of the address byte, indicating a read or write operation.
* - A : ACKnowledge bit sent by a master.
* - N : Not ACKnowledge bit set by a master.
* - As : ACKnowledge bit sent by a slave.
* - Ns : Not ACKnowledge bit set by a slave.
* - DATA : data byte sent by a master.
* - DATAs : data byte sent by a slave.
*/

// TODO: add proper error checking around all i2c functions

#include <stddef.h>
#include <string.h>
#include <inttypes.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"

#include "smbus.h"

static const char * TAG = "smbus";

#define WRITE_BIT I2C_MASTER_WRITE
#define READ_BIT I2C_MASTER_READ
#define ACK_CHECK true
#define NO_ACK_CHECK false
#define ACK_VALUE 0x0
#define NACK_VALUE 0x1
#define MAX_BLOCK_LEN 255 // SMBus v3.0 increases this from 32 to 255
//#define MEASURE // enable measurement and reporting of I2C transaction duration

static bool _is_init(const smbus_info_t * smbus_info)
{
bool ok = false;
if (smbus_info != NULL)
{
if (smbus_info->init)
{
ok = true;
}
else
{
ESP_LOGE(TAG, "smbus_info is not initialised");
}
}
else
{
ESP_LOGE(TAG, "smbus_info is NULL");
}
return ok;
}

static esp_err_t _check_i2c_error(esp_err_t err)
{
switch (err)
{
case ESP_OK: // Success
break;
case ESP_ERR_INVALID_ARG: // Parameter error
ESP_LOGE(TAG, "I2C parameter error");
break;
case ESP_FAIL: // Sending command error, slave doesn't ACK the transfer.
ESP_LOGE(TAG, "I2C no slave ACK");
break;
case ESP_ERR_INVALID_STATE: // I2C driver not installed or not in master mode.
ESP_LOGE(TAG, "I2C driver not installed or not master");
break;
case ESP_ERR_TIMEOUT: // Operation timeout because the bus is busy.
ESP_LOGE(TAG, "I2C timeout");
break;
default:
ESP_LOGE(TAG, "I2C error %d", err);
}
return err;
}

esp_err_t _write_bytes(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | (DATA | As){*len} | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info) && data)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK);
i2c_master_write_byte(cmd, command, ACK_CHECK);
i2c_master_write(cmd, data, len, ACK_CHECK);
i2c_master_stop(cmd);
#ifdef MEASURE
uint64_t start_time = esp_timer_get_time();
#endif
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
#ifdef MEASURE
ESP_LOGI(TAG, "_write_bytes: i2c_master_cmd_begin took %"PRIu64" us", esp_timer_get_time() - start_time);
#endif
i2c_cmd_link_delete(cmd);
}
return err;
}

esp_err_t _read_bytes(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | (DATAs | A){*len-1} | DATAs | N | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info) && data)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK);
i2c_master_write_byte(cmd, command, ACK_CHECK);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK);
if (len > 1)
{
i2c_master_read(cmd, data, len - 1, ACK_VALUE);
}
i2c_master_read_byte(cmd, &data[len - 1], NACK_VALUE);
i2c_master_stop(cmd);
#ifdef MEASURE
uint64_t start_time = esp_timer_get_time();
#endif
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
#ifdef MEASURE
ESP_LOGI(TAG, "_read_bytes: i2c_master_cmd_begin took %"PRIu64" us", esp_timer_get_time() - start_time);
#endif
i2c_cmd_link_delete(cmd);
}
return err;
}


// Public API

smbus_info_t * smbus_malloc(void)
{
smbus_info_t * smbus_info = malloc(sizeof(*smbus_info));
if (smbus_info != NULL)
{
memset(smbus_info, 0, sizeof(*smbus_info));
ESP_LOGD(TAG, "malloc smbus_info_t %p", smbus_info);
}
else
{
ESP_LOGE(TAG, "malloc smbus_info_t failed");
}
return smbus_info;
}

void smbus_free(smbus_info_t ** smbus_info)
{
if (smbus_info != NULL && (*smbus_info != NULL))
{
ESP_LOGD(TAG, "free smbus_info_t %p", *smbus_info);
free(*smbus_info);
*smbus_info = NULL;
}
else
{
ESP_LOGE(TAG, "free smbus_info_t failed");
}
}

esp_err_t smbus_init(smbus_info_t * smbus_info, i2c_port_t i2c_port, i2c_address_t address)
{
if (smbus_info != NULL)
{
smbus_info->i2c_port = i2c_port;
smbus_info->address = address;
smbus_info->timeout = SMBUS_DEFAULT_TIMEOUT;
smbus_info->init = true;
}
else
{
ESP_LOGE(TAG, "smbus_info is NULL");
return ESP_FAIL;
}
return ESP_OK;
}

esp_err_t smbus_set_timeout(smbus_info_t * smbus_info, portBASE_TYPE timeout)
{
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info))
{
smbus_info->timeout = timeout;
err = ESP_OK;
}
return err;
}

esp_err_t smbus_quick(const smbus_info_t * smbus_info, bool bit)
{
// Protocol: [S | ADDR | R/W | As | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info))
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | bit, ACK_CHECK);
i2c_master_stop(cmd);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);
}
return err;
}

esp_err_t smbus_send_byte(const smbus_info_t * smbus_info, uint8_t data)
{
// Protocol: [S | ADDR | Wr | As | DATA | As | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info))
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK);
i2c_master_write_byte(cmd, data, ACK_CHECK);
i2c_master_stop(cmd);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);
}
return err;
}

esp_err_t smbus_receive_byte(const smbus_info_t * smbus_info, uint8_t * data)
{
// Protocol: [S | ADDR | Rd | As | DATAs | N | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info))
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK);
i2c_master_read_byte(cmd, data, NACK_VALUE);
i2c_master_stop(cmd);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);
}
return err;
}

esp_err_t smbus_write_byte(const smbus_info_t * smbus_info, uint8_t command, uint8_t data)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | DATA | As | P]
return _write_bytes(smbus_info, command, &data, 1);
}

esp_err_t smbus_write_word(const smbus_info_t * smbus_info, uint8_t command, uint16_t data)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | DATA-LOW | As | DATA-HIGH | As | P]
uint8_t temp[2] = { data & 0xff, (data >> 8) & 0xff };
return _write_bytes(smbus_info, command, temp, 2);
}

esp_err_t smbus_read_byte(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | DATA | N | P]
return _read_bytes(smbus_info, command, data, 1);
}

esp_err_t smbus_read_word(const smbus_info_t * smbus_info, uint8_t command, uint16_t * data)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | DATA-LOW | A | DATA-HIGH | N | P]
esp_err_t err = ESP_FAIL;
uint8_t temp[2] = { 0 };
if (data)
{
err = _read_bytes(smbus_info, command, temp, 2);
if (err == ESP_OK)
{
*data = (temp[1] << 8) + temp[0];
}
else
{
*data = 0;
}
}
return err;
}

esp_err_t smbus_write_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, uint8_t len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | LEN | As | DATA-1 | As | DATA-2 | As ... | DATA-LEN | As | P]
esp_err_t err = ESP_FAIL;
if (_is_init(smbus_info) && data)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK);
i2c_master_write_byte(cmd, command, ACK_CHECK);
i2c_master_write_byte(cmd, len, ACK_CHECK);
for (size_t i = 0; i < len; ++i)
{
i2c_master_write_byte(cmd, data[i], ACK_CHECK);
}
i2c_master_stop(cmd);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);
}
return err;
}

esp_err_t smbus_read_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, uint8_t * len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | LENs | A | DATA-1 | A | DATA-2 | A ... | DATA-LEN | N | P]
esp_err_t err = ESP_FAIL;

if (_is_init(smbus_info) && data && len)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK);
i2c_master_write_byte(cmd, command, ACK_CHECK);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK);
uint8_t slave_len = 0;
i2c_master_read_byte(cmd, &slave_len, ACK_VALUE);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);

if (err != ESP_OK)
{
*len = 0;
return err;
}

if (slave_len > *len)
{
ESP_LOGW(TAG, "slave data length %d exceeds data len %d bytes", slave_len, *len);
slave_len = *len;
}

cmd = i2c_cmd_link_create();
for (size_t i = 0; i < slave_len - 1; ++i)
{
i2c_master_read_byte(cmd, &data[i], ACK_VALUE);
}
i2c_master_read_byte(cmd, &data[slave_len - 1], NACK_VALUE);
i2c_master_stop(cmd);
err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout));
i2c_cmd_link_delete(cmd);

if (err == ESP_OK)
{
*len = slave_len;
}
else
{
*len = 0;
}
}
return err;
}

esp_err_t smbus_i2c_write_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | (DATA | As){*len} | P]
return _write_bytes(smbus_info, command, data, len);
}

esp_err_t smbus_i2c_read_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len)
{
// Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | (DATAs | A){*len-1} | DATAs | N | P]
return _read_bytes(smbus_info, command, data, len);
}
210 changes: 210 additions & 0 deletions main/components/esp32-smbus/smbus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* MIT License
*
* Copyright (c) 2017 David Antliff
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/**
* @file smbus.h
* @brief Interface definitions for the ESP32-compatible SMBus Protocol component.
*
* This component provides structures and functions that are useful for communicating
* with SMBus-compatible I2C slave devices.
*/

#ifndef SMBUS_H
#define SMBUS_H

#include "stdbool.h"
#include "driver/i2c.h"

#ifdef __cplusplus
extern "C"
{
#endif

#define SMBUS_DEFAULT_TIMEOUT (1000 / portTICK_PERIOD_MS) ///< Default transaction timeout in ticks
/**
* @brief 7-bit or 10-bit I2C slave address.
*/
typedef uint16_t i2c_address_t;

/**
* @brief Structure containing information related to the SMBus protocol.
*/
typedef struct
{
bool init; ///< True if struct has been initialised, otherwise false
i2c_port_t i2c_port; ///< ESP-IDF I2C port number
i2c_address_t address; ///< I2C address of slave device
portBASE_TYPE timeout; ///< Number of ticks until I2C operation timeout
} smbus_info_t;

/**
* @brief Construct a new SMBus info instance.
* New instance should be initialised before calling other functions.
* @return Pointer to new device info instance, or NULL if it cannot be created.
*/
smbus_info_t *smbus_malloc(void);

/**
* @brief Delete an existing SMBus info instance.
* @param[in,out] smbus_info Pointer to SMBus info instance that will be freed and set to NULL.
*/
void smbus_free(smbus_info_t **smbus_info);

/**
* @brief Initialise a SMBus info instance with the specified I2C information.
* The I2C timeout defaults to approximately 1 second.
* @param[in] smbus_info Pointer to SMBus info instance.
* @param[in] i2c_port I2C port to associate with this SMBus instance.
* @param[in] address Address of I2C slave device.
*/
esp_err_t smbus_init(smbus_info_t *smbus_info, i2c_port_t i2c_port, i2c_address_t address);

/**
* @brief Set the I2C timeout.
* I2C transactions that do not complete within this period are considered an error.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] timeout Number of ticks to wait until the transaction is considered in error.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_set_timeout(smbus_info_t *smbus_info, portBASE_TYPE timeout);

/**
* @brief Send a single bit to a slave device in the place of the read/write bit.
* May be used to simply turn a device function on or off, or enable or disable
* a low-power standby mode. There is no data sent or received.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] bit Data bit to send.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_quick(const smbus_info_t *smbus_info, bool bit);

/**
* @brief Send a single byte to a slave device.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] data Data byte to send to slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_send_byte(const smbus_info_t *smbus_info, uint8_t data);

/**
* @brief Receive a single byte from a slave device.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[out] data Data byte received from slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_receive_byte(const smbus_info_t *smbus_info, uint8_t *data);

/**
* @brief Write a single byte to a slave device with a command code.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[in] data Data byte to send to slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_write_byte(const smbus_info_t *smbus_info, uint8_t command, uint8_t data);

/**
* @brief Write a single word (two bytes) to a slave device with a command code.
* The least significant byte is transmitted first.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[in] data Data word to send to slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_write_word(const smbus_info_t *smbus_info, uint8_t command, uint16_t data);

/**
* @brief Read a single byte from a slave device with a command code.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[out] data Data byte received from slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_read_byte(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data);

/**
* @brief Read a single word (two bytes) from a slave device with a command code.
* The first byte received is the least significant byte.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[out] data Data byte received from slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_read_word(const smbus_info_t *smbus_info, uint8_t command, uint16_t *data);

/**
* @brief Write up to 255 bytes to a slave device with a command code.
* This uses a byte count to negotiate the length of the transaction.
* The first byte in the data array is transmitted first.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[in] data Data bytes to send to slave.
* @param[in] len Number of bytes to send to slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_write_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, uint8_t len);

/**
* @brief Read up to 255 bytes from a slave device with a command code.
* This uses a byte count to negotiate the length of the transaction.
* The first byte received is placed in the first array location.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[out] data Data bytes received from slave.
* @param[in/out] len Size of data array, and number of bytes actually received.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_read_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, uint8_t *len);

/**
* @brief Write bytes to a slave device with a command code.
* No byte count is used - the transaction lasts as long as the master requires.
* The first byte in the data array is transmitted first.
* This operation is not defined by the SMBus specification.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[in] data Data bytes to send to slave.
* @param[in] len Number of bytes to send to slave.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_i2c_write_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, size_t len);

/**
* @brief Read bytes from a slave device with a command code (combined format).
* No byte count is used - the transaction lasts as long as the master requires.
* The first byte received is placed in the first array location.
* This operation is not defined by the SMBus specification.
* @param[in] smbus_info Pointer to initialised SMBus info instance.
* @param[in] command Device-specific command byte.
* @param[out] data Data bytes received from slave.
* @param[in/out] len Size of data array. If the slave fails to provide sufficient bytes, ESP_ERR_TIMEOUT will be returned.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t smbus_i2c_read_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, size_t len);

#ifdef __cplusplus
}
#endif

#endif // SMBUS_H