diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..19bad7a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRCS + "source/io.c" + "source/driver.c" +) \ No newline at end of file diff --git a/include/ds3231.h b/include/ds3231.h new file mode 100644 index 0000000..ae7fc6e --- /dev/null +++ b/include/ds3231.h @@ -0,0 +1,89 @@ +#ifndef _ESP_IDF_DS3231_H_ +#define _ESP_IDF_DS3231_H_ + +#define DS3231_API + +#include +#include +#include + +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 diff --git a/source/driver.c b/source/driver.c new file mode 100644 index 0000000..107e11b --- /dev/null +++ b/source/driver.c @@ -0,0 +1,426 @@ +#include "private.h" +#include + +#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; +} diff --git a/source/io.c b/source/io.c new file mode 100644 index 0000000..d2abd45 --- /dev/null +++ b/source/io.c @@ -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; +} diff --git a/source/private.h b/source/private.h new file mode 100644 index 0000000..e5615cc --- /dev/null +++ b/source/private.h @@ -0,0 +1,79 @@ +#ifndef _DS3231_PRIVATE_H_ +#define _DS3231_PRIVATE_H_ + +#include +#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 diff --git a/source/registers.h b/source/registers.h new file mode 100644 index 0000000..4402c26 --- /dev/null +++ b/source/registers.h @@ -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