Implement the entire driver.

This commit is contained in:
H. Utku Maden 2021-07-25 19:14:45 +03:00
parent 34774a0786
commit 0d1a8e4252
6 changed files with 700 additions and 0 deletions

6
CMakeLists.txt Normal file

@ -0,0 +1,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRCS
"source/io.c"
"source/driver.c"
)

89
include/ds3231.h Normal file

@ -0,0 +1,89 @@
#ifndef _ESP_IDF_DS3231_H_
#define _ESP_IDF_DS3231_H_
#define DS3231_API
#include <driver/i2c.h>
#include <time.h>
#include <errno.h>
typedef struct ds3231_t *ds3231_t;
typedef enum ds3231_alarmType_t
{
DS3231_ALARM1_PERSEC = 0,
DS3231_ALARM1_S,
DS3231_ALARM1_SM,
DS3231_ALARM1_SMH,
DS3231_ALARM1_SMHMD,
DS3231_ALARM1_SMHWD,
DS3231_ALARM2_PREMIN = 0,
DS3231_ALARM2_M,
DS3231_ALARM2_MH,
DS3231_ALARM2_MHMD,
DS3231_ALARM2_MHWD
} ds3231_alarmType_t;
typedef enum ds3231_rate_t
{
DS3231_RATE_1HZ = 0,
DS3231_RATE_1024HZ = 1,
DS3231_RATE_4096HZ = 2,
DS3231_RATE_8192HZ = 3
} ds3231_rate_t;
typedef enum ds3231_status_t
{
DS3231_STATUS_OSF = 1 << 7,
DS3231_STATUS_BSY = 1 << 2,
DS3231_STATUS_AL2 = 1 << 1,
DS3231_STATUS_AL1 = 1 << 0,
} ds3231_status_t;
DS3231_API ds3231_t ds3231_create(i2c_port_t port);
DS3231_API void ds3231_destroy(ds3231_t driver);
DS3231_API int ds3231_initialize(ds3231_t driver, int *opt_out_osf);
DS3231_API int ds3231_setSquareWaveOutput(ds3231_t driver, ds3231_rate_t rate, int battery_backed);
DS3231_API int ds3231_setInterrupt(ds3231_t driver, int alarm1, int alarm2);
DS3231_API int ds3231_getStatus(ds3231_t driver, ds3231_status_t *out_status);
DS3231_API int ds3231_getAgingOffset(ds3231_t driver, int8_t *out_offset);
DS3231_API int ds3231_setAgingOffset(ds3231_t driver, int8_t offset);
DS3231_API int ds3231_getTime(ds3231_t driver, struct tm *time);
DS3231_API int ds3231_setTime(ds3231_t driver, const struct tm *time);
DS3231_API int ds3231_setAlarm1(ds3231_t driver, ds3231_alarmType_t type, const struct tm *time);
DS3231_API int ds3231_setAlarm2(ds3231_t driver, ds3231_alarmType_t type, const struct tm *time);
DS3231_API int ds3231_beginTemperature(ds3231_t driver);
DS3231_API int ds3231_endTemperature(ds3231_t driver, int16_t *out_temperature);
inline static int ds3231_endTemperatureF(ds3231_t driver, float *out_temperature)
{
if (out_temperature == NULL)
{
errno = EINVAL;
return -1;
}
int16_t iTemp;
if (ds3231_endTemperature(driver, &iTemp))
{
return -1;
}
*out_temperature = (float)iTemp / 4.0f;
}
#endif

426
source/driver.c Normal file

@ -0,0 +1,426 @@
#include "private.h"
#include <errno.h>
#define F_INIT (1 << 0)
struct ds3231_t
{
int flags;
i2c_port_t port;
};
#define ASSERT_DRV() do { \
if (driver == NULL || !(driver->flags & F_INIT)) { \
errno = EINVAL; \
return -1; \
} \
} while(0)
DS3231_API ds3231_t ds3231_create(i2c_port_t port)
{
if (port > I2C_NUM_MAX)
{
errno = EINVAL;
return NULL;
}
ds3231_t driver = malloc(sizeof(struct ds3231_t));
if (driver)
{
driver->flags = 0;
driver->port = port;
}
return driver;
}
DS3231_API void ds3231_destroy(ds3231_t driver)
{
free(driver);
}
DS3231_API int ds3231_initialize(ds3231_t driver, int *opt_out_osf)
{
if (driver == NULL)
{
errno = EINVAL;
return -1;
}
// Read status regıister
uint8_t status;
if (ds3231_io_read(driver->port, DS3231_REGISTER_STATUS, &status, sizeof status))
{
errno = EIO;
return -1;
}
if (opt_out_osf)
{
*opt_out_osf = !!(status & DS3231_STATUS_OSF);
}
driver->flags |= F_INIT;
int err;
{
// Wait until busy flag clears.
ds3231_status_t status;
while(!(err = ds3231_getStatus(driver, &status)) && (status & DS3231_STATUS_BSY))
{
vTaskDelay(pdMS_TO_TICKS(10));
}
}
return err;
}
DS3231_API int ds3231_setSquareWaveOutput(ds3231_t driver, ds3231_rate_t rate, int battery_backed)
{
ASSERT_DRV();
uint8_t control = battery_backed ? DS3231_CONTROL_BBSQW : 0;
switch (rate)
{
default:
errno = EINVAL;
return -1;
case DS3231_RATE_1HZ:
control |= DS3231_CONTROL_RS_1HZ;
break;
case DS3231_RATE_1024HZ:
control |= DS3231_CONTROL_RS_1024HZ;
break;
case DS3231_RATE_4096HZ:
control |= DS3231_CONTROL_RS_4096HZ;
break;
case DS3231_RATE_8192HZ:
control |= DS3231_CONTROL_RS_8192HZ;
break;
}
if (ds3231_io_write(driver->port, DS3231_REGISTER_CONTROL, &control, sizeof control))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_setInterrupt(ds3231_t driver, int alarm1, int alarm2)
{
ASSERT_DRV();
uint8_t control =
DS3231_CONTROL_INTCN |
alarm1 ? DS3231_CONTROL_A1IE : 0 |
alarm2 ? DS3231_CONTROL_A2IE : 0;
if (ds3231_io_write(driver->port, DS3231_REGISTER_CONTROL, &control, sizeof control))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_getStatus(ds3231_t driver, ds3231_status_t *out_status)
{
ASSERT_DRV();
if (out_status == NULL)
{
errno = EINVAL;
return -1;
}
ds3231_status_t rstatus;
if (ds3231_io_read(driver->port, DS3231_REGISTER_STATUS, &rstatus, sizeof rstatus))
{
errno = EIO;
return -1;
}
*out_status = (ds3231_status_t)rstatus;
return 0;
}
DS3231_API int ds3231_getAgingOffset(ds3231_t driver, int8_t *out_offset)
{
ASSERT_DRV();
if (out_offset == NULL)
{
errno = EINVAL;
return -1;
}
if (ds3231_io_read(driver->port, DS3231_REGISTER_OFFSET, out_offset, sizeof *out_offset))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_setAgingOffset(ds3231_t driver, int8_t offset)
{
ASSERT_DRV();
if (ds3231_io_write(driver->port, DS3231_REGISTER_OFFSET, &offset, sizeof offset))
{
errno = EIO;
return -1;
}
return 0;
}
inline static void ds3231_fill_yday(struct tm *time)
{
time->tm_yday = time->tm_mday;
switch(time->tm_mon)
{
case 0: // Jan
break;
case 1: // Feb
time->tm_yday += 31; break;
case 2: // Mar
time->tm_yday += 59; break;
case 3: // Apr
time->tm_yday += 90; break;
case 4: // May
time->tm_yday += 120; break;
case 5: // Jun
time->tm_yday += 151; break;
case 6: // Jul
time->tm_yday += 181; break;
case 7: // Aug
time->tm_yday += 212; break;
case 8: // Sep
time->tm_yday += 243; break;
case 9: // Oct
time->tm_yday += 273; break;
case 10: // Nov
time->tm_yday += 304; break;
case 11: // Dec
time->tm_yday += 334; break;
default:
break;
}
if ((time->tm_year % 4) || (time->tm_year == 2100))
{
time->tm_yday -= 1;
}
}
DS3231_API int ds3231_getTime(ds3231_t driver, struct tm *time)
{
uint8_t rtime[7];
ASSERT_DRV();
if (time == NULL)
{
errno = EINVAL;
return -1;
}
if (ds3231_io_read(driver->port, DS3231_REGISTER_SECONDS, rtime, sizeof rtime))
{
errno = EIO;
return -1;
}
time->tm_sec = BCD_TO_DEC(rtime[0]);
time->tm_min = BCD_TO_DEC(rtime[1]);
if (rtime[2] & (1 << 6))
{
uint8_t bcd = DS3231_12_TO_24(rtime[2]);
time->tm_hour = BCD_TO_DEC(bcd);
}
else
{
time->tm_hour = BCD_TO_DEC(rtime[2]);
}
time->tm_wday = rtime[3];
time->tm_mday = BCD_TO_DEC(rtime[4]);
time->tm_mon = BCD_TO_DEC((rtime[5] & 0x1F)) - 1;
time->tm_year = 2000 + BCD_TO_DEC(rtime[6]) + ((rtime[5] & (1 << 7)) ? 100 : 0);
ds3231_fill_yday(time);
return 0;
}
DS3231_API int ds3231_setTime(ds3231_t driver, const struct tm *time)
{
ASSERT_DRV();
if (time == NULL)
{
errno = EIO;
return -1;
}
uint8_t rtime[7];
rtime[0] = DEC_TO_BCD(time->tm_sec);
rtime[1] = DEC_TO_BCD(time->tm_min);
rtime[2] = DEC_TO_BCD(time->tm_hour);
rtime[3] = time->tm_wday;
rtime[4] = DEC_TO_BCD(time->tm_mday);
rtime[5] = DEC_TO_BCD(time->tm_mon + 1) + ((time->tm_year >= 2100) ? (1 << 7) : 0);
rtime[6] = DEC_TO_BCD(time->tm_year % 100);
if (ds3231_io_write(driver->port, DS3231_REGISTER_SECONDS, rtime, sizeof rtime))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_setAlarm1(ds3231_t driver, ds3231_alarmType_t type, const struct tm *time)
{
ASSERT_DRV();
if (type > DS3231_ALARM1_SMHWD || time == NULL)
{
errno = EINVAL;
return -1;
}
uint8_t rtime[4];
rtime[0] = DEC_TO_BCD(time->tm_sec);
rtime[1] = DEC_TO_BCD(time->tm_min);
rtime[2] = DEC_TO_BCD(time->tm_hour);
switch(type)
{
case DS3231_ALARM1_PERSEC:
rtime[0] |= (1 << 7);
case DS3231_ALARM1_S:
rtime[1] |= (1 << 7);
case DS3231_ALARM1_SM:
rtime[2] |= (1 << 7);
case DS3231_ALARM1_SMH:
rtime[3] = (1 << 7);
break;
case DS3231_ALARM1_SMHWD: // Week day.
rtime[3] = (1 << 6) | DEC_TO_BCD(time->tm_wday);
break;
case DS3231_ALARM1_SMHMD: // Month day.
rtime[3] = DEC_TO_BCD(time->tm_mday);
break;
}
if (ds3231_io_write(driver->port, DS3231_REGISTER_ALARM1_SECONDS, rtime, sizeof rtime))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_setAlarm2(ds3231_t driver, ds3231_alarmType_t type, const struct tm *time)
{
ASSERT_DRV();
if (type > DS3231_ALARM2_MHWD || time == NULL)
{
errno = EIO;
return -1;
}
uint8_t rtime[3];
rtime[0] = DEC_TO_BCD(time->tm_min);
rtime[1] = DEC_TO_BCD(time->tm_hour);
switch(type)
{
case DS3231_ALARM2_PREMIN:
rtime[0] |= (1 << 7);
case DS3231_ALARM2_M:
rtime[1] |= (1 << 7);
case DS3231_ALARM2_MH:
rtime[2] = (1 << 7);
break;
case DS3231_ALARM2_MHMD:
rtime[2] = DEC_TO_BCD(time->tm_mday);
break;
case DS3231_ALARM2_MHWD:
rtime[2] = (1 << 6) | DEC_TO_BCD(time->tm_wday);
break;
default:
errno = EINVAL;
return -1;
}
if (ds3231_io_write(driver->port, DS3231_REGISTER_ALARM2_MINUTES, rtime, sizeof rtime))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_beginTemperature(ds3231_t driver)
{
ASSERT_DRV();
uint8_t control;
if (ds3231_io_read(driver->port, DS3231_REGISTER_CONTROL, &control, sizeof control))
{
errno = EIO;
return -1;
}
control |= DS3231_CONTROL_CONV;
if (ds3231_io_write(driver->port, DS3231_REGISTER_CONTROL, &control, sizeof control))
{
errno = EIO;
return -1;
}
return 0;
}
DS3231_API int ds3231_endTemperature(ds3231_t driver, int16_t *out_temperature)
{
ASSERT_DRV();
if (out_temperature == NULL)
{
errno = EIO;
return -1;
}
uint8_t status = 0xFF;
uint8_t tempr[2];
while (
!ds3231_io_read(driver->port, DS3231_REGISTER_CONTROL, &status, sizeof status) &&
(status & DS3231_RSTATUS_BSY)
)
{
vTaskDelay(pdMS_TO_TICKS(10));
status = 0xFF;
}
if (
(status & DS3231_RSTATUS_BSY) ||
ds3231_io_read(driver->port, DS3231_REGISTER_TEMP_H, tempr, sizeof tempr)
)
{
errno = EIO;
return -1;
}
*out_temperature = (int16_t)((tempr[0] << 8) | (tempr[1]));
*out_temperature >>= 6;
return 0;
}

52
source/io.c Normal file

@ -0,0 +1,52 @@
#include "private.h"
#define DS3231_ADDR 0xD0
esp_err_t ds3231_io_read(i2c_port_t port, uint8_t addr, void *data, size_t sz)
{
i2c_cmd_handle_t cmd;
esp_err_t retval;
if ((cmd = i2c_cmd_link_create()))
{
i2c_master_start(cmd);
i2c_master_write_byte(cmd, DS3231_ADDR | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, addr, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, DS3231_ADDR | I2C_MASTER_READ, true);
i2c_master_read(cmd, data, sz, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
retval = i2c_master_cmd_begin(port, cmd, 20);
i2c_cmd_link_delete(cmd);
return retval;
}
return ESP_ERR_NO_MEM;
}
esp_err_t ds3231_io_write(i2c_port_t port, uint8_t addr, const void *data, size_t sz)
{
i2c_cmd_handle_t cmd;
esp_err_t retval;
if ((cmd = i2c_cmd_link_create()))
{
for (int i = 0; i < sz; ++ i)
{
i2c_master_start(cmd);
i2c_master_write_byte(cmd, DS3231_ADDR | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, addr, true);
i2c_master_write_byte(cmd, ((uint8_t*)data)[i], true);
}
i2c_master_stop(cmd);
retval = i2c_master_cmd_begin(port, cmd, 20);
i2c_cmd_link_delete(cmd);
return retval;
}
return ESP_ERR_NO_MEM;
}

79
source/private.h Normal file

@ -0,0 +1,79 @@
#ifndef _DS3231_PRIVATE_H_
#define _DS3231_PRIVATE_H_
#include <ds3231.h>
#include "registers.h"
#define BCD_TO_DEC(bcd) ((((bcd) >> 4) * 10) + (bcd & 0x0F))
#define DEC_TO_BCD(dec) ((((dec) / 10) << 4) | (dec % 10))
inline static uint8_t DS3231_12_TO_24(uint8_t _12)
{
// This lookup table brought to you by maxim.
// Thanks really thanks.
switch (_12 & 0x3F)
{
default: return 0xFF;
case 0x12: return 0x00;
case 0x01: return 0x01;
case 0x02: return 0x02;
case 0x03: return 0x03;
case 0x04: return 0x04;
case 0x05: return 0x05;
case 0x06: return 0x06;
case 0x07: return 0x07;
case 0x08: return 0x08;
case 0x09: return 0x09;
case 0x10: return 0x10;
case 0x11: return 0x11;
case 0x32: return 0x12;
case 0x21: return 0x13;
case 0x22: return 0x14;
case 0x23: return 0x15;
case 0x24: return 0x16;
case 0x25: return 0x17;
case 0x26: return 0x18;
case 0x27: return 0x19;
case 0x28: return 0x20;
case 0x29: return 0x21;
case 0x30: return 0x22;
case 0x31: return 0x23;
}
}
inline static uint8_t DS3231_24_TO_12(uint8_t _24)
{
switch (_24 & 0x3F)
{
default: return 0xFF;
case 0x00: return 0x12;
case 0x01: return 0x01;
case 0x02: return 0x02;
case 0x03: return 0x03;
case 0x04: return 0x04;
case 0x05: return 0x05;
case 0x06: return 0x06;
case 0x07: return 0x07;
case 0x08: return 0x08;
case 0x09: return 0x09;
case 0x10: return 0x10;
case 0x11: return 0x11;
case 0x12: return 0x32;
case 0x13: return 0x21;
case 0x14: return 0x22;
case 0x15: return 0x23;
case 0x16: return 0x24;
case 0x17: return 0x25;
case 0x18: return 0x26;
case 0x19: return 0x27;
case 0x20: return 0x28;
case 0x21: return 0x29;
case 0x22: return 0x30;
case 0x23: return 0x31;
}
}
esp_err_t ds3231_io_read(i2c_port_t port, uint8_t addr, void *data, size_t sz);
esp_err_t ds3231_io_write(i2c_port_t port, uint8_t addr, const void *data, size_t sz);
#endif

48
source/registers.h Normal file

@ -0,0 +1,48 @@
#ifndef _DS3231_REGISTERS_H_
#define _DS3231_REGISTERS_H_
#define DS3231_REGISTER_SECONDS (0x00)
#define DS3231_REGISTER_MINUTES (0x01)
#define DS3231_REGISTER_HOURS (0x02)
#define DS3231_REGISTER_DAY (0x03)
#define DS3231_REGISTER_DATE (0x04)
#define DS3231_REGISTER_MONTH (0x05)
#define DS3231_REGISTER_YEAR (0x06)
#define DS3231_REGISTER_ALARM1_SECONDS (0x07)
#define DS3231_REGISTER_ALARM1_MINUTES (0x08)
#define DS3231_REGISTER_ALARM1_HOURS (0x09)
#define DS3231_REGISTER_ALARM1_DAY_DATE (0x0A)
#define DS3231_REGISTER_ALARM2_MINUTES (0x0B)
#define DS3231_REGISTER_ALARM2_HOURS (0x0C)
#define DS3231_REGISTER_ALARM2_DAY_DATE (0x0D)
#define DS3231_REGISTER_CONTROL (0x0E)
#define DS3231_REGISTER_STATUS (0x0F)
#define DS3231_REGISTER_OFFSET (0x10)
#define DS3231_REGISTER_TEMP_H (0x11)
#define DS3231_REGISTER_TEMP_L (0x12)
#define DS3231_HOURS_12H_BIT (1 << 6)
#define DS3231_DAY_DATE_BIT (1 << 6)
#define DS3231_CENTURY_BIT (1 << 7)
#define DS3231_ALARM_EN_BIT (1 << 7)
#define DS3231_CONTROL_EOSC (1 << 7)
#define DS3231_CONTROL_BBSQW (1 << 6)
#define DS3231_CONTROL_CONV (1 << 5)
#define DS3231_CONTROL_RS_POS 3
#define DS3231_CONTROL_RS_MASK (3 << DS3231_CONTROL_RS_POS)
#define DS3231_CONTROL_RS_1HZ (0 << DS3231_CONTROL_RS_POS)
#define DS3231_CONTROL_RS_1024HZ (1 << DS3231_CONTROL_RS_POS)
#define DS3231_CONTROL_RS_4096HZ (2 << DS3231_CONTROL_RS_POS)
#define DS3231_CONTROL_RS_8192HZ (3 << DS3231_CONTROL_RS_POS)
#define DS3231_CONTROL_INTCN (1 << 2)
#define DS3231_CONTROL_A2IE (1 << 1)
#define DS3231_CONTROL_A1IE (1 << 0)
#define DS3231_RSTATUS_OSF (1 << 7)
#define DS3231_RSTATUS_EN32KHZ (1 << 3)
#define DS3231_RSTATUS_BSY (1 << 2)
#define DS3231_RSTATUS_A2F (1 << 1)
#define DS3231_RSTATUS_A1F (1 << 0)
#endif