├── CMakeLists.txt ├── examples └── basic_usage │ ├── main │ ├── CMakeLists.txt │ └── main.cpp │ └── CMakeLists.txt ├── idf_component.yml ├── LICENSE ├── include └── RV3032.h ├── README.md └── src └── RV3032.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "src/RV3032.c" 3 | INCLUDE_DIRS "include" 4 | REQUIRES driver esp_timer freertos 5 | ) 6 | -------------------------------------------------------------------------------- /examples/basic_usage/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "." 4 | REQUIRES RV3032_ESP32 5 | ) 6 | 7 | # MAKE SURE ROOT DIRECTORY IS CALLED RV3032_ESP32 8 | -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | version: "0.1.0" 2 | name: "RV3032" 3 | description: "ESP-IDF driver for RV-3032-C7 Real-Time Clock" 4 | url: "https://github.com/jonasschnelli/RV3032_ESP32" 5 | documentation: "https://github.com/jonasschnelli/RV3032_ESP32/blob/main/README.md" 6 | license: "MIT" 7 | 8 | dependencies: 9 | idf: 10 | version: ">=5.5.0" 11 | 12 | targets: 13 | - esp32 14 | - esp32s2 15 | - esp32s3 16 | - esp32c3 17 | - esp32c6 18 | - esp32h2 19 | 20 | tags: 21 | - rtc 22 | - real-time-clock 23 | - i2c 24 | - time 25 | -------------------------------------------------------------------------------- /examples/basic_usage/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists.txt. In this file we just import the global project.cmake 3 | # file which takes care of the rest. 4 | 5 | cmake_minimum_required(VERSION 3.16) 6 | 7 | # Set default target to ESP32-S3 8 | if(NOT DEFINED IDF_TARGET) 9 | set(IDF_TARGET esp32s3) 10 | endif() 11 | 12 | # Set the components to include the components from the parent directory 13 | set(EXTRA_COMPONENT_DIRS "../../") 14 | 15 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 16 | project(rv3032_basic_usage) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jonas Schnelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/RV3032.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file RV3032.h 3 | * @brief ESP-IDF 5.5 library for RV-3032-C7 Real-Time Clock Module 4 | * @author Jonas Schnelli 5 | * @version 1.0 6 | * 7 | * This library provides basic functionality for the RV-3032-C7 RTC: 8 | * - Time/Date reading and setting 9 | * - Alarm functionality 10 | * - Optimized for ESP32 platform with ESP-IDF 5.5 11 | * 12 | * Based on RV-3032-C7 Application Manual Rev. 1.3 (May 2023) 13 | */ 14 | 15 | #ifndef RV3032_H 16 | #define RV3032_H 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include "esp_err.h" 25 | #include "driver/i2c.h" 26 | #include "driver/gpio.h" 27 | #include "freertos/FreeRTOS.h" 28 | #include "freertos/task.h" 29 | 30 | // RV3032 I2C Address 31 | #define RV3032_I2C_ADDR 0x51 // 7-bit address (1010001b) 32 | 33 | // Register Addresses - Time/Date 34 | #define RV3032_REG_HUNDREDTHS 0x00 // 100th seconds (read-only) 35 | #define RV3032_REG_SECONDS 0x01 // Seconds (00-59) 36 | #define RV3032_REG_MINUTES 0x02 // Minutes (00-59) 37 | #define RV3032_REG_HOURS 0x03 // Hours (00-23) 38 | #define RV3032_REG_WEEKDAY 0x04 // Weekday (0-6) 39 | #define RV3032_REG_DATE 0x05 // Date (01-31) 40 | #define RV3032_REG_MONTH 0x06 // Month (01-12) 41 | #define RV3032_REG_YEAR 0x07 // Year (00-99) 42 | 43 | // Register Addresses - Alarm 44 | #define RV3032_REG_MINUTES_ALARM 0x08 // Minutes alarm + enable bit 45 | #define RV3032_REG_HOURS_ALARM 0x09 // Hours alarm + enable bit 46 | #define RV3032_REG_DATE_ALARM 0x0A // Date alarm + enable bit 47 | 48 | // Register Addresses - Control/Status 49 | #define RV3032_REG_STATUS 0x0D // Status register 50 | #define RV3032_REG_CONTROL2 0x11 // Control 2 register 51 | 52 | // Alarm Enable Bits (bit 7 of each alarm register) 53 | #define RV3032_ALARM_ENABLE_BIT 0x00 // 0 = enabled, 1 = disabled 54 | #define RV3032_ALARM_DISABLE_BIT 0x80 55 | 56 | // Status Register Bits 57 | #define RV3032_STATUS_AF 0x08 // Alarm Flag (bit 3) 58 | 59 | // Control 2 Register Bits 60 | #define RV3032_CTRL2_AIE 0x08 // Alarm Interrupt Enable (bit 3) 61 | 62 | // I2C Configuration 63 | #define RV3032_I2C_MASTER_NUM I2C_NUM_0 64 | #define RV3032_I2C_MASTER_TIMEOUT_MS 1000 65 | 66 | /** 67 | * @brief Structure to hold time/date information 68 | */ 69 | typedef struct { 70 | uint8_t year; // 0-99 (represents 2000-2099) 71 | uint8_t month; // 1-12 72 | uint8_t date; // 1-31 73 | uint8_t weekday; // 0-6 (user defined) 74 | uint8_t hours; // 0-23 75 | uint8_t minutes; // 0-59 76 | uint8_t seconds; // 0-59 77 | uint8_t hundredths; // 0-99 (read-only) 78 | } rv3032_datetime_t; 79 | 80 | /** 81 | * @brief Structure to hold alarm information 82 | */ 83 | typedef struct { 84 | uint8_t date; // 1-31 (0 = disabled) 85 | uint8_t hours; // 0-23 (255 = disabled) 86 | uint8_t minutes; // 0-59 (255 = disabled) 87 | bool date_enable; // Enable date matching 88 | bool hours_enable; // Enable hours matching 89 | bool minutes_enable; // Enable minutes matching 90 | } rv3032_alarm_t; 91 | 92 | /** 93 | * @brief RV3032 configuration structure 94 | */ 95 | typedef struct { 96 | i2c_port_t i2c_port; // I2C port number 97 | gpio_num_t int_pin; // Interrupt pin (GPIO_NUM_NC if not used) 98 | void (*alarm_callback)(void); // Alarm callback function 99 | bool initialized; // Initialization status 100 | } rv3032_handle_t; 101 | 102 | /** 103 | * @brief Initialize the RV3032 RTC 104 | * @param handle Pointer to RV3032 handle structure 105 | * @param int_pin GPIO pin for interrupt (GPIO_NUM_NC if not used) 106 | * @param alarm_callback Callback function for alarm interrupt (NULL if not used) 107 | * @return ESP_OK on success, error code otherwise 108 | * @note Assumes I2C is already initialized by the main application 109 | */ 110 | esp_err_t rv3032_init(rv3032_handle_t* handle, 111 | gpio_num_t int_pin, 112 | void (*alarm_callback)(void)); 113 | 114 | /** 115 | * @brief Deinitialize the RV3032 RTC 116 | * @param handle Pointer to RV3032 handle structure 117 | * @return ESP_OK on success, error code otherwise 118 | */ 119 | esp_err_t rv3032_deinit(rv3032_handle_t* handle); 120 | 121 | // === Time/Date Functions === 122 | 123 | /** 124 | * @brief Read current date and time 125 | * @param handle Pointer to RV3032 handle structure 126 | * @param dt Pointer to datetime structure to fill 127 | * @return ESP_OK on success, error code otherwise 128 | */ 129 | esp_err_t rv3032_get_datetime(rv3032_handle_t* handle, rv3032_datetime_t* dt); 130 | 131 | /** 132 | * @brief Set date and time 133 | * @param handle Pointer to RV3032 handle structure 134 | * @param dt Pointer to datetime structure with values to set 135 | * @return ESP_OK on success, error code otherwise 136 | */ 137 | esp_err_t rv3032_set_datetime(rv3032_handle_t* handle, const rv3032_datetime_t* dt); 138 | 139 | /** 140 | * @brief Get current Unix timestamp (seconds since Jan 1, 2000) 141 | * @param handle Pointer to RV3032 handle structure 142 | * @param unix_time Pointer to store Unix timestamp 143 | * @return ESP_OK on success, error code otherwise 144 | */ 145 | esp_err_t rv3032_get_unix_time(rv3032_handle_t* handle, uint32_t* unix_time); 146 | 147 | /** 148 | * @brief Set time using Unix timestamp (seconds since Jan 1, 2000) 149 | * @param handle Pointer to RV3032 handle structure 150 | * @param unix_time Unix timestamp 151 | * @return ESP_OK on success, error code otherwise 152 | */ 153 | esp_err_t rv3032_set_unix_time(rv3032_handle_t* handle, uint32_t unix_time); 154 | 155 | // === Alarm Functions === 156 | 157 | /** 158 | * @brief Set alarm 159 | * @param handle Pointer to RV3032 handle structure 160 | * @param alarm Pointer to alarm structure with settings 161 | * @param enable_interrupt Enable alarm interrupt on INT pin 162 | * @return ESP_OK on success, error code otherwise 163 | */ 164 | esp_err_t rv3032_set_alarm(rv3032_handle_t* handle, 165 | const rv3032_alarm_t* alarm, 166 | bool enable_interrupt); 167 | 168 | /** 169 | * @brief Get current alarm settings 170 | * @param handle Pointer to RV3032 handle structure 171 | * @param alarm Pointer to alarm structure to fill 172 | * @return ESP_OK on success, error code otherwise 173 | */ 174 | esp_err_t rv3032_get_alarm(rv3032_handle_t* handle, rv3032_alarm_t* alarm); 175 | 176 | /** 177 | * @brief Clear alarm flag and disable alarm 178 | * @param handle Pointer to RV3032 handle structure 179 | * @return ESP_OK on success, error code otherwise 180 | */ 181 | esp_err_t rv3032_clear_alarm(rv3032_handle_t* handle); 182 | 183 | /** 184 | * @brief Check if alarm has triggered 185 | * @param handle Pointer to RV3032 handle structure 186 | * @param triggered Pointer to store alarm status 187 | * @return ESP_OK on success, error code otherwise 188 | */ 189 | esp_err_t rv3032_is_alarm_triggered(rv3032_handle_t* handle, bool* triggered); 190 | 191 | // === Utility Functions === 192 | 193 | /** 194 | * @brief Check if RTC is responding 195 | * @param handle Pointer to RV3032 handle structure 196 | * @param connected Pointer to store connection status 197 | * @return ESP_OK on success, error code otherwise 198 | */ 199 | esp_err_t rv3032_is_connected(rv3032_handle_t* handle, bool* connected); 200 | 201 | #ifdef __cplusplus 202 | } 203 | #endif 204 | 205 | #endif // RV3032_H -------------------------------------------------------------------------------- /examples/basic_usage/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "driver/i2c.h" 2 | #include "esp_log.h" 3 | 4 | // Define pins 5 | #define LED_RED_PIN 20 6 | #define LED_GREEN_PIN 19 7 | #define LED_BLUE_PIN 18 8 | #define POWER_STATE_PIN 15 9 | #define POWER_HOLD_PIN 6 10 | #define RV3032_INT_PIN 2 11 | 12 | #define I2C_MASTER_NUM I2C_NUM_0 13 | #define I2C_MASTER_SCL_IO 40 14 | #define I2C_MASTER_SDA_IO 39 15 | #define I2C_MASTER_FREQ_HZ 100000 16 | #define I2C_ADDR_RV3032 0x51 // 7-bit address 17 | 18 | #define RV3032_REG_SECONDS 0x01 19 | #define RV3032_REG_MINUTES 0x02 20 | #define RV3032_REG_HOURS 0x03 21 | #define RV3032_REG_ALM_MIN 0x08 22 | #define RV3032_REG_ALM_HR 0x09 23 | #define RV3032_REG_ALM_DATE 0x0A 24 | #define RV3032_REG_CTRL2 0x11 25 | #define RV3032_REG_STATUS 0x0D 26 | 27 | static const char* TAG = "power_rtc"; 28 | 29 | extern "C" { 30 | void app_main(void); 31 | } 32 | 33 | volatile bool rtc_alarm_triggered = false; 34 | 35 | extern "C" void IRAM_ATTR rtc_int_isr_handler(void* arg) { 36 | rtc_alarm_triggered = true; 37 | } 38 | 39 | // Initialize the LED GPIOs 40 | void led_init(void) { 41 | // Configure LED pins 42 | gpio_config_t led_conf = {}; 43 | led_conf.mode = GPIO_MODE_OUTPUT; 44 | led_conf.pin_bit_mask = (1ULL << LED_RED_PIN) | (1ULL << LED_GREEN_PIN) | (1ULL << LED_BLUE_PIN); 45 | led_conf.intr_type = GPIO_INTR_DISABLE; 46 | led_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 47 | led_conf.pull_up_en = GPIO_PULLUP_DISABLE; 48 | gpio_config(&led_conf); 49 | 50 | // Configure power state pin as input 51 | gpio_config_t power_conf = {}; 52 | power_conf.mode = GPIO_MODE_INPUT; 53 | power_conf.pin_bit_mask = (1ULL << POWER_STATE_PIN); 54 | power_conf.intr_type = GPIO_INTR_DISABLE; 55 | power_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 56 | power_conf.pull_up_en = GPIO_PULLUP_ENABLE; 57 | gpio_config(&power_conf); 58 | 59 | // Turn off all LEDs initially 60 | gpio_set_level((gpio_num_t)LED_RED_PIN, 0); 61 | gpio_set_level((gpio_num_t)LED_GREEN_PIN, 0); 62 | gpio_set_level((gpio_num_t)LED_BLUE_PIN, 0); 63 | 64 | ESP_LOGI(TAG, "GPIOs initialized"); 65 | } 66 | 67 | // Set RGB LED color (1 = on, 0 = off for each component) 68 | void led_set_color(int red, int green, int blue) { 69 | gpio_set_level((gpio_num_t)LED_RED_PIN, red ? 1 : 0); 70 | gpio_set_level((gpio_num_t)LED_GREEN_PIN, green ? 1 : 0); 71 | gpio_set_level((gpio_num_t)LED_BLUE_PIN, blue ? 1 : 0); 72 | } 73 | 74 | // Read power state and update LED 75 | void update_power_indicator(void) { 76 | // Read power state pin (HIGH = USB, LOW = Battery) 77 | int power_state = gpio_get_level((gpio_num_t)POWER_STATE_PIN); 78 | 79 | if (power_state) { 80 | // USB power (blue) 81 | led_set_color(0, 0, 1); 82 | ESP_LOGI(TAG, "Power source: USB"); 83 | } else { 84 | // Battery power (red) 85 | led_set_color(1, 0, 0); 86 | ESP_LOGI(TAG, "Power source: Battery"); 87 | } 88 | } 89 | 90 | void i2c_master_init() { 91 | i2c_config_t conf; 92 | conf.mode = I2C_MODE_MASTER; 93 | conf.sda_io_num = I2C_MASTER_SDA_IO; 94 | conf.scl_io_num = I2C_MASTER_SCL_IO; 95 | conf.sda_pullup_en = GPIO_PULLUP_ENABLE; 96 | conf.scl_pullup_en = GPIO_PULLUP_ENABLE; 97 | conf.master.clk_speed = I2C_MASTER_FREQ_HZ; 98 | conf.clk_flags = 0; 99 | 100 | ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf)); 101 | ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0)); 102 | } 103 | 104 | // Convert decimal to BCD 105 | static uint8_t dec_to_bcd(uint8_t val) { 106 | return ((val / 10) << 4) | (val % 10); 107 | } 108 | 109 | // Write 1 byte to RV-3032 110 | esp_err_t rv3032_write(uint8_t reg, uint8_t val) { 111 | return i2c_master_write_to_device(I2C_MASTER_NUM, I2C_ADDR_RV3032, (uint8_t[]){reg, val}, 2, 100 / portTICK_PERIOD_MS); 112 | } 113 | 114 | // Read 1 byte from RV-3032 115 | esp_err_t rv3032_read(uint8_t reg, uint8_t *val) { 116 | return i2c_master_write_read_device(I2C_MASTER_NUM, I2C_ADDR_RV3032, ®, 1, val, 1, 100 / portTICK_PERIOD_MS); 117 | } 118 | 119 | // Set alarm 30s in the future 120 | void rv3032_set_alarm_30s_later() { 121 | uint8_t sec, min, hr; 122 | rv3032_read(RV3032_REG_SECONDS, &sec); 123 | rv3032_read(RV3032_REG_MINUTES, &min); 124 | rv3032_read(RV3032_REG_HOURS, &hr); 125 | 126 | sec &= 0x7F; 127 | min &= 0x7F; 128 | hr &= 0x3F; 129 | 130 | uint8_t dec_sec = ((sec >> 4) * 10) + (sec & 0x0F); 131 | uint8_t dec_min = ((min >> 4) * 10) + (min & 0x0F); 132 | uint8_t dec_hr = ((hr >> 4) * 10) + (hr & 0x0F); 133 | 134 | // Add 30 seconds 135 | dec_sec += 60; 136 | if (dec_sec >= 60) { 137 | dec_sec -= 60; 138 | dec_min += 1; 139 | if (dec_min >= 60) { 140 | dec_min = 0; 141 | dec_hr = (dec_hr + 1) % 24; 142 | } 143 | } 144 | 145 | uint8_t bcd_min = dec_to_bcd(dec_min); 146 | uint8_t bcd_hr = dec_to_bcd(dec_hr); 147 | 148 | // Set alarm for that minute/hour, ignore date 149 | rv3032_write(RV3032_REG_ALM_MIN, bcd_min & 0x7F); // AE_M = 0 150 | rv3032_write(RV3032_REG_ALM_HR, bcd_hr & 0x7F); // AE_H = 0 151 | rv3032_write(RV3032_REG_ALM_DATE, 0x80); // AE_D = 1 → disable date 152 | 153 | // Enable alarm interrupt 154 | uint8_t ctrl2; 155 | rv3032_read(RV3032_REG_CTRL2, &ctrl2); 156 | ctrl2 |= (1 << 3); // Set AIE 157 | rv3032_write(RV3032_REG_CTRL2, ctrl2); 158 | 159 | ESP_LOGI("RTC", "Alarm set for %02d:%02d", dec_hr, dec_min); 160 | } 161 | 162 | 163 | 164 | // Clear alarm flag manually 165 | void rv3032_clear_alarm_flag() { 166 | uint8_t status; 167 | rv3032_read(RV3032_REG_STATUS, &status); 168 | status &= ~(1 << 3); // Clear AF bit (bit 3) 169 | rv3032_write(RV3032_REG_STATUS, status); 170 | ESP_LOGI("RTC", "Alarm flag cleared."); 171 | } 172 | 173 | void rtc_int_gpio_init() { 174 | gpio_config_t io_conf = {}; 175 | io_conf.intr_type = GPIO_INTR_NEGEDGE; // Falling edge = INT̅ active 176 | io_conf.mode = GPIO_MODE_INPUT; 177 | io_conf.pin_bit_mask = (1ULL << RV3032_INT_PIN); 178 | io_conf.pull_up_en = GPIO_PULLUP_DISABLE; // You already have R45 external 179 | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 180 | ESP_ERROR_CHECK(gpio_config(&io_conf)); 181 | 182 | ESP_LOGI("RTC", "Installing ISR service..."); 183 | ESP_ERROR_CHECK(gpio_install_isr_service(0)); 184 | 185 | ESP_LOGI("RTC", "Attaching ISR handler..."); 186 | ESP_ERROR_CHECK(gpio_isr_handler_add((gpio_num_t)RV3032_INT_PIN, rtc_int_isr_handler, NULL)); 187 | } 188 | 189 | void rv3032_debug_status() { 190 | uint8_t sec, min, hr, alm_min, alm_hr, alm_date; 191 | uint8_t ctrl2, status; 192 | 193 | rv3032_read(RV3032_REG_SECONDS, &sec); 194 | rv3032_read(RV3032_REG_MINUTES, &min); 195 | rv3032_read(RV3032_REG_HOURS, &hr); 196 | 197 | rv3032_read(RV3032_REG_ALM_MIN, &alm_min); 198 | rv3032_read(RV3032_REG_ALM_HR, &alm_hr); 199 | rv3032_read(RV3032_REG_ALM_DATE, &alm_date); 200 | 201 | rv3032_read(RV3032_REG_CTRL2, &ctrl2); 202 | rv3032_read(RV3032_REG_STATUS, &status); 203 | 204 | int int_level = gpio_get_level((gpio_num_t)RV3032_INT_PIN); 205 | 206 | ESP_LOGI("RTC-DEBUG", "Current time: %02x:%02x:%02x", hr, min, sec); 207 | ESP_LOGI("RTC-DEBUG", "Alarm set: %02x:%02x date: 0x%02x", alm_hr, alm_min, alm_date); 208 | ESP_LOGI("RTC-DEBUG", "CTRL2: 0x%02x (AIE=%d)", ctrl2, (ctrl2 >> 3) & 1); 209 | ESP_LOGI("RTC-DEBUG", "STATUS: 0x%02x (AF=%d)", status, (status >> 3) & 1); 210 | ESP_LOGI("RTC-DEBUG", "INT̅ pin level: %d (should be LOW when asserted)", int_level); 211 | } 212 | 213 | void app_main(void) { 214 | // IMPORTANT: Set power hold HIGH immediately 215 | gpio_reset_pin((gpio_num_t)POWER_HOLD_PIN); 216 | gpio_set_direction((gpio_num_t)POWER_HOLD_PIN, GPIO_MODE_OUTPUT); 217 | gpio_set_level((gpio_num_t)POWER_HOLD_PIN, 1); 218 | 219 | // Initialize LED pins 220 | led_init(); 221 | 222 | i2c_master_init(); 223 | 224 | rtc_int_gpio_init(); 225 | 226 | rv3032_clear_alarm_flag(); 227 | // Initial test sequence 228 | ESP_LOGI(TAG, "Starting power and RTC monitor"); 229 | 230 | // Blink all colors briefly to show the system is starting 231 | led_set_color(1, 0, 0); // Red 232 | vTaskDelay(pdMS_TO_TICKS(300)); 233 | 234 | rv3032_set_alarm_30s_later(); 235 | 236 | vTaskDelay(pdMS_TO_TICKS(1000)); 237 | gpio_set_level((gpio_num_t)POWER_HOLD_PIN, 0); 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RV3032 RTC Library for ESP-IDF 5.5 2 | 3 | A lightweight, ESP-IDF 5.5 native C library for the **RV-3032-C7** Temperature Compensated Real-Time Clock Module. 4 | 5 | ## Features 6 | 7 | - ✅ **ESP-IDF 5.5 Native**: Uses latest ESP-IDF I2C master driver and GPIO APIs 8 | - ✅ **Time/Date Management**: Read and set date/time with high precision 9 | - ✅ **Alarm Functionality**: Flexible alarm system with interrupt support 10 | - ✅ **Unix Timestamp**: Convert between RTC time and Unix timestamps 11 | - ✅ **Memory Efficient**: Minimal RAM footprint and efficient I2C communication 12 | - ✅ **Thread Safe**: Uses ESP-IDF synchronization primitives 13 | - ✅ **Well Documented**: Comprehensive documentation and examples 14 | - ✅ **Extensible Design**: Easy to add more RTC features later 15 | 16 | ## Hardware Requirements 17 | 18 | - ESP32 series microcontroller 19 | - RV-3032-C7 RTC module 20 | - Pull-up resistors for I2C (usually built into ESP32 boards) 21 | - Optional: Pull-up resistor for INT pin (10kΩ recommended) 22 | 23 | ## Documentation 24 | 25 | 📖 **[RV-3032-C7 Application Manual](https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3032-C7_App-Manual.pdf)** - Official datasheet and technical reference 26 | 27 | ## Quick Start 28 | 29 | ```c 30 | #include 31 | #include "freertos/FreeRTOS.h" 32 | #include "freertos/task.h" 33 | #include "driver/i2c_master.h" 34 | #include "driver/gpio.h" 35 | #include "esp_log.h" 36 | #include "RV3032.h" 37 | 38 | static i2c_master_bus_handle_t i2c_bus_handle; 39 | static rv3032_handle_t rtc_handle; 40 | 41 | // Initialize I2C bus 42 | static esp_err_t init_i2c(void) { 43 | i2c_master_bus_config_t i2c_config = { 44 | .clk_source = I2C_CLK_SRC_DEFAULT, 45 | .i2c_port = -1, 46 | .scl_io_num = GPIO_NUM_22, 47 | .sda_io_num = GPIO_NUM_21, 48 | .glitch_ignore_cnt = 7, 49 | .flags.enable_internal_pullup = true, 50 | }; 51 | return i2c_new_master_bus(&i2c_config, &i2c_bus_handle); 52 | } 53 | 54 | void app_main(void) { 55 | // Initialize I2C 56 | ESP_ERROR_CHECK(init_i2c()); 57 | 58 | // Initialize RTC 59 | ESP_ERROR_CHECK(rv3032_init(&rtc_handle, i2c_bus_handle, GPIO_NUM_NC, NULL)); 60 | 61 | // Set time: 2024-12-15 12:30:00 62 | rv3032_datetime_t dt = { 63 | .year = 24, .month = 12, .date = 15, 64 | .hours = 12, .minutes = 30, .seconds = 0 65 | }; 66 | rv3032_set_datetime(&rtc_handle, &dt); 67 | 68 | while (1) { 69 | rv3032_datetime_t current_time; 70 | if (rv3032_get_datetime(&rtc_handle, ¤t_time) == ESP_OK) { 71 | printf("Time: 20%02d-%02d-%02d %02d:%02d:%02d\n", 72 | current_time.year, current_time.month, current_time.date, 73 | current_time.hours, current_time.minutes, current_time.seconds); 74 | } 75 | vTaskDelay(pdMS_TO_TICKS(1000)); 76 | } 77 | } 78 | ``` 79 | 80 | ## API Reference 81 | 82 | ### Initialization Functions 83 | 84 | #### `esp_err_t rv3032_init(rv3032_handle_t* handle, i2c_master_bus_handle_t i2c_bus_handle, gpio_num_t int_pin, void (*alarm_callback)(void))` 85 | Initialize the RTC module. 86 | - `handle`: Pointer to RV3032 handle structure 87 | - `i2c_bus_handle`: ESP-IDF I2C master bus handle 88 | - `int_pin`: GPIO pin for interrupt (GPIO_NUM_NC if not used) 89 | - `alarm_callback`: Callback function for alarm interrupt (NULL if not used) 90 | - **Returns**: ESP_OK on success, error code otherwise 91 | 92 | #### `esp_err_t rv3032_deinit(rv3032_handle_t* handle)` 93 | Deinitialize the RTC and free resources. 94 | 95 | ### Time/Date Functions 96 | 97 | #### `esp_err_t rv3032_get_datetime(rv3032_handle_t* handle, rv3032_datetime_t* dt)` 98 | Read current date and time. 99 | 100 | #### `esp_err_t rv3032_set_datetime(rv3032_handle_t* handle, const rv3032_datetime_t* dt)` 101 | Set the RTC date and time. 102 | 103 | #### `esp_err_t rv3032_get_unix_time(rv3032_handle_t* handle, uint32_t* unix_time)` 104 | Get Unix timestamp (seconds since Jan 1, 2000). 105 | 106 | #### `esp_err_t rv3032_set_unix_time(rv3032_handle_t* handle, uint32_t unix_time)` 107 | Set time using Unix timestamp. 108 | 109 | ### Alarm Functions 110 | 111 | #### `esp_err_t rv3032_set_alarm(rv3032_handle_t* handle, const rv3032_alarm_t* alarm, bool enable_interrupt)` 112 | Configure alarm settings. 113 | 114 | #### `esp_err_t rv3032_get_alarm(rv3032_handle_t* handle, rv3032_alarm_t* alarm)` 115 | Read current alarm settings. 116 | 117 | #### `esp_err_t rv3032_clear_alarm(rv3032_handle_t* handle)` 118 | Clear alarm flag and disable alarm. 119 | 120 | #### `esp_err_t rv3032_is_alarm_triggered(rv3032_handle_t* handle, bool* triggered)` 121 | Check if alarm has been triggered. 122 | 123 | ### Data Structures 124 | 125 | #### `rv3032_datetime_t` 126 | ```c 127 | typedef struct { 128 | uint8_t year; // 0-99 (represents 2000-2099) 129 | uint8_t month; // 1-12 130 | uint8_t date; // 1-31 131 | uint8_t weekday; // 0-6 (user defined) 132 | uint8_t hours; // 0-23 133 | uint8_t minutes; // 0-59 134 | uint8_t seconds; // 0-59 135 | uint8_t hundredths; // 0-99 (read-only) 136 | } rv3032_datetime_t; 137 | ``` 138 | 139 | #### `rv3032_alarm_t` 140 | ```c 141 | typedef struct { 142 | uint8_t date; // 1-31 143 | uint8_t hours; // 0-23 144 | uint8_t minutes; // 0-59 145 | bool date_enable; // Enable date matching 146 | bool hours_enable; // Enable hours matching 147 | bool minutes_enable; // Enable minutes matching 148 | } rv3032_alarm_t; 149 | ``` 150 | 151 | ## Alarm Examples 152 | 153 | ### Daily Alarm (Every Day at Specific Time) 154 | ```c 155 | rv3032_alarm_t alarm = { 156 | .hours = 7, // 7:00 AM 157 | .minutes = 30, // 30 minutes 158 | .date = 1, // Ignored 159 | .hours_enable = true, 160 | .minutes_enable = true, 161 | .date_enable = false // Disable date matching 162 | }; 163 | rv3032_set_alarm(&rtc_handle, &alarm, true); 164 | ``` 165 | 166 | ### Monthly Alarm (Specific Date and Time Each Month) 167 | ```c 168 | rv3032_alarm_t alarm = { 169 | .date = 15, // 15th of each month 170 | .hours = 9, // 9:00 AM 171 | .minutes = 0, 172 | .date_enable = true, 173 | .hours_enable = true, 174 | .minutes_enable = true 175 | }; 176 | rv3032_set_alarm(&rtc_handle, &alarm, true); 177 | ``` 178 | 179 | ## Interrupt Handling 180 | 181 | ```c 182 | static volatile bool alarm_flag = false; 183 | 184 | static void alarm_callback(void) { 185 | // Called from ISR context - keep it minimal 186 | alarm_flag = true; 187 | } 188 | 189 | void app_main(void) { 190 | // Initialize with interrupt callback 191 | rv3032_init(&rtc_handle, i2c_bus_handle, GPIO_NUM_2, alarm_callback); 192 | 193 | // Set up alarm... 194 | 195 | while (1) { 196 | if (alarm_flag) { 197 | alarm_flag = false; 198 | printf("Alarm triggered!\n"); 199 | 200 | // Handle alarm event 201 | bool triggered; 202 | if (rv3032_is_alarm_triggered(&rtc_handle, &triggered) == ESP_OK && triggered) { 203 | // Process alarm 204 | rv3032_clear_alarm(&rtc_handle); // Optional: disable further alarms 205 | } 206 | } 207 | vTaskDelay(pdMS_TO_TICKS(100)); 208 | } 209 | } 210 | ``` 211 | 212 | ## C++ Compatibility 213 | 214 | If you're using a C++ file (main.cpp), you need to: 215 | 216 | 1. **Include the library with extern "C"**: 217 | ```cpp 218 | extern "C" { 219 | #include "RV3032.h" 220 | } 221 | ``` 222 | 223 | 2. **Use proper GPIO types**: 224 | ```cpp 225 | // Use GPIO_NUM_X constants instead of plain integers 226 | #define RTC_INT_PIN GPIO_NUM_2 // Not just "2" 227 | ``` 228 | 229 | 3. **Initialize structs without designated initializers**: 230 | ```cpp 231 | // C++ style initialization 232 | rv3032_datetime_t dt = {}; 233 | dt.year = 24; 234 | dt.month = 12; 235 | dt.date = 15; 236 | // ... set other fields 237 | 238 | // Instead of C99 designated initializers: 239 | // rv3032_datetime_t dt = {.year = 24, .month = 12, .date = 15}; 240 | ``` 241 | 242 | Multiple devices can share the same I2C bus: 243 | 244 | ```c 245 | // Create I2C bus once 246 | i2c_master_bus_handle_t i2c_bus; 247 | i2c_master_bus_config_t bus_config = { 248 | .clk_source = I2C_CLK_SRC_DEFAULT, 249 | .i2c_port = -1, 250 | .scl_io_num = GPIO_NUM_22, 251 | .sda_io_num = GPIO_NUM_21, 252 | .glitch_ignore_cnt = 7, 253 | .flags.enable_internal_pullup = true, 254 | }; 255 | i2c_new_master_bus(&bus_config, &i2c_bus); 256 | 257 | // Initialize RTC with shared bus 258 | rv3032_init(&rtc_handle, i2c_bus, GPIO_NUM_2, alarm_callback); 259 | 260 | // Initialize other I2C devices with the same bus... 261 | ``` 262 | 263 | ## Technical Notes 264 | 265 | ### ESP-IDF 5.5 Compatibility 266 | - Uses new `i2c_master` driver (replaces legacy `i2c` driver) 267 | - Compatible with ESP-IDF 5.5+ 268 | - Uses ESP-IDF native error handling (`esp_err_t`) 269 | - Thread-safe operations using ESP-IDF primitives 270 | 271 | ### I2C Communication 272 | - Default I2C address: `0x51` (7-bit) 273 | - Supports up to 400kHz I2C speed 274 | - Uses efficient burst read/write operations 275 | - Built-in timeout handling (1000ms default) 276 | 277 | ### Memory Usage 278 | - Minimal RAM footprint (~100 bytes per handle) 279 | - No dynamic memory allocation during normal operation 280 | - Stack-based temporary buffers for I2C operations 281 | 282 | ## Future Extensions 283 | 284 | This library is designed for easy extension: 285 | 286 | - **Temperature sensor** reading (registers 0x0E, 0x0F) 287 | - **Periodic timer** interrupts (registers 0x0B, 0x0C) 288 | - **External event** timestamping (EVI pin) 289 | - **Clock output** configuration (CLKOUT pin) 290 | - **EEPROM** access for user data 291 | - **Power management** features 292 | - **Time stamp** functions 293 | 294 | ## Troubleshooting 295 | 296 | ### RTC Not Found 297 | - Check I2C wiring (SDA, SCL) 298 | - Verify 3.3V power supply 299 | - Ensure pull-up resistors on I2C lines 300 | - Check I2C bus configuration in menuconfig 301 | 302 | ### I2C Communication Errors 303 | - Verify I2C pins are not used by other peripherals 304 | - Try lower I2C speed in bus configuration 305 | - Check for I2C conflicts with other devices 306 | 307 | ### Interrupts Not Working 308 | - Verify INT pin connection and pull-up 309 | - Check GPIO pin configuration 310 | - Ensure interrupt service is installed 311 | - Verify alarm settings are correct 312 | 313 | ### Build Errors 314 | - Ensure ESP-IDF version is 5.5 or higher 315 | - Check CMakeLists.txt includes required components 316 | - Verify all source files are in SRCS list 317 | - If building the examples: MAKE SURE THE ROOT DIRECTORY IS NAME "RV3032_ESP32" (or change the CMakeList.txt) 318 | 319 | ## License 320 | 321 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 322 | 323 | Author: Jonas Schnelli 324 | 325 | Based on the RV-3032-C7 Application Manual Rev. 1.3 (May 2023). 326 | 327 | ## Contributing 328 | 329 | Contributions are welcome! Please: 330 | 1. Follow ESP-IDF coding standards 331 | 2. Keep the code memory efficient 332 | 3. Add appropriate error handling 333 | 4. Test thoroughly with ESP-IDF 5.5+ 334 | 5. Update documentation accordingly 335 | -------------------------------------------------------------------------------- /src/RV3032.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file RV3032.c 3 | * @brief Implementation of RV3032 RTC library for ESP-IDF 5.5 4 | */ 5 | 6 | #include "RV3032.h" 7 | #include "esp_log.h" 8 | #include "esp_timer.h" 9 | #include 10 | 11 | static const char* TAG = "RV3032"; 12 | 13 | // Days in each month (non-leap year) 14 | static const uint8_t DAYS_IN_MONTH[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 15 | 16 | // === Private Function Declarations === 17 | 18 | static uint8_t bcd_to_dec(uint8_t bcd); 19 | static uint8_t dec_to_bcd(uint8_t dec); 20 | static esp_err_t rv3032_write_register(rv3032_handle_t* handle, uint8_t reg, uint8_t value); 21 | static esp_err_t rv3032_read_register(rv3032_handle_t* handle, uint8_t reg, uint8_t* value); 22 | static esp_err_t rv3032_read_registers(rv3032_handle_t* handle, uint8_t reg, uint8_t* buffer, uint8_t length); 23 | static esp_err_t rv3032_write_registers(rv3032_handle_t* handle, uint8_t reg, const uint8_t* buffer, uint8_t length); 24 | static uint8_t days_in_month(uint8_t month, uint8_t year); 25 | static bool is_leap_year(uint8_t year); 26 | static uint32_t datetime_to_unix(const rv3032_datetime_t* dt); 27 | static void unix_to_datetime(uint32_t unix_time, rv3032_datetime_t* dt); 28 | static void IRAM_ATTR gpio_isr_handler(void* arg); 29 | 30 | // === Public Functions === 31 | 32 | esp_err_t rv3032_init(rv3032_handle_t* handle, 33 | gpio_num_t int_pin, 34 | void (*alarm_callback)(void)) { 35 | if (!handle) { 36 | return ESP_ERR_INVALID_ARG; 37 | } 38 | 39 | memset(handle, 0, sizeof(rv3032_handle_t)); 40 | handle->i2c_port = RV3032_I2C_MASTER_NUM; 41 | handle->int_pin = int_pin; 42 | handle->alarm_callback = alarm_callback; 43 | 44 | // ASSUMES I2C is already initialized in the main application 45 | 46 | // Give the chip time to start up 47 | vTaskDelay(pdMS_TO_TICKS(100)); 48 | 49 | // Test communication 50 | bool connected; 51 | esp_err_t ret = rv3032_is_connected(handle, &connected); 52 | if (ret != ESP_OK || !connected) { 53 | ESP_LOGE(TAG, "RV3032 not responding"); 54 | return ESP_ERR_NOT_FOUND; 55 | } 56 | 57 | // Configure interrupt pin if provided 58 | if (int_pin != GPIO_NUM_NC && alarm_callback != NULL) { 59 | gpio_config_t io_conf = { 60 | .intr_type = GPIO_INTR_NEGEDGE, 61 | .mode = GPIO_MODE_INPUT, 62 | .pin_bit_mask = (1ULL << int_pin), 63 | .pull_down_en = GPIO_PULLDOWN_DISABLE, 64 | .pull_up_en = GPIO_PULLUP_ENABLE, 65 | }; 66 | 67 | ret = gpio_config(&io_conf); 68 | if (ret != ESP_OK) { 69 | ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); 70 | return ret; 71 | } 72 | 73 | ret = gpio_install_isr_service(0); 74 | if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { 75 | ESP_LOGE(TAG, "GPIO ISR service install failed: %s", esp_err_to_name(ret)); 76 | return ret; 77 | } 78 | 79 | ret = gpio_isr_handler_add(int_pin, gpio_isr_handler, handle); 80 | if (ret != ESP_OK) { 81 | ESP_LOGE(TAG, "GPIO ISR handler add failed: %s", esp_err_to_name(ret)); 82 | return ret; 83 | } 84 | } 85 | 86 | handle->initialized = true; 87 | ESP_LOGI(TAG, "RV3032 initialized successfully"); 88 | return ESP_OK; 89 | } 90 | 91 | esp_err_t rv3032_deinit(rv3032_handle_t* handle) { 92 | if (!handle) { 93 | return ESP_ERR_INVALID_ARG; 94 | } 95 | 96 | // Remove GPIO interrupt handler if configured 97 | if (handle->int_pin != GPIO_NUM_NC) { 98 | gpio_isr_handler_remove(handle->int_pin); 99 | } 100 | 101 | handle->initialized = false; 102 | memset(handle, 0, sizeof(rv3032_handle_t)); 103 | return ESP_OK; 104 | } 105 | 106 | esp_err_t rv3032_get_datetime(rv3032_handle_t* handle, rv3032_datetime_t* dt) { 107 | if (!handle || !dt) { 108 | return ESP_ERR_INVALID_ARG; 109 | } 110 | 111 | uint8_t buffer[8]; 112 | 113 | // Read all time registers in one transaction (addresses 0x00-0x07) 114 | esp_err_t ret = rv3032_read_registers(handle, RV3032_REG_HUNDREDTHS, buffer, 8); 115 | if (ret != ESP_OK) { 116 | return ret; 117 | } 118 | 119 | // Convert from BCD to decimal 120 | dt->hundredths = bcd_to_dec(buffer[0]); 121 | dt->seconds = bcd_to_dec(buffer[1] & 0x7F); // Mask bit 7 (always 0) 122 | dt->minutes = bcd_to_dec(buffer[2] & 0x7F); // Mask bit 7 (always 0) 123 | dt->hours = bcd_to_dec(buffer[3] & 0x3F); // Mask bits 7:6 (always 0) 124 | dt->weekday = buffer[4] & 0x07; // Mask bits 7:3 (always 0) 125 | dt->date = bcd_to_dec(buffer[5] & 0x3F); // Mask bits 7:6 (always 0) 126 | dt->month = bcd_to_dec(buffer[6] & 0x1F); // Mask bits 7:5 (always 0) 127 | dt->year = bcd_to_dec(buffer[7]); 128 | 129 | return ESP_OK; 130 | } 131 | 132 | esp_err_t rv3032_set_datetime(rv3032_handle_t* handle, const rv3032_datetime_t* dt) { 133 | if (!handle || !dt) { 134 | return ESP_ERR_INVALID_ARG; 135 | } 136 | 137 | uint8_t buffer[7]; 138 | 139 | // Convert to BCD and prepare buffer (skip hundredths - read-only) 140 | buffer[0] = dec_to_bcd(dt->seconds); 141 | buffer[1] = dec_to_bcd(dt->minutes); 142 | buffer[2] = dec_to_bcd(dt->hours); 143 | buffer[3] = dt->weekday & 0x07; 144 | buffer[4] = dec_to_bcd(dt->date); 145 | buffer[5] = dec_to_bcd(dt->month); 146 | buffer[6] = dec_to_bcd(dt->year); 147 | 148 | // Write all registers starting from seconds (0x01-0x07) 149 | // Writing to seconds register also clears hundredths and synchronizes 150 | return rv3032_write_registers(handle, RV3032_REG_SECONDS, buffer, 7); 151 | } 152 | 153 | esp_err_t rv3032_get_unix_time(rv3032_handle_t* handle, uint32_t* unix_time) { 154 | if (!handle || !unix_time) { 155 | return ESP_ERR_INVALID_ARG; 156 | } 157 | 158 | rv3032_datetime_t dt; 159 | esp_err_t ret = rv3032_get_datetime(handle, &dt); 160 | if (ret != ESP_OK) { 161 | return ret; 162 | } 163 | 164 | *unix_time = datetime_to_unix(&dt); 165 | return ESP_OK; 166 | } 167 | 168 | esp_err_t rv3032_set_unix_time(rv3032_handle_t* handle, uint32_t unix_time) { 169 | if (!handle) { 170 | return ESP_ERR_INVALID_ARG; 171 | } 172 | 173 | rv3032_datetime_t dt; 174 | unix_to_datetime(unix_time, &dt); 175 | return rv3032_set_datetime(handle, &dt); 176 | } 177 | 178 | esp_err_t rv3032_set_alarm(rv3032_handle_t* handle, 179 | const rv3032_alarm_t* alarm, 180 | bool enable_interrupt) { 181 | if (!handle || !alarm) { 182 | return ESP_ERR_INVALID_ARG; 183 | } 184 | 185 | uint8_t buffer[3]; 186 | 187 | // Prepare alarm registers 188 | // Minutes alarm (0x08): bit 7 = enable, bits 6:0 = BCD minutes 189 | buffer[0] = dec_to_bcd(alarm->minutes); 190 | if (!alarm->minutes_enable) { 191 | buffer[0] |= RV3032_ALARM_DISABLE_BIT; 192 | } 193 | 194 | // Hours alarm (0x09): bit 7 = enable, bits 5:0 = BCD hours 195 | buffer[1] = dec_to_bcd(alarm->hours); 196 | if (!alarm->hours_enable) { 197 | buffer[1] |= RV3032_ALARM_DISABLE_BIT; 198 | } 199 | 200 | // Date alarm (0x0A): bit 7 = enable, bits 5:0 = BCD date 201 | buffer[2] = dec_to_bcd(alarm->date); 202 | if (!alarm->date_enable) { 203 | buffer[2] |= RV3032_ALARM_DISABLE_BIT; 204 | } 205 | 206 | // Write alarm registers 207 | esp_err_t ret = rv3032_write_registers(handle, RV3032_REG_MINUTES_ALARM, buffer, 3); 208 | if (ret != ESP_OK) { 209 | return ret; 210 | } 211 | 212 | // Enable/disable alarm interrupt 213 | uint8_t ctrl2; 214 | ret = rv3032_read_register(handle, RV3032_REG_CONTROL2, &ctrl2); 215 | if (ret != ESP_OK) { 216 | return ret; 217 | } 218 | 219 | if (enable_interrupt) { 220 | ctrl2 |= RV3032_CTRL2_AIE; 221 | } else { 222 | ctrl2 &= ~RV3032_CTRL2_AIE; 223 | } 224 | 225 | return rv3032_write_register(handle, RV3032_REG_CONTROL2, ctrl2); 226 | } 227 | 228 | esp_err_t rv3032_get_alarm(rv3032_handle_t* handle, rv3032_alarm_t* alarm) { 229 | if (!handle || !alarm) { 230 | return ESP_ERR_INVALID_ARG; 231 | } 232 | 233 | uint8_t buffer[3]; 234 | 235 | // Read alarm registers 236 | esp_err_t ret = rv3032_read_registers(handle, RV3032_REG_MINUTES_ALARM, buffer, 3); 237 | if (ret != ESP_OK) { 238 | return ret; 239 | } 240 | 241 | // Parse alarm settings 242 | alarm->minutes_enable = !(buffer[0] & RV3032_ALARM_DISABLE_BIT); 243 | alarm->minutes = bcd_to_dec(buffer[0] & 0x7F); 244 | 245 | alarm->hours_enable = !(buffer[1] & RV3032_ALARM_DISABLE_BIT); 246 | alarm->hours = bcd_to_dec(buffer[1] & 0x3F); 247 | 248 | alarm->date_enable = !(buffer[2] & RV3032_ALARM_DISABLE_BIT); 249 | alarm->date = bcd_to_dec(buffer[2] & 0x3F); 250 | 251 | return ESP_OK; 252 | } 253 | 254 | esp_err_t rv3032_clear_alarm(rv3032_handle_t* handle) { 255 | if (!handle) { 256 | return ESP_ERR_INVALID_ARG; 257 | } 258 | 259 | // Clear alarm flag by writing 0 to AF bit 260 | uint8_t status; 261 | esp_err_t ret = rv3032_read_register(handle, RV3032_REG_STATUS, &status); 262 | if (ret != ESP_OK) { 263 | return ret; 264 | } 265 | 266 | status &= ~RV3032_STATUS_AF; // Clear AF bit 267 | ret = rv3032_write_register(handle, RV3032_REG_STATUS, status); 268 | if (ret != ESP_OK) { 269 | return ret; 270 | } 271 | 272 | // Disable alarm interrupt 273 | uint8_t ctrl2; 274 | ret = rv3032_read_register(handle, RV3032_REG_CONTROL2, &ctrl2); 275 | if (ret != ESP_OK) { 276 | return ret; 277 | } 278 | 279 | ctrl2 &= ~RV3032_CTRL2_AIE; // Clear AIE bit 280 | return rv3032_write_register(handle, RV3032_REG_CONTROL2, ctrl2); 281 | } 282 | 283 | esp_err_t rv3032_is_alarm_triggered(rv3032_handle_t* handle, bool* triggered) { 284 | if (!handle || !triggered) { 285 | return ESP_ERR_INVALID_ARG; 286 | } 287 | 288 | uint8_t status; 289 | esp_err_t ret = rv3032_read_register(handle, RV3032_REG_STATUS, &status); 290 | if (ret != ESP_OK) { 291 | return ret; 292 | } 293 | 294 | *triggered = (status & RV3032_STATUS_AF) != 0; 295 | return ESP_OK; 296 | } 297 | 298 | esp_err_t rv3032_is_connected(rv3032_handle_t* handle, bool* connected) { 299 | if (!handle || !connected) { 300 | return ESP_ERR_INVALID_ARG; 301 | } 302 | 303 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 304 | i2c_master_start(cmd); 305 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_WRITE, true); 306 | i2c_master_stop(cmd); 307 | 308 | esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(RV3032_I2C_MASTER_TIMEOUT_MS)); 309 | i2c_cmd_link_delete(cmd); 310 | 311 | *connected = (ret == ESP_OK); 312 | return ESP_OK; 313 | } 314 | 315 | // === Private Helper Functions === 316 | 317 | static uint8_t bcd_to_dec(uint8_t bcd) { 318 | return ((bcd >> 4) * 10) + (bcd & 0x0F); 319 | } 320 | 321 | static uint8_t dec_to_bcd(uint8_t dec) { 322 | return ((dec / 10) << 4) | (dec % 10); 323 | } 324 | 325 | static esp_err_t rv3032_write_register(rv3032_handle_t* handle, uint8_t reg, uint8_t value) { 326 | uint8_t write_buf[2] = {reg, value}; 327 | 328 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 329 | i2c_master_start(cmd); 330 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_WRITE, true); 331 | i2c_master_write(cmd, write_buf, 2, true); 332 | i2c_master_stop(cmd); 333 | 334 | esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(RV3032_I2C_MASTER_TIMEOUT_MS)); 335 | i2c_cmd_link_delete(cmd); 336 | 337 | return ret; 338 | } 339 | 340 | static esp_err_t rv3032_read_register(rv3032_handle_t* handle, uint8_t reg, uint8_t* value) { 341 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 342 | 343 | // Write register address 344 | i2c_master_start(cmd); 345 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_WRITE, true); 346 | i2c_master_write_byte(cmd, reg, true); 347 | 348 | // Read data 349 | i2c_master_start(cmd); 350 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_READ, true); 351 | i2c_master_read_byte(cmd, value, I2C_MASTER_NACK); 352 | i2c_master_stop(cmd); 353 | 354 | esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(RV3032_I2C_MASTER_TIMEOUT_MS)); 355 | i2c_cmd_link_delete(cmd); 356 | 357 | return ret; 358 | } 359 | 360 | static esp_err_t rv3032_read_registers(rv3032_handle_t* handle, uint8_t reg, uint8_t* buffer, uint8_t length) { 361 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 362 | 363 | // Write register address 364 | i2c_master_start(cmd); 365 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_WRITE, true); 366 | i2c_master_write_byte(cmd, reg, true); 367 | 368 | // Read data 369 | i2c_master_start(cmd); 370 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_READ, true); 371 | 372 | if (length > 1) { 373 | i2c_master_read(cmd, buffer, length - 1, I2C_MASTER_ACK); 374 | } 375 | i2c_master_read_byte(cmd, buffer + length - 1, I2C_MASTER_NACK); 376 | i2c_master_stop(cmd); 377 | 378 | esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(RV3032_I2C_MASTER_TIMEOUT_MS)); 379 | i2c_cmd_link_delete(cmd); 380 | 381 | return ret; 382 | } 383 | 384 | static esp_err_t rv3032_write_registers(rv3032_handle_t* handle, uint8_t reg, const uint8_t* buffer, uint8_t length) { 385 | uint8_t* write_buf = malloc(length + 1); 386 | if (!write_buf) { 387 | return ESP_ERR_NO_MEM; 388 | } 389 | 390 | write_buf[0] = reg; 391 | memcpy(&write_buf[1], buffer, length); 392 | 393 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 394 | i2c_master_start(cmd); 395 | i2c_master_write_byte(cmd, (RV3032_I2C_ADDR << 1) | I2C_MASTER_WRITE, true); 396 | i2c_master_write(cmd, write_buf, length + 1, true); 397 | i2c_master_stop(cmd); 398 | 399 | esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(RV3032_I2C_MASTER_TIMEOUT_MS)); 400 | i2c_cmd_link_delete(cmd); 401 | free(write_buf); 402 | return ret; 403 | } 404 | 405 | static uint8_t days_in_month(uint8_t month, uint8_t year) { 406 | if (month == 2 && is_leap_year(year)) { 407 | return 29; 408 | } 409 | return DAYS_IN_MONTH[month - 1]; 410 | } 411 | 412 | static bool is_leap_year(uint8_t year) { 413 | // Year is 0-99 representing 2000-2099 414 | // All years divisible by 4 in this range are leap years 415 | // (2100 is not a leap year, but it's outside our range) 416 | uint16_t full_year = 2000 + year; 417 | return (full_year % 4 == 0); 418 | } 419 | 420 | static uint32_t datetime_to_unix(const rv3032_datetime_t* dt) { 421 | // Calculate days since January 1, 2000 422 | uint32_t days = 0; 423 | 424 | // Add days for complete years 425 | for (uint8_t y = 0; y < dt->year; y++) { 426 | days += is_leap_year(y) ? 366 : 365; 427 | } 428 | 429 | // Add days for complete months in current year 430 | for (uint8_t m = 1; m < dt->month; m++) { 431 | days += days_in_month(m, dt->year); 432 | } 433 | 434 | // Add days in current month 435 | days += (dt->date - 1); 436 | 437 | // Convert to seconds and add time 438 | uint32_t seconds = days * 86400UL; // 24 * 60 * 60 439 | seconds += dt->hours * 3600UL; // 60 * 60 440 | seconds += dt->minutes * 60UL; 441 | seconds += dt->seconds; 442 | 443 | return seconds; 444 | } 445 | 446 | static void unix_to_datetime(uint32_t unix_time, rv3032_datetime_t* dt) { 447 | // Extract time components 448 | dt->seconds = unix_time % 60; 449 | unix_time /= 60; 450 | dt->minutes = unix_time % 60; 451 | unix_time /= 60; 452 | dt->hours = unix_time % 24; 453 | uint32_t days = unix_time / 24; 454 | 455 | // Find year 456 | dt->year = 0; 457 | while (true) { 458 | uint16_t days_in_year = is_leap_year(dt->year) ? 366 : 365; 459 | if (days < days_in_year) { 460 | break; 461 | } 462 | days -= days_in_year; 463 | dt->year++; 464 | } 465 | 466 | // Find month 467 | dt->month = 1; 468 | while (true) { 469 | uint8_t days_in_current_month = days_in_month(dt->month, dt->year); 470 | if (days < days_in_current_month) { 471 | break; 472 | } 473 | days -= days_in_current_month; 474 | dt->month++; 475 | } 476 | 477 | // Set date 478 | dt->date = days + 1; 479 | 480 | // Calculate weekday (0 = Saturday for Jan 1, 2000) 481 | uint32_t total_days = (unix_time / 86400UL); 482 | dt->weekday = (total_days + 6) % 7; // Adjust so 0 = Sunday 483 | 484 | // Hundredths is not calculated from Unix time 485 | dt->hundredths = 0; 486 | } 487 | 488 | static void IRAM_ATTR gpio_isr_handler(void* arg) { 489 | rv3032_handle_t* handle = (rv3032_handle_t*)arg; 490 | if (handle && handle->alarm_callback) { 491 | handle->alarm_callback(); 492 | } 493 | } --------------------------------------------------------------------------------