/* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** library to communicate with the Maxim DS1307 I2C RTC IC (code) * @file rtc_ds1307.c * @author King Kévin * @date 2016 * @note user RAM is not handled * @note peripherals used: I2C @ref rtc_ds1307_i2c, GPIO & timer @ref rtc_ds1307_square_wave_timer */ /* standard libraries */ #include // standard integer types #include // standard I/O facilities #include // general utilities /* STM32 (including CM3) libraries */ #include // real-time control clock library #include // general purpose input output library #include // I2C library #include // interrupt handler #include // Cortex M3 utilities #include // timer utilities #include "global.h" // global utilities #include "rtc_ds1307.h" // RTC header and definitions #if defined(RTC_DS1307_SQUARE_WAVE_TICKS) volatile uint32_t rtc_ds1307_ticks = 0; volatile bool rtc_ds1307_tick_flag = false; #endif void rtc_ds1307_setup(void) { // configure I2C peripheral (see RM008 26.3.3, I2C master) rcc_periph_clock_enable(RTC_DS1307_I2C_PORT_RCC); // enable clock for I2C I/O peripheral gpio_set_mode(RTC_DS1307_I2C_PORT, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, RTC_DS1307_I2C_PIN_SDA | RTC_DS1307_I2C_PIN_SCL); // setup I2C I/O pins rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function rcc_periph_clock_enable(RTC_DS1307_I2C_RCC); // enable clock for I2C peripheral i2c_reset(RTC_DS1307_I2C); // reset configuration i2c_peripheral_disable(RTC_DS1307_I2C); // I2C needs to be disable to be configured i2c_set_clock_frequency(RTC_DS1307_I2C, rcc_apb1_frequency/1E6); // configure the peripheral clock to the APB1 freq (where it is connected to) i2c_set_standard_mode(RTC_DS1307_I2C); // the DS1307 has a maximum I2C SCL freq if 100 kHz (corresponding to the standard mode) i2c_set_ccr(RTC_DS1307_I2C, rcc_apb1_frequency/(100E3*2)); // set Thigh/Tlow to generate frequency of 100 kHz i2c_set_trise(RTC_DS1307_I2C, rcc_apb1_frequency/1E6); // max rise time for 100 kHz is 1000 ns (~1 MHz) i2c_peripheral_enable(RTC_DS1307_I2C); // enable I2C after configuration completed #if defined(RTC_DS1307_SQUARE_WAVE_TICKS) // setup timer to generate tick from square wave output rcc_periph_clock_enable(RTC_DS1307_SQUARE_WAVE_GPIO_RCC); // enable clock for GPIO peripheral gpio_set_mode(RTC_DS1307_SQUARE_WAVE_GPIO_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, RTC_DS1307_SQUARE_WAVE_GPIO_PIN); // set pin as input gpio_set(RTC_DS1307_SQUARE_WAVE_GPIO_PORT, RTC_DS1307_SQUARE_WAVE_GPIO_PIN); // enable pull-up rcc_periph_clock_enable(RTC_DS1307_SQUARE_WAVE_TIMER_RCC); // enable clock for timer peripheral timer_reset(RTC_DS1307_SQUARE_WAVE_TIMER); // reset timer state timer_ic_set_input(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TIMER_IC, RTC_DS1307_SQUARE_WAVE_TIMER_IN); // configure channel as input capture timer_ic_set_filter(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TIMER_IC, TIM_IC_OFF); // use no input capture filter timer_ic_set_polarity(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TIMER_IC, TIM_IC_FALLING); //capture on falling edge timer_slave_set_trigger(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TIMER_TS); // select trigger timer_slave_set_mode(RTC_DS1307_SQUARE_WAVE_TIMER, TIM_SMCR_SMS_ECM1); // select external clock more 1 as input timer_ic_enable(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TIMER_IC); // enable input capture timer_set_mode(RTC_DS1307_SQUARE_WAVE_TIMER, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up timer_set_prescaler(RTC_DS1307_SQUARE_WAVE_TIMER, 0); // no need to prescale timer_set_period(RTC_DS1307_SQUARE_WAVE_TIMER, RTC_DS1307_SQUARE_WAVE_TICKS-1); // set the tick period timer_enable_irq(RTC_DS1307_SQUARE_WAVE_TIMER, TIM_DIER_UIE); // enable interrupt for timer nvic_enable_irq(RTC_DS1307_SQUARE_WAVE_TIMER_IRQ); // allow interrupt for timer rtc_ds1307_tick_flag = false; // reset RTC tick flag timer_enable_counter(RTC_DS1307_SQUARE_WAVE_TIMER); // enable timer to count ticks rtc_ds1307_write_square_wave(RTC_DS1307_SQUARE_WAVE_FREQUENCY); // set square wave output frequency #endif } /** read memory from RTC IC * @param[in] addr start address for memory to read * @param[out] data buffer to store read memory * @param[in] len number of byte to read from the memory * @return if read succeeded */ static bool rtc_ds1307_read_memory(uint8_t addr, uint8_t* data, size_t len) { bool to_return = false; // return if read succeeded if (data==NULL || len==0) { // verify there it data to be read goto error; } i2c_send_start(RTC_DS1307_I2C); // send start condition to start transaction while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_SB)); // wait until start condition is transmitted if (!(I2C_SR2(RTC_DS1307_I2C) & I2C_SR2_MSL)) { // verify if in master mode goto error; } i2c_send_7bit_address(RTC_DS1307_I2C, RTC_DS1307_I2C_ADDR, I2C_WRITE); // select slave while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_ADDR)); // wait until address is transmitted if (!((I2C_SR2(RTC_DS1307_I2C) & I2C_SR2_TRA))) { // verify we are in transmit mode (and read SR2 to clear ADDR) goto error; } i2c_send_data(RTC_DS1307_I2C, addr); // send memory address we want to read while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_TxE)); // wait until byte has been transmitted i2c_send_start(RTC_DS1307_I2C); // send restart condition to switch from write to read mode while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_SB)); // wait until start condition is transmitted i2c_send_7bit_address(RTC_DS1307_I2C, RTC_DS1307_I2C_ADDR, I2C_READ); // select slave while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_ADDR)); // wait until address is transmitted if ((I2C_SR2(RTC_DS1307_I2C) & I2C_SR2_TRA)) { // verify we are in read mode (and read SR2 to clear ADDR) goto error; } for (size_t i=0; i>4)*10+(data[0]&0x0f); // convert BCD coding into seconds return to_return; } uint8_t rtc_ds1307_read_minutes(void) { uint8_t to_return = 0; // minutes to return uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(1, data, LENGTH(data)); // read a single byte containing minutes value to_return = (data[0]>>4)*10+(data[0]&0x0f); // convert BCD coding into minutes return to_return; } uint8_t rtc_ds1307_read_hours(void) { uint8_t to_return = 0; // hours to return uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(2, data, LENGTH(data)); // read a single byte containing hours value if (data[0]&0x40) { // 12 hour mode if (data[0]&0x02) { // PM to_return += 12; // add the 12 hours } to_return += ((data[0]&0x10)>>4)*10; // convert BCD coding into hours (first digit) } else { to_return = ((data[0]&0x30)>>4)*10; // convert BCD coding into hours (first digit) } to_return += (data[0]&0x0f); // convert BCD coding into hours (second digit) return to_return; } uint8_t rtc_ds1307_read_day(void) { uint8_t to_return = 0; // day to return uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(3, data, LENGTH(data)); // read a single byte containing day value to_return = (data[0]&0x07); // convert BCD coding into days return to_return; } uint8_t rtc_ds1307_read_date(void) { uint8_t to_return = 0; // date to return uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(4, data, LENGTH(data)); // read a single byte containing date value to_return = ((data[0]&0x30)>>4)*10+(data[0]&0x0f); // convert BCD coding into date return to_return; } uint8_t rtc_ds1307_read_month(void) { uint8_t to_return = 0; // month to return uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(5, data, LENGTH(data)); // read a single byte containing month value to_return = ((data[0]&0x10)>>4)*10+(data[0]&0x0f); // convert BCD coding into month return to_return; } uint8_t rtc_ds1307_read_year(void) { uint8_t data[1] = {0}; // to read data over I2C rtc_ds1307_read_memory(6, data, LENGTH(data)); // read a single byte containing year value uint8_t to_return = ((data[0]&0xf0)>>4)*10+(data[0]&0x0f); // convert BCD coding into year return to_return; } uint8_t* rtc_ds1307_read_time(void) { static uint8_t time[7] = {0}; // store time {seconds, minutes, hours, day, date, month, year} uint8_t data[7] = {0}; // to read data over I2C rtc_ds1307_read_memory(0, data, LENGTH(data)); // read all time bytes time[0] = ((data[0]&0x70)>>4)*10+(data[0]&0x0f); // convert seconds from BCD time[1] = (data[1]>>4)*10+(data[1]&0x0f); // convert minutes from BCD time[2] = 0; // re-initialize hours if (data[2]&0x40) { // 12 hour mode if (data[2]&0x02) { // PM time[2] += 12; // add the 12 hours } time[2] += ((data[2]&0x10)>>4)*10; // convert BCD coding into hours (first digit) } else { time[2] = ((data[2]&0x30)>>4)*10; // convert BCD coding into hours (first digit) } time[2] += (data[2]&0x0f); // convert BCD coding into hours (second digit) time[3] = (data[3]&0x07); // convert BCD coding into days time[4] = ((data[4]&0x30)>>4)*10+(data[4]&0x0f); // convert BCD coding into date time[5] = ((data[5]&0x10)>>4)*10+(data[5]&0x0f); // convert BCD coding into month time[6] = ((data[6]&0xf0)>>4)*10+(data[6]&0x0f); // convert BCD coding into year return time; } /** write memory into RTC IC * @param[in] addr start address for memory to be written * @param[in] data buffer to for memory to be written * @param[in] len number of byte to write into the memory * @return if write succeeded */ static bool rtc_ds1307_write_memory(uint8_t addr, uint8_t* data, size_t len) { bool to_return = false; // return if read succeeded if (data==NULL || len==0) { // verify there it data to be read goto error; } i2c_send_start(RTC_DS1307_I2C); // send start condition to start transaction while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_SB)); // wait until start condition is transmitted if (!(I2C_SR2(RTC_DS1307_I2C) & I2C_SR2_MSL)) { // verify if in master mode goto error; } i2c_send_7bit_address(RTC_DS1307_I2C, RTC_DS1307_I2C_ADDR, I2C_WRITE); // select slave while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_ADDR)); // wait until address is transmitted if (!((I2C_SR2(RTC_DS1307_I2C) & I2C_SR2_TRA))) { // verify we are in transmit mode (and read SR2 to clear ADDR) goto error; } i2c_send_data(RTC_DS1307_I2C, addr); // send memory address we want to read while (!(I2C_SR1(RTC_DS1307_I2C) & I2C_SR1_TxE)); // wait until byte has been transmitted for (size_t i=0; i59) { return false; } uint8_t data[1] = {0}; // to read CH value data and write seconds value over I2C if (!rtc_ds1307_read_memory(0, data, LENGTH(data))) { // read seconds with CH value return false; } data[0] &= 0x80; // only keep CH flag data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format return rtc_ds1307_write_memory(0, data, LENGTH(data)); // write current seconds with previous CH value } bool rtc_ds1307_write_minutes(uint8_t minutes) { if (minutes>59) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format return rtc_ds1307_write_memory(1, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_hours(uint8_t hours) { if (hours>24) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format return rtc_ds1307_write_memory(2, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_day(uint8_t day) { if (day<1 || day>7) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (day%8); // encode day in BCD format return rtc_ds1307_write_memory(3, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_date(uint8_t date) { if (date<1 || date>31) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format return rtc_ds1307_write_memory(4, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_month(uint8_t month) { if (month<1 || month>12) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format return rtc_ds1307_write_memory(5, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_year(uint8_t year) { if (year>99) { return false; } uint8_t data[1] = {0}; // to write time value data[0] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format return rtc_ds1307_write_memory(6, data, LENGTH(data)); // write time value on RTC } bool rtc_ds1307_write_time(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t day, uint8_t date, uint8_t month, uint8_t year) { uint8_t data[7] = {0}; // to write all time values // seconds if (seconds>59) { return false; } if (!rtc_ds1307_read_memory(0, data, 1)) { // read seconds with CH value return false; } data[0] &= 0x80; // only keep CH flag data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format // minutes if (minutes>59) { return false; } data[1] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format // hours if (hours>24) { return false; } data[2] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format // day if (day<1 || day>7) { return false; } data[3] = (day%8); // encode day in BCD format // date if (date<1 || date>31) { return false; } data[4] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format // month if (month<1 || month>12) { return false; } data[5] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format // year if (year>99) { return false; } data[6] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format return rtc_ds1307_write_memory(0, data, LENGTH(data)); // write time values on RTC } #if defined(RTC_DS1307_SQUARE_WAVE_TICKS) /** timer interrupt service routine called when number of ticks have been received */ void RTC_DS1307_SQUARE_WAVE_TIMER_ISR(void) { if (timer_get_flag(RTC_DS1307_SQUARE_WAVE_TIMER, TIM_SR_UIF)) { // overflow even happened timer_clear_flag(RTC_DS1307_SQUARE_WAVE_TIMER, TIM_SR_UIF); // clear flag rtc_ds1307_ticks++; // increment count rtc_ds1307_tick_flag = true; // update flag } } #endif