Merge pull request #30 from Angus71/master

Added bmp180_i2c example
This commit is contained in:
Angus Gratton 2015-09-03 08:37:53 +10:00
commit 7e30f48650
14 changed files with 996 additions and 0 deletions

View file

@ -0,0 +1,3 @@
PROGRAM=BMP180_Reader
EXTRA_COMPONENTS = extras/i2c extras/bmp180
include ../../common.mk

View file

@ -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.

View file

@ -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__);
}
}

10
extras/README.md Normal file
View file

@ -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/)

22
extras/bmp180/LICENSE Normal file
View file

@ -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.

56
extras/bmp180/README.md Normal file
View file

@ -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;
```

351
extras/bmp180/bmp180.c Normal file
View file

@ -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, &current_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(&current_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);
}

55
extras/bmp180/bmp180.h Normal file
View file

@ -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_ */

View file

@ -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))

22
extras/i2c/LICENSE Normal file
View file

@ -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.

40
extras/i2c/README.md Normal file
View file

@ -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 <i2c.h>
#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, &reg_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

9
extras/i2c/component.mk Normal file
View file

@ -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))

229
extras/i2c/i2c.c Normal file
View file

@ -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 <esp8266.h>
#include <espressif/esp_misc.h> // 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;
}

51
extras/i2c/i2c.h Normal file
View file

@ -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 <stdint.h>
#include <stdbool.h>
// 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);