Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions drivers/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ ifneq (,$(filter ft6% ft5% ft3%,$(USEMODULE)))
USEMODULE += ft5x06
endif

ifneq (,$(filter gp8%,$(USEMODULE)))
USEMODULE += gp8xxx
endif

ifneq (,$(filter hmc5883l_%,$(USEMODULE)))
USEMODULE += hmc5883l
endif
Expand Down
1 change: 1 addition & 0 deletions drivers/gp8xxx/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTMAKE)/driver_with_saul.mk
1 change: 1 addition & 0 deletions drivers/gp8xxx/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FEATURES_REQUIRED += periph_i2c
10 changes: 10 additions & 0 deletions drivers/gp8xxx/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# include variants of GP8xxx drivers as pseudo modules
PSEUDOMODULES += gp8503
PSEUDOMODULES += gp8512
PSEUDOMODULES += gp8211s
PSEUDOMODULES += gp8403
PSEUDOMODULES += gp8413
PSEUDOMODULES += gp8302

USEMODULE_INCLUDES_gp8xxx := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_gp8xxx)
201 changes: 201 additions & 0 deletions drivers/gp8xxx/gp8xxx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com>
* SPDX-License-Identifier: LGPL-2.1-only
*/

/**
* @ingroup drivers_gp8xxx
* @{
*
* @file
* @brief Device driver implementation for the gp8xxx
*
* @author Bas Stottelaar <basstottelaar@gmail.com>
*
* @}
*/

#include <assert.h>
#include <errno.h>
#include <string.h>

#include "gp8xxx.h"
#include "gp8xxx_constants.h"
#include "gp8xxx_params.h"

#define ENABLE_DEBUG 0
#include "debug.h"

#define GP8XXX_I2C (dev->params.i2c_dev)
#define GP8XXX_ADDR (dev->params.address)

#if GP8XXX_HAS_VDAC
# define GP8XXX_RANGE (dev->params.range)
#endif

int gp8xxx_init(gp8xxx_t *dev, const gp8xxx_params_t *params)
{
/* initialize the device descriptor */
dev->params = *params;

/* setup the i2c bus */
i2c_acquire(GP8XXX_I2C);

/* initialize the peripheral */
if (i2c_write_byte(GP8XXX_I2C, GP8XXX_ADDR, 0, 0) != 0) {
i2c_release(GP8XXX_I2C);
DEBUG("[gp8xxx] gp8xxx_init: init failed\n");
return -ENODEV;
}

i2c_release(GP8XXX_I2C);

/* configure a peripheral to a known state */
#if GP8XXX_HAS_VDAC
if (dev->params.info->type == GP8XXX_INFO_TYPE_VDAC) {
gp8xxx_set_voltage_range(dev, GP8XXX_RANGE);
gp8xxx_set_voltage(dev, GP8XXX_CHANNEL_ALL, 0);
}
#endif
#if GP8XXX_HAS_IDAC
if (dev->params.info->type == GP8XXX_INFO_TYPE_IDAC) {
dev->range = GP8XXX_OUTPUT_RANGE_25_MA;
gp8xxx_set_current(dev, GP8XXX_CHANNEL_ALL, 0);
}
#endif

return 0;
}

int gp8xxx_set_dac(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t value)
{
assert(channel < dev->params.info->channels || channel == GP8XXX_CHANNEL_ALL);
assert(value <= dev->params.info->resolution);

DEBUG("[gp8xxx] gp8xxx_set_dac: channel=%d value=%d\n", (int)channel, value);

/* value must be sent MSB */
if (dev->params.info->resolution == GP8XXX_INFO_RESOLUTION_12_BIT) {
value = value << 4;
}
else if (dev->params.info->resolution == GP8XXX_INFO_RESOLUTION_15_BIT) {
value = value << 1;
}

/* when both channels are selected, write 4 bytes instead of 2 bytes */
uint8_t out[] = {
GP8XXX_REG_CHANNEL << (int)(channel == GP8XXX_CHANNEL_ALL ? 0 : channel),
value & 0xff,
value >> 8,
value & 0xff,
value >> 8
};

size_t size = (channel == GP8XXX_CHANNEL_ALL ? 5 : 3);

i2c_acquire(GP8XXX_I2C);

if (i2c_write_bytes(GP8XXX_I2C, GP8XXX_ADDR, &out, size, 0) != 0) {
i2c_release(GP8XXX_I2C);
DEBUG("[gp8xxx] gp8xxx_set_dac: set raw value failed\n");
return -EIO;
}

i2c_release(GP8XXX_I2C);

return 0;
}

#if GP8XXX_HAS_VDAC
int gp8xxx_set_voltage_range(gp8xxx_t *dev, gp8xxx_output_range_t range)
{
assert(dev->params.info->type == GP8XXX_INFO_TYPE_VDAC);

/* only the 5 V/10 V models have a customizable range */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to return an error when the range wasn't actually set because the device doesn't support it? -ENOSUP for example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided that wrong use would fall in the use of programming errors, and that an assert would be sufficient alternative to another return value or making it type safer.

if (dev->params.info->range == GP8XXX_INFO_RANGE_5V_10V) {
uint8_t value = 0x00;

switch (range) {
case GP8XXX_OUTPUT_RANGE_5V:
value = 0x00;
break;
case GP8XXX_OUTPUT_RANGE_10V:
value = 0x11;
break;
default:
return -ERANGE;
}

i2c_acquire(GP8XXX_I2C);

if (i2c_write_reg(GP8XXX_I2C, GP8XXX_ADDR, GP8XXX_REG_OUTPUT_RANGE, value, 0) != 0) {
DEBUG("[gp8xxx] gp8xxx_set_voltage_range: cannot set voltage range\n");
i2c_release(GP8XXX_I2C);
return -EIO;
}

i2c_release(GP8XXX_I2C);
}

/* store range for calculation purposes */
dev->range = range;

return 0;
}

int gp8xxx_set_voltage(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t voltage)
{
assert(dev->params.info->type == GP8XXX_INFO_TYPE_VDAC);

if (voltage > dev->range) {
DEBUG("[gp8xxx] gp8xxx_set_voltage: voltage out of range\n");
return -ERANGE;
}

uint32_t value = ((uint32_t)voltage * (dev->params.info->resolution - 1) + (dev->range / 2)) /
dev->range;

return gp8xxx_set_dac(dev, channel, (uint16_t)value);
}
#endif /* GP8XXX_HAS_VDAC */

#if GP8XXX_HAS_IDAC
void gp8xxx_set_current_calibration(gp8xxx_t *dev, uint16_t calibration_4ma,
uint16_t calibration_20ma)
{
assert(dev->params.info->type == GP8XXX_INFO_TYPE_IDAC);
assert((calibration_4ma == 0 && calibration_20ma == 0) ||
(calibration_4ma < calibration_20ma && calibration_4ma < dev->params.info->resolution &&
calibration_20ma <= dev->params.info->resolution));

dev->calibration_4ma = calibration_4ma;
dev->calibration_20ma = calibration_20ma;
}

int gp8xxx_set_current(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t current)
{
assert(dev->params.info->type == GP8XXX_INFO_TYPE_IDAC);

/* ensure current is within configured range */
if (current > dev->range) {
DEBUG("[gp8xxx] gp8xxx_set_current: current out of range\n");
return -ERANGE;
}

/* compute ADC value based on either calibration data or configured range */
uint32_t value;

if (dev->calibration_4ma > 0 && dev->calibration_20ma > 0 && current >= 4000 &&
current <= 20000) {
value = dev->calibration_4ma +
((uint32_t)(current - 4000) * (dev->calibration_20ma - dev->calibration_4ma)) /
(20000 - 4000);
}
else {
value = ((uint32_t)current * (dev->params.info->resolution - 1) + (dev->range / 2)) /
(dev->range);
}

return gp8xxx_set_dac(dev, channel, (uint16_t)value);
}
#endif /* GP8XXX_HAS_IDAC */
60 changes: 60 additions & 0 deletions drivers/gp8xxx/gp8xxx_info.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com>
* SPDX-License-Identifier: LGPL-2.1-only
*/

/**
* @ingroup drivers_gp8xxx
* @{
*
* @file
* @brief Definitions of the Guestgood GP8xxx I2C DACs
*
* @author Bas Stottelaar <basstottelaar@gmail.com>
*
* @}
*/

#include "gp8xxx_info.h"

const gp8xxx_info_t gp8503_info = {
.type = GP8503_INFO_TYPE,
.channels = GP8503_INFO_CHANNELS,
.range = GP8503_INFO_RANGE,
.resolution = GP8503_INFO_RESOLUTION,
};

const gp8xxx_info_t gp8512_info = {
.type = GP8512_INFO_TYPE,
.channels = GP8512_INFO_CHANNELS,
.range = GP8512_INFO_RANGE,
.resolution = GP8512_INFO_RESOLUTION,
};

const gp8xxx_info_t gp8211s_info = {
.type = GP8211S_INFO_TYPE,
.channels = GP8211S_INFO_CHANNELS,
.range = GP8211S_INFO_RANGE,
.resolution = GP8211S_INFO_RESOLUTION,
};

const gp8xxx_info_t gp8403_info = {
.type = GP8403_INFO_TYPE,
.channels = GP8403_INFO_CHANNELS,
.range = GP8403_INFO_RANGE,
.resolution = GP8403_INFO_RESOLUTION,
};

const gp8xxx_info_t gp8413_info = {
.type = GP8413_INFO_TYPE,
.channels = GP8413_INFO_CHANNELS,
.range = GP8413_INFO_RANGE,
.resolution = GP8413_INFO_RESOLUTION,
};

const gp8xxx_info_t gp8302_info = {
.type = GP8302_INFO_TYPE,
.channels = GP8302_INFO_CHANNELS,
.range = GP8302_INFO_RANGE,
.resolution = GP8302_INFO_RESOLUTION,
};
95 changes: 95 additions & 0 deletions drivers/gp8xxx/gp8xxx_saul.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com>
* SPDX-License-Identifier: LGPL-2.1-only
*/

/**
* @ingroup drivers_gp8xxx
* @{
*
* @file
* @brief SAUL adoption for the Guestgood GP8xxx I2C DACs
*
* @author Bas Stottelaar <basstottelaar@gmail.com>
*
* @}
*/

#include <math.h>

#include "saul.h"

#include "gp8xxx.h"

#define ENABLE_DEBUG 0
#include "debug.h"

static uint16_t convert(uint16_t value, int scale)
{
if (scale > 0) {
return value / pow(10, scale);
}
else if (scale < 0) {
return value * pow(10, -scale);
}

return value;
}

#if GP8XXX_HAS_VDAC
static int set_voltage(const void *dev, const phydat_t *data)
{
gp8xxx_saul_t *saul_dev = (gp8xxx_saul_t *)dev;

DEBUG("[gp8xxx_saul] set_voltage value=%d scale=%d\n", data->val[0], data->scale);

/* convert value to mV */
uint16_t value = convert(data->val[0], -3 - data->scale);

/* write voltage*/
int res = gp8xxx_set_voltage(saul_dev->dev, saul_dev->channel, value);

if (res != 0) {
return -ECANCELED;
}

return 1;
}
#endif

#if GP8XXX_HAS_IDAC
static int set_current(const void *dev, const phydat_t *data)
{
gp8xxx_saul_t *saul_dev = (gp8xxx_saul_t *)dev;

DEBUG("[gp8xxx_saul] set_current value=%d scale=%d\n", data->val[0], data->scale);

/* convert value to uA */
uint16_t value = convert(data->val[0], -6 - data->scale);

/* write current */
int res = gp8xxx_set_current(saul_dev->dev, saul_dev->channel, value);

if (res != 0) {
return -ECANCELED;
}

return 1;
}
#endif

#if GP8XXX_HAS_VDAC
const saul_driver_t gp8xxx_voltage_saul_driver = {
.read = saul_read_notsup,
.write = set_voltage,
.type = SAUL_ACT_VOLTAGE,
};
#endif

#if GP8XXX_HAS_IDAC
const saul_driver_t gp8xxx_current_saul_driver = {
.read = saul_read_notsup,
.write = set_current,
.type = SAUL_ACT_CURRENT,
};
#endif
Loading