diff --git a/examples/bmp180_i2c/Makefile b/examples/bmp180_i2c/Makefile new file mode 100644 index 0000000..dd5eaf6 --- /dev/null +++ b/examples/bmp180_i2c/Makefile @@ -0,0 +1,3 @@ +PROGRAM=BMP180_Reader +EXTRA_COMPONENTS = extras/i2c extras/bmp180 +include ../../common.mk diff --git a/examples/bmp180_i2c/README.md b/examples/bmp180_i2c/README.md new file mode 100644 index 0000000..21d754a --- /dev/null +++ b/examples/bmp180_i2c/README.md @@ -0,0 +1,7 @@ +# I2C / BMP180 Example + +This example references two addtional drivers [i2c](https://github.com/kanflo/esp-open-rtos-driver-i2c) and [bmp180](https://github.com/Angus71/esp-open-rtos-driver-bmp180), which are provided in the `../../extras` folder. + +If you plan to use one or both of this drivers in your own projects, please check the main development pages for updated versions or reported issues. + +To run this example connect the BMP085/BMP180 SCL to GPIO0 and SDA to GPIO2. diff --git a/examples/bmp180_i2c/bmp180_i2c.c b/examples/bmp180_i2c/bmp180_i2c.c new file mode 100644 index 0000000..76b3705 --- /dev/null +++ b/examples/bmp180_i2c/bmp180_i2c.c @@ -0,0 +1,132 @@ +/* Simple example for I2C / BMP180 / Timer & Event Handling + * + * This sample code is in the public domain. + */ +#include "espressif/esp_common.h" +#include "espressif/sdk_private.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "timers.h" +#include "queue.h" + +// BMP180 driver +#include "bmp180/bmp180.h" + +#define MY_EVT_TIMER 0x01 +#define MY_EVT_BMP180 0x02 + +#define SCL_PIN GPIO_ID_PIN((0)) +#define SDA_PIN GPIO_ID_PIN((2)) + +typedef struct +{ + uint8_t event_type; + bmp180_result_t bmp180_data; +} my_event_t; + +// Communication Queue +static xQueueHandle mainqueue; +static xTimerHandle timerHandle; + +// Own BMP180 User Inform Implementation +bool bmp180_i2c_informUser(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure) +{ + my_event_t ev; + + ev.event_type = MY_EVT_BMP180; + ev.bmp180_data.cmd = cmd; + ev.bmp180_data.temperatue = temperatue; + ev.bmp180_data.pressure = pressure; + + return (xQueueSend(*resultQueue, &ev, 0) == pdTRUE); +} + +// Timer call back +static void bmp180_i2c_timer_cb(xTimerHandle xTimer) +{ + my_event_t ev; + ev.event_type = MY_EVT_TIMER; + + xQueueSend(mainqueue, &ev, 0); +} + +// Check for communiction events +void bmp180_task(void *pvParameters) +{ + // Received pvParameters is communication queue + xQueueHandle *com_queue = (xQueueHandle *)pvParameters; + + printf("%s: Started user interface task\n", __FUNCTION__); + + while(1) + { + my_event_t ev; + + xQueueReceive(*com_queue, &ev, portMAX_DELAY); + + switch(ev.event_type) + { + case MY_EVT_TIMER: + printf("%s: Received Timer Event\n", __FUNCTION__); + + bmp180_trigger_measurement(com_queue); + break; + case MY_EVT_BMP180: + printf("%s: Received BMP180 Event temp:=%ld.%d°C press=%ld.%02ldhPa\n", __FUNCTION__, \ + (int32_t)ev.bmp180_data.temperatue, abs((int32_t)(ev.bmp180_data.temperatue*10)%10), \ + ev.bmp180_data.pressure/100, ev.bmp180_data.pressure%100 ); + break; + default: + break; + } + } +} + +// Setup HW +void user_setup(void) +{ + // Set UART Parameter + sdk_uart_div_modify(0, UART_CLK_FREQ / 115200); + + // Give the UART some time to settle + sdk_os_delay_us(500); +} + +void user_init(void) +{ + // Setup HW + user_setup(); + + // Just some infomations + printf("\n"); + printf("SDK version : %s\n", sdk_system_get_sdk_version()); + printf("GIT version : %s\n", GITSHORTREV); + + // Use our user inform implementation + bmp180_informUser = bmp180_i2c_informUser; + + // Init BMP180 Interface + bmp180_init(SCL_PIN, SDA_PIN); + + // Create Main Communication Queue + mainqueue = xQueueCreate(10, sizeof(my_event_t)); + + // Create user interface task + xTaskCreate(bmp180_task, (signed char *)"bmp180_task", 256, &mainqueue, 2, NULL); + + // Create Timer (Trigger a measurement every second) + timerHandle = xTimerCreate((signed char *)"BMP180 Trigger", 1000/portTICK_RATE_MS, pdTRUE, NULL, bmp180_i2c_timer_cb); + + if (timerHandle != NULL) + { + if (xTimerStart(timerHandle, 0) != pdPASS) + { + printf("%s: Unable to start Timer ...\n", __FUNCTION__); + } + } + else + { + printf("%s: Unable to create Timer ...\n", __FUNCTION__); + } +} diff --git a/extras/README.md b/extras/README.md new file mode 100644 index 0000000..1bc0a52 --- /dev/null +++ b/extras/README.md @@ -0,0 +1,10 @@ +# extras + +This folder contains supportive source code. + +For current versions or reporting issues etc. please check the main development pages. + +- [bmp180](https://github.com/Angus71/esp-open-rtos-driver-bmp180) +- [i2c](https://github.com/kanflo/esp-open-rtos-driver-i2c) +- [rboot-ota](http://richard.burtons.org/) + diff --git a/extras/bmp180/LICENSE b/extras/bmp180/LICENSE new file mode 100644 index 0000000..1f44f09 --- /dev/null +++ b/extras/bmp180/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Frank Bargstedt + +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. + diff --git a/extras/bmp180/README.md b/extras/bmp180/README.md new file mode 100644 index 0000000..d6073c6 --- /dev/null +++ b/extras/bmp180/README.md @@ -0,0 +1,56 @@ +# Driver for BMP085/BMP180 digital pressure sensor + +This driver is written for usage with the ESP8266 and FreeRTOS ([esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos) and [esp-open-rtos-driver-i2c](https://github.com/kanflo/esp-open-rtos-driver-i2c)). + +### Usage + +Before using the BMP180 module, the function `bmp180_init(SCL_PIN, SDA_PIN)` needs to be called to setup the I2C interface and do validation if the BMP180/BMP085 is accessible. + +If the setup is sucessfully and a measurement is triggered, the result of the measurement is provided to the user as an event send via the `qQueue` provided with `bmp180_trigger_*measurement(pQueue);` + +#### Example + +``` +#define SCL_PIN GPIO_ID_PIN(0) +#define SDA_PIN GPIO_ID_PIN(2) +... + +if (!bmp180_init(SCL_PIN, SDA_PIN)) { +// An error occured, while dong the init (E.g device not found etc.) +} + +// Trigger a measurement +bmp180_trigger_measurement(pQueue); + +``` + +#### Change queue event + +Per default the event send to the user via the provided queue is of the type `bmp180_result_t`. As this might not always be desired, a way is provided so that the user can provide a function, which creates and sends the event via the provided queue. + +As all data aqquired from the BMP180/BMP085 is provided to the `bmp180_informUser` function, it is also possible to calculate new informations (E.g altitude etc.) + +##### Example + +``` +// Own BMP180 User Inform Implementation +bool my_informUser(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure) { + my_event_t ev; + + ev.event_type = MY_EVT_BMP180; + ev.bmp180_data.cmd = cmd; + ev.bmp180_data.temperatue = temperatue; + ev.bmp180_data.pressure = pressure; + + return (xQueueSend(*resultQueue, &ev, 0) == pdTRUE); +} + +... + +// Use our user inform implementation +// needs to be set before first measurement is triggered +bmp180_informUser = my_informUser; + + +``` + diff --git a/extras/bmp180/bmp180.c b/extras/bmp180/bmp180.c new file mode 100644 index 0000000..66203b0 --- /dev/null +++ b/extras/bmp180/bmp180.c @@ -0,0 +1,351 @@ +#include "bmp180.h" + +#include "FreeRTOS.h" +#include "queue.h" +#include "task.h" + +#include "espressif/esp_common.h" +#include "espressif/sdk_private.h" + +#include "i2c/i2c.h" + +#define BMP180_RX_QUEUE_SIZE 10 +#define BMP180_TASK_PRIORITY 9 + +#define BMP180_DEVICE_ADDRESS 0x77 + +#define BMP180_VERSION_REG 0xD0 +#define BMP180_CONTROL_REG 0xF4 +#define BMP180_RESET_REG 0xE0 +#define BMP180_OUT_MSB_REG 0xF6 +#define BMP180_OUT_LSB_REG 0xF7 +#define BMP180_OUT_XLSB_REG 0xF8 + +#define BMP180_CALIBRATION_REG 0xAA + +// +// Values for BMP180_CONTROL_REG +// +#define BMP180_MEASURE_TEMP 0x2E +#define BMP180_MEASURE_PRESS_OSS0 0x34 +#define BMP180_MEASURE_PRESS_OSS1 0x74 +#define BMP180_MEASURE_PRESS_OSS2 0xB4 +#define BMP180_MEASURE_PRESS_OSS3 0xF4 + +#define BMP180_DEFAULT_CONV_TIME 5000 + +// +// CHIP ID stored in BMP180_VERSION_REG +// +#define BMP180_CHIP_ID 0x55 + +// +// Reset value for BMP180_RESET_REG +// +#define BMP180_RESET_VALUE 0xB6 + + +// BMP180_Event_Command +typedef struct +{ + uint8_t cmd; + const xQueueHandle* resultQueue; +} bmp180_command_t; + +// Just works due to the fact that xQueueHandle is a "void *" +static xQueueHandle bmp180_rx_queue = NULL; +static xTaskHandle bmp180_task_handle = NULL; + +// Calibration constants +static int16_t AC1; +static int16_t AC2; +static int16_t AC3; +static uint16_t AC4; +static uint16_t AC5; +static uint16_t AC6; + +static int16_t B1; +static int16_t B2; + +static int16_t MB; +static int16_t MC; +static int16_t MD; + +// +// Forward declarations +// +static void bmp180_meassure(const bmp180_command_t* command); +static bool bmp180_informUser_Impl(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure); + +// Set default implementation .. User gets result as bmp180_result_t event +bool (*bmp180_informUser)(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure) = bmp180_informUser_Impl; + +// I2C Driver Task +static void bmp180_driver_task(void *pvParameters) +{ + // Data to be received from user + bmp180_command_t current_command; + +#ifdef BMP180_DEBUG + // Wait for commands from the outside + printf("%s: Started Task\n", __FUNCTION__); +#endif + + while(1) + { + // Wait for user to insert commands + if (xQueueReceive(bmp180_rx_queue, ¤t_command, portMAX_DELAY) == pdTRUE) + { +#ifdef BMP180_DEBUG + printf("%s: Received user command %d 0x%p\n", __FUNCTION__, current_command.cmd, current_command.resultQueue); +#endif + // use user provided queue + if (current_command.resultQueue != NULL) + { + // Work on it ... + bmp180_meassure(¤t_command); + } + } + } +} + +static uint8_t bmp180_readRegister8(uint8_t reg) +{ + uint8_t r = 0; + + if (!i2c_slave_read(BMP180_DEVICE_ADDRESS, reg, &r, 1)) + { + r = 0; + } + return r; +} + + +static int16_t bmp180_readRegister16(uint8_t reg) +{ + uint8_t d[] = { 0, 0 }; + int16_t r = 0; + + if (i2c_slave_read(BMP180_DEVICE_ADDRESS, reg, d, 2)) + { + r = ((int16_t)d[0]<<8) | (d[1]); + } + return r; +} + +static void bmp180_start_Messurement(uint8_t cmd) +{ + uint8_t d[] = { BMP180_CONTROL_REG, cmd }; + + i2c_slave_write(BMP180_DEVICE_ADDRESS, d, 2); +} + +static int16_t bmp180_getUncompensatedMessurement(uint8_t cmd) +{ + // Write Start Code into reg 0xF4 (Currently without oversampling ...) + bmp180_start_Messurement((cmd==BMP180_TEMPERATURE)?BMP180_MEASURE_TEMP:BMP180_MEASURE_PRESS_OSS0); + + // Wait 5ms Datasheet states 4.5ms + sdk_os_delay_us(BMP180_DEFAULT_CONV_TIME); + + return (int16_t)bmp180_readRegister16(BMP180_OUT_MSB_REG); +} + +static void bmp180_fillInternalConstants(void) +{ + AC1 = bmp180_readRegister16(BMP180_CALIBRATION_REG+0); + AC2 = bmp180_readRegister16(BMP180_CALIBRATION_REG+2); + AC3 = bmp180_readRegister16(BMP180_CALIBRATION_REG+4); + AC4 = bmp180_readRegister16(BMP180_CALIBRATION_REG+6); + AC5 = bmp180_readRegister16(BMP180_CALIBRATION_REG+8); + AC6 = bmp180_readRegister16(BMP180_CALIBRATION_REG+10); + + B1 = bmp180_readRegister16(BMP180_CALIBRATION_REG+12); + B2 = bmp180_readRegister16(BMP180_CALIBRATION_REG+14); + + MB = bmp180_readRegister16(BMP180_CALIBRATION_REG+16); + MC = bmp180_readRegister16(BMP180_CALIBRATION_REG+18); + MD = bmp180_readRegister16(BMP180_CALIBRATION_REG+20); + +#ifdef BMP180_DEBUG + printf("%s: AC1:=%d AC2:=%d AC3:=%d AC4:=%u AC5:=%u AC6:=%u \n", __FUNCTION__, AC1, AC2, AC3, AC4, AC5, AC6); + printf("%s: B1:=%d B2:=%d\n", __FUNCTION__, B1, B2); + printf("%s: MB:=%d MC:=%d MD:=%d\n", __FUNCTION__, MB, MC, MD); +#endif +} + +static bool bmp180_create_communication_queues() +{ + // Just create them once + if (bmp180_rx_queue==NULL) + { + bmp180_rx_queue = xQueueCreate(BMP180_RX_QUEUE_SIZE, sizeof(bmp180_result_t)); + } + + return (bmp180_rx_queue!=NULL); +} + +static bool bmp180_is_avaialble() +{ + return (bmp180_readRegister8(BMP180_VERSION_REG)==BMP180_CHIP_ID); +} + +static bool bmp180_createTask() +{ + // We already have a task + portBASE_TYPE x = pdPASS; + + if (bmp180_task_handle==NULL) + { + x = xTaskCreate(bmp180_driver_task, (signed char *)"bmp180_driver_task", 256, NULL, BMP180_TASK_PRIORITY, &bmp180_task_handle); + } + return (x==pdPASS); +} + +static void bmp180_meassure(const bmp180_command_t* command) +{ + int32_t T, P; + + // Init result to 0 + T = P = 0; + + if (command->resultQueue != NULL) + { + int32_t UT, X1, X2, B5; + + // + // Temperature is always needed ... Also required for pressure only + // + // Calculation taken from BMP180 Datasheet + UT = (int32_t)bmp180_getUncompensatedMessurement(BMP180_TEMPERATURE); + + X1 = (UT - (int32_t)AC6) * ((int32_t)AC5) >> 15; + X2 = ((int32_t)MC << 11) / (X1 + (int32_t)MD); + B5 = X1 + X2; + + T = (B5 + 8) >> 4; + +#ifdef BMP180_DEBUG + printf("%s: T:= %ld.%d\n", __FUNCTION__, T/10, abs(T%10)); +#endif + + // Do we also need pressure? + if (command->cmd & BMP180_PRESSURE) + { + int32_t X3, B3, B6; + uint32_t B4, B7, UP; + + UP = ((uint32_t)bmp180_getUncompensatedMessurement(BMP180_PRESSURE) & 0xFFFF); + + // Calculation taken from BMP180 Datasheet + B6 = B5 - 4000; + X1 = ((int32_t)B2 * ((B6 * B6) >> 12)) >> 11; + X2 = ((int32_t)AC2 * B6) >> 11; + X3 = X1 + X2; + + B3 = (((int32_t)AC1 * 4 + X3) + 2) >> 2; + X1 = ((int32_t)AC3 * B6) >> 13; + X2 = ((int32_t)B1 * ((B6 * B6) >> 12)) >> 16; + X3 = ((X1 + X2) + 2) >> 2; + B4 = ((uint32_t)AC4 * (uint32_t)(X3 + 32768)) >> 15; + B7 = (UP - B3) * (uint32_t)(50000UL); + + if (B7 < 0x80000000) + { + P = (B7 * 2) / B4; + } + else + { + P = (B7 / B4) * 2; + } + + X1 = (P >> 8) * (P >> 8); + X1 = (X1 * 3038) >> 16; + X2 = (-7357 * P) >> 16; + P = P + ((X1 + X2 + (int32_t)3791) >> 4); + +#ifdef BMP180_DEBUG + printf("%s: P:= %ld\n", __FUNCTION__, P); +#endif + } + + // Inform the user ... + if (!bmp180_informUser(command->resultQueue, command->cmd, ((bmp180_temp_t)T)/10.0, (bmp180_press_t)P)) + { + // Failed to send info to user + printf("%s: Unable to inform user bmp180_informUser returned \"false\"\n", __FUNCTION__); + } + } +} + +// Default user inform implementation +static bool bmp180_informUser_Impl(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure) +{ + bmp180_result_t result; + + result.cmd = cmd; + result.temperatue = temperatue; + result.pressure = pressure; + + return (xQueueSend(*resultQueue, &result, 0) == pdTRUE); +} + +// Just init all needed queues +bool bmp180_init(uint8_t scl, uint8_t sda) +{ + // 1. Create required queues + bool result = false; + + if (bmp180_create_communication_queues()) + { + // 2. Init i2c driver + i2c_init(scl, sda); + + // 3. Check for bmp180 ... + if (bmp180_is_avaialble()) + { + // 4. Init all internal constants ... + bmp180_fillInternalConstants(); + + // 5. Start driver task + if (bmp180_createTask()) + { + // We are finished + result = true; + } + } + } + + return result; +} + +void bmp180_trigger_measurement(const xQueueHandle* resultQueue) +{ + bmp180_command_t c; + + c.cmd = BMP180_PRESSURE + BMP180_TEMPERATURE; + c.resultQueue = resultQueue; + + xQueueSend(bmp180_rx_queue, &c, 0); +} + + +void bmp180_trigger_pressure_measurement(const xQueueHandle* resultQueue) +{ + bmp180_command_t c; + + c.cmd = BMP180_PRESSURE; + c.resultQueue = resultQueue; + + xQueueSend(bmp180_rx_queue, &c, 0); +} + +void bmp180_trigger_temperature_measurement(const xQueueHandle* resultQueue) +{ + bmp180_command_t c; + + c.cmd = BMP180_TEMPERATURE; + c.resultQueue = resultQueue; + + xQueueSend(bmp180_rx_queue, &c, 0); +} diff --git a/extras/bmp180/bmp180.h b/extras/bmp180/bmp180.h new file mode 100644 index 0000000..73709ff --- /dev/null +++ b/extras/bmp180/bmp180.h @@ -0,0 +1,55 @@ +/* + * bmp180.h + * + * Created on: 23.08.2015 + * Author: fbargste + */ + +#ifndef DRIVER_BMP180_H_ +#define DRIVER_BMP180_H_ + +#include "stdint.h" +#include "stdbool.h" + +#include "FreeRTOS.h" +#include "queue.h" + +// Uncomment to enable debug output +//#define BMP180_DEBUG + +#define BMP180_TEMPERATURE (1<<0) +#define BMP180_PRESSURE (1<<1) + +// +// Create bmp180_types +// + +// temperature in °C +typedef float bmp180_temp_t; +// pressure in mPa (To get hPa divide by 100) +typedef uint32_t bmp180_press_t; + +// BMP180_Event_Result +typedef struct +{ + uint8_t cmd; + bmp180_temp_t temperatue; + bmp180_press_t pressure; +} bmp180_result_t; + +// Init bmp180 driver ... +bool bmp180_init(uint8_t scl, uint8_t sda); + +// Trigger a "complete" measurement (temperature and pressure will be valid when given to "bmp180_informUser) +void bmp180_trigger_measurement(const xQueueHandle* resultQueue); + +// Trigger a "temperature only" measurement (only temperature will be valid when given to "bmp180_informUser) +void bmp180_trigger_temperature_measurement(const xQueueHandle* resultQueue); + +// Trigger a "pressure only" measurement (only pressure will be valid when given to "bmp180_informUser) +void bmp180_trigger_pressure_measurement(const xQueueHandle* resultQueue); + +// Give the user the chance to create it's own handler +extern bool (*bmp180_informUser)(const xQueueHandle* resultQueue, uint8_t cmd, bmp180_temp_t temperatue, bmp180_press_t pressure); + +#endif /* DRIVER_BMP180_H_ */ diff --git a/extras/bmp180/component.mk b/extras/bmp180/component.mk new file mode 100644 index 0000000..5efc68a --- /dev/null +++ b/extras/bmp180/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/bmp180 + +INC_DIRS += $(ROOT)extras + +# args for passing into compile rule generation +extras/bmp180_INC_DIR = $(ROOT)extras +extras/bmp180_SRC_DIR = $(ROOT)extras/bmp180 + +$(eval $(call component_compile_rules,extras/bmp180)) diff --git a/extras/i2c/LICENSE b/extras/i2c/LICENSE new file mode 100644 index 0000000..85820f6 --- /dev/null +++ b/extras/i2c/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Johan Kanflo + +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. + diff --git a/extras/i2c/README.md b/extras/i2c/README.md new file mode 100644 index 0000000..7ab90f6 --- /dev/null +++ b/extras/i2c/README.md @@ -0,0 +1,40 @@ +# Yet another I2C driver for the ESP8266 + +This time a driver for the excellent esp-open-rtos. This is a bit banging I2C driver based on the Wikipedia pesudo C code [1]. + +### Adding to your project + +Add the driver to your project as a submodule rather than cloning it: + +```` +% git submodule add https://github.com/kanflo/esp-open-rtos-driver-i2c.git i2c +```` +The esp-open-rtos makefile-fu will make sure the driver is built. + +### Usage + + +```` +#include + +#define SCL_PIN (0) +#define SDA_PIN (2) + +uint8_t slave_addr = 0x20; +uint8_t reg_addr = 0x1f; +uint8_t reg_data; +uint8_t data[] = {reg_addr, 0xc8}; + +i2c_init(SCL_PIN, SDA_PIN); + +// Write data to slave +bool success = i2c_slave_write(slave_addr, data, sizeof(data)); + +// Issue write to slave, sending reg_addr, followed by reading 1 byte +success = i2c_slave_read(slave_addr, ®_addr, reg_data, 1); + +```` + +The driver is released under the MIT license. + +[1] https://en.wikipedia.org/wiki/I²C#Example_of_bit-banging_the_I.C2.B2C_Master_protocol \ No newline at end of file diff --git a/extras/i2c/component.mk b/extras/i2c/component.mk new file mode 100644 index 0000000..13b0f83 --- /dev/null +++ b/extras/i2c/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/i2c + +INC_DIRS += $(ROOT)extras + +# args for passing into compile rule generation +extras/i2c_INC_DIR = $(ROOT)extras +extras/i2c_SRC_DIR = $(ROOT)extras/i2c + +$(eval $(call component_compile_rules,extras/i2c)) diff --git a/extras/i2c/i2c.c b/extras/i2c/i2c.c new file mode 100644 index 0000000..ef19c45 --- /dev/null +++ b/extras/i2c/i2c.c @@ -0,0 +1,229 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Johan Kanflo (github.com/kanflo) + * + * 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. + */ + +#include +#include // sdk_os_delay_us +#include "i2c.h" + + +// I2C driver for ESP8266 written for use with esp-open-rtos +// Based on https://en.wikipedia.org/wiki/I²C#Example_of_bit-banging_the_I.C2.B2C_Master_protocol + +// With calling overhead, we end up at ~100kbit/s +#define CLK_HALF_PERIOD_US (1) + +#define CLK_STRETCH (10) + +static bool started; +static uint8_t g_scl_pin; +static uint8_t g_sda_pin; + +void i2c_init(uint8_t scl_pin, uint8_t sda_pin) +{ + started = false; + g_scl_pin = scl_pin; + g_sda_pin = sda_pin; +} + +static void i2c_delay(void) +{ + sdk_os_delay_us(CLK_HALF_PERIOD_US); +} + +// Set SCL as input and return current level of line, 0 or 1 +static bool read_scl(void) +{ + gpio_enable(g_scl_pin, GPIO_INPUT); + return gpio_read(g_scl_pin); // Clock high, valid ACK +} + +// Set SDA as input and return current level of line, 0 or 1 +static bool read_sda(void) +{ + gpio_enable(g_sda_pin, GPIO_INPUT); + // TODO: Without this delay we get arbitration lost in i2c_stop + i2c_delay(); + return gpio_read(g_sda_pin); // Clock high, valid ACK +} + +// Actively drive SCL signal low +static void clear_scl(void) +{ + gpio_enable(g_scl_pin, GPIO_OUTPUT); + gpio_write(g_scl_pin, 0); +} + +// Actively drive SDA signal low +static void clear_sda(void) +{ + gpio_enable(g_sda_pin, GPIO_OUTPUT); + gpio_write(g_sda_pin, 0); +} + +// Output start condition +void i2c_start(void) +{ + uint32_t clk_stretch = CLK_STRETCH; + if (started) { // if started, do a restart cond + // Set SDA to 1 + (void) read_sda(); + i2c_delay(); + while (read_scl() == 0 && clk_stretch--) ; + // Repeated start setup time, minimum 4.7us + i2c_delay(); + } + if (read_sda() == 0) { + printf("I2C: arbitration lost in i2c_start\n"); + } + // SCL is high, set SDA from 1 to 0. + clear_sda(); + i2c_delay(); + clear_scl(); + started = true; +} + +// Output stop condition +void i2c_stop(void) +{ + uint32_t clk_stretch = CLK_STRETCH; + // Set SDA to 0 + clear_sda(); + i2c_delay(); + // Clock stretching + while (read_scl() == 0 && clk_stretch--) ; + // Stop bit setup time, minimum 4us + i2c_delay(); + // SCL is high, set SDA from 0 to 1 + if (read_sda() == 0) { + printf("I2C: arbitration lost in i2c_stop\n"); + } + i2c_delay(); + started = false; +} + +// Write a bit to I2C bus +static void i2c_write_bit(bool bit) +{ + uint32_t clk_stretch = CLK_STRETCH; + if (bit) { + (void) read_sda(); + } else { + clear_sda(); + } + i2c_delay(); + // Clock stretching + while (read_scl() == 0 && clk_stretch--) ; + // SCL is high, now data is valid + // If SDA is high, check that nobody else is driving SDA + if (bit && read_sda() == 0) { + printf("I2C: arbitration lost in i2c_write_bit\n"); + } + i2c_delay(); + clear_scl(); +} + +// Read a bit from I2C bus +static bool i2c_read_bit(void) +{ + uint32_t clk_stretch = CLK_STRETCH; + bool bit; + // Let the slave drive data + (void) read_sda(); + i2c_delay(); + // Clock stretching + while (read_scl() == 0 && clk_stretch--) ; + // SCL is high, now data is valid + bit = read_sda(); + i2c_delay(); + clear_scl(); + return bit; +} + +bool i2c_write(uint8_t byte) +{ + bool nack; + uint8_t bit; + for (bit = 0; bit < 8; bit++) { + i2c_write_bit((byte & 0x80) != 0); + byte <<= 1; + } + nack = i2c_read_bit(); + return !nack; +} + +uint8_t i2c_read(bool ack) +{ + uint8_t byte = 0; + uint8_t bit; + for (bit = 0; bit < 8; bit++) { + byte = (byte << 1) | i2c_read_bit(); + } + i2c_write_bit(ack); + return byte; +} + +bool i2c_slave_write(uint8_t slave_addr, uint8_t *data, uint8_t len) +{ + bool success = false; + do { + i2c_start(); + if (!i2c_write(slave_addr << 1)) + break; + while (len--) { + if (!i2c_write(*data++)) + break; + } + i2c_stop(); + success = true; + } while(0); + return success; +} + +bool i2c_slave_read(uint8_t slave_addr, uint8_t data, uint8_t *buf, uint32_t len) +{ + bool success = false; + do { + i2c_start(); + if (!i2c_write(slave_addr << 1)) { + break; + } + i2c_write(data); + i2c_stop(); + i2c_start(); + if (!i2c_write(slave_addr << 1 | 1)) { // Slave address + read + break; + } + while(len) { + *buf = i2c_read(len == 1); + buf++; + len--; + } + success = true; + } while(0); + i2c_stop(); + if (!success) { + printf("I2C: write error\n"); + } + return success; +} diff --git a/extras/i2c/i2c.h b/extras/i2c/i2c.h new file mode 100644 index 0000000..ac3163f --- /dev/null +++ b/extras/i2c/i2c.h @@ -0,0 +1,51 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Johan Kanflo (github.com/kanflo) + * + * 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. + */ + +#ifndef __I2C_H__ +#define __I2C_H__ +#endif + +#include +#include + +// Init bitbanging I2C driver on given pins +void i2c_init(uint8_t scl_pin, uint8_t sda_pin); + +// Write a byte to I2C bus. Return true if slave acked. +bool i2c_write(uint8_t byte); + +// Read a byte from I2C bus. Return true if slave acked. +uint8_t i2c_read(bool ack); + +// Write 'len' bytes from 'buf' to slave. Return true if slave acked. +bool i2c_slave_write(uint8_t slave_addr, uint8_t *buf, uint8_t len); + +// Issue a read operation and send 'data', followed by reading 'len' bytes +// from slave into 'buf'. Return true if slave acked. +bool i2c_slave_read(uint8_t slave_addr, uint8_t data, uint8_t *buf, uint32_t len); + +// Send start and stop conditions. Only needed when implementing protocols for +// devices where the i2c_slave_[read|write] functions above are of no use. +void i2c_start(void); +void i2c_stop(void);