grovepi/Software/Cpp/grovepi.cpp
2025-03-21 16:04:17 +01:00

416 lines
10 KiB
C++

// GrovePi C++ library
// v0.2
//
// This library provides the basic functions for using the GrovePi in C
//
// The GrovePi connects the Raspberry Pi and Grove sensors. You can learn more about GrovePi here: http://www.dexterindustries.com/GrovePi
//
// Have a question about this example? Ask on the forums here: http://forum.dexterindustries.com/c/grovepi
//
// History
// ------------------------------------------------
// Author Date Comments
// Karan 28 Dec 2015 Initial Authoring
// Robert April 2017 Continuing
/*
License
The MIT License (MIT)
GrovePi for the Raspberry Pi: an open source platform for connecting Grove Sensors to the Raspberry Pi.
Copyright (C) 2017 Dexter Industries
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 "grovepi.h"
static const bool DEBUG = false;
static const int RBUFFER_SIZE = 32;
static const int WBUFFER_SIZE = 4;
static int file_device = 0;
static int max_i2c_retries = 5;
static const uint8_t DIGITAL_READ = 1;
static const uint8_t DIGITAL_WRITE = 2;
static const uint8_t ANALOG_READ = 3;
static const uint8_t ANALOG_WRITE = 4;
static const uint8_t PIN_MODE = 5;
static const uint8_t USONIC_READ = 7;
namespace GrovePi
{
// variables which can be used
// in the user-program
const uint8_t INPUT = 0;
const uint8_t OUTPUT = 1;
const bool LOW = false;
const bool HIGH = true;
uint8_t GROVE_ADDRESS = 0x04;
}
/**
* determines the revision of the raspberry hardware
* @return revision number
*/
static uint8_t gpioHardwareRevision()
{
int revision = 0;
FILE * filp = fopen("/proc/cpuinfo", "r");
char buffer[512];
char term;
if(filp != NULL)
{
while(fgets(buffer,sizeof(buffer),filp) != NULL)
{
if(!strncasecmp("revision\t", buffer, 9))
{
if(sscanf(buffer + strlen(buffer) - 5, "%x%c", &revision, &term) == 2)
{
if(term == '\n')
break;
revision = 0;
}
}
}
fclose(filp);
}
return revision;
}
/**
* determines wheter I2C is found at "/dev/i2c-0" or "/dev/i2c-1"
* depending on the raspberry model
*
* @param smbus_name string to hold the filename
*
* hw_rev 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
* Type.1 X X - - X - - X X X X X - - X X
* Type.2 - - X X X - - X X X X X - - X X
* Type.3 X X X X X X X X X X X X X X
*
* hw_rev 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
* Type.1 - X X - - X X X X X - - - - - -
* Type.2 - X X - - - X X X X - X X X X X
* Type.3 X X X X X X X X X X X X - - - -
*
*/
void GrovePi::SMBusName(char *smbus_name)
{
unsigned int hw_revision = gpioHardwareRevision();
unsigned int smbus_rev;
if(hw_revision < 4)
// type 1
smbus_rev = 1;
else if(hw_revision < 16)
// type 2
smbus_rev = 2;
else
// type 3
smbus_rev = 3;
if(smbus_rev == 2 || smbus_rev == 3)
strcpy(smbus_name, "/dev/i2c-1");
else
strcpy(smbus_name, "/dev/i2c-0");
}
/**
* sets the threshold limit
* at which an exception is thrown
* @param _max_i2c_retries number of retries
*/
void GrovePi::setMaxI2CRetries(int _max_i2c_retries)
{
// each failed retry takes 1 second
// so the number of [max_i2c_retries]
// represents the timeout on a
// transaction / connection in seconds
max_i2c_retries = _max_i2c_retries;
}
/**
* tries to get communication w/ the GrovePi
* throws I2CError on failing to establish communication
* @param 7-bit address of the slave device
*/
void GrovePi::initGrovePi()
{
file_device = initDevice(GROVE_ADDRESS);
}
/**
* for setting the address of an I2C device
* @param address 7-bit address of the slave device
*/
int GrovePi::initDevice(uint8_t address)
{
char filename[11]; // enough to hold "/dev/i2c-x"
int current_retry = 0;
int i2c_file_device;
SMBusName(filename);
// try to connect for a number of times
while(current_retry < max_i2c_retries)
{
// increase the counter
current_retry += 1;
// open port for read/write operation
if((i2c_file_device = open(filename, O_RDWR)) < 0)
{
printf("[failed to open i2c port]\n");
// try in the next loop to connect
continue;
}
// setting up port options and address of the device
if(ioctl(i2c_file_device, I2C_SLAVE, address) < 0)
{
printf("[unable to get bus access to talk to slave]\n");
// try in the next loop to connect
continue;
}
// if it got connected, then exit
break;
}
// if connection couldn't be established
// throw exception
if(current_retry == max_i2c_retries)
throw I2CError("[I2CError on opening port]\n");
return i2c_file_device;
}
void GrovePi::setGrovePiAddress(uint8_t address)
{
GROVE_ADDRESS = address;
}
/**
* writes a block of [WBUFFER_SIZE] bytes to the slave i2c device
* throws I2CError on failing to send data
* @param command command to send to GrovePi
* @param pin_number number
* @param opt1 optional argument depending on sensor/actuator/etc
* @param opt2 optional argument depending on sensor/actuator/etc
*/
void GrovePi::writeBlock(uint8_t command, uint8_t pin_number, uint8_t opt1, uint8_t opt2)
{
int output_code = -1;
int current_retry = 0;
uint8_t data_block[WBUFFER_SIZE] = {command, pin_number, opt1, opt2};
// repeat until it writes the data
// or until it fails sending it
while(output_code == -1 && current_retry < max_i2c_retries)
{
output_code = i2c_smbus_write_i2c_block_data(file_device, 1, WBUFFER_SIZE, &data_block[0]);
current_retry += 1;
}
// if the error persisted
// after retrying for [max_i2c_retries] retries
// then throw exception
if(output_code == -1)
throw I2CError("[I2CError writing block: max retries reached]\n");
}
/**
* sends a single byte to the i2c slave
* throws I2CError on failing to send data
* @param byte_val byte to be sent
*/
void GrovePi::writeByte(uint8_t byte_val)
{
uint8_t data_block[WBUFFER_SIZE] = {byte_val};
uint8_t length = 1;
int current_retry = 0;
int output_code = 0;
// repeat until it writes the data
// or until it fails sending it
while(output_code != length && current_retry < max_i2c_retries)
{
output_code = write(file_device, data_block, length);
current_retry += 1;
}
// if the error persisted
// after retrying for [max_i2c_retries] retries
// then throw exception
if(output_code != length)
throw I2CError("[I2CError writing byte: max retries reached]\n");
}
/**
* reads a block of [RBUFFER_SIZE] bytes from the slave device
* throws I2CError on failing to read data
* @param data_block pointer to hold the read data
* @return number of bytes read
*/
uint8_t GrovePi::readBlock(uint8_t *data_block)
{
int current_retry = 0;
int output_code = 0;
// repeat until it reads the data
// or until it fails sending it
while(output_code == 0 && current_retry < max_i2c_retries)
{
output_code = i2c_smbus_read_i2c_block_data(file_device, 1, RBUFFER_SIZE, data_block);
current_retry += 1;
}
// if the error persisted
// after retrying for [max_i2c_retries] retries
// then throw exception
if(output_code == 0)
throw I2CError("[I2CError reading block: max retries reached]\n");
return output_code;
}
/**
* reads 1 byte from the slave device
* throws I2CError on failing to read data
* @return value read from the slave device
*/
uint8_t GrovePi::readByte()
{
int current_retry = 0;
int output_code = -1;
// repeat until it reads the data
// or until it fails sending it
while((output_code < 0 || output_code == 255) && current_retry < max_i2c_retries)
{
output_code = i2c_smbus_read_byte(file_device);
current_retry += 1;
}
// if the error persisted
// after retrying for [max_i2c_retries] retries
// then throw exception
if(output_code < 0 || output_code == 255)
throw I2CError("[I2CError reading byte: max retries reached]\n");
return output_code;
}
/**
* sleep raspberry
* @param milliseconds time
*/
void GrovePi::delay(unsigned int milliseconds)
{
usleep(milliseconds * 1000);
}
/**
* set pin as OUTPUT or INPUT
* @param pin number
* @param mode OUTPUT/INPUT
*/
void GrovePi::pinMode(uint8_t pin, uint8_t mode)
{
writeBlock(PIN_MODE, pin, mode);
}
/**
* set a pin as HIGH or LOW
* @param pin number
* @param value HIGH or LOW
*/
void GrovePi::digitalWrite(uint8_t pin, bool value)
{
writeBlock(DIGITAL_WRITE, pin, (uint8_t)value);
}
/**
* reads whether a pin is HIGH or LOW
* @param pin number
* @return HIGH or LOW
*/
bool GrovePi::digitalRead(uint8_t pin)
{
writeBlock(DIGITAL_READ, pin);
return readByte();
}
/**
* describe at a desired pin a voltage between 0 and VCC
* @param pin number
* @param value 0-255
*/
void GrovePi::analogWrite(uint8_t pin, uint8_t value)
{
writeBlock(ANALOG_WRITE, pin, value);
}
/**
* reads analog data from grovepi sensor(s)
* @param pin number
* @return 16-bit data
*/
short GrovePi::analogRead(uint8_t pin)
{
uint8_t data[32];
writeBlock(ANALOG_READ, pin);
readBlock(data);
short output = (data[1] << 8) + data[2];
if(output == 65535)
output = -1;
return output;
}
/**
* to be completed
* @param pin number
* @return time taken for the sound to travel back?
*/
short GrovePi::ultrasonicRead(uint8_t pin)
{
uint8_t incoming[32];
short output;
writeBlock(USONIC_READ, pin);
delay(60);
readByte();
readBlock(incoming);
output = (incoming[1] << 8) + incoming[2];
if(output == (2 << 16) - 1)
output = -1;
return output;
}
const char* GrovePi::I2CError::detail()
{
return this->what();
}