finishing rtc driver and demo

pcf85063a
Jonathan Williamson 2022-05-30 08:40:26 +01:00 zatwierdzone przez Phil Howard
rodzic 48c502c3a1
commit 4a5df45573
4 zmienionych plików z 298 dodań i 175 usunięć

Wyświetl plik

@ -7,4 +7,4 @@ target_sources(${DRIVER_NAME} INTERFACE
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c hardware_rtc)
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c hardware_rtc pimoroni_i2c)

Wyświetl plik

@ -1,145 +1,178 @@
#include "pcf85063a.hpp"
#include <chrono>
#include <cstdio>
#include "hardware/i2c.h"
#include "hardware/rtc.h"
// binary coded decimal conversion helper functions
uint8_t bcd_encode(uint v) {
uint v10 = v / 10, v1 = v - (v10 * 10); return v1 | (v10 << 4); }
int8_t bcd_decode(uint v) {
uint v10 = (v >> 4) & 0x0f, v1 = v & 0x0f; return v1 + (v10 * 10); }
namespace pimoroni {
const uint8_t REG_CONTROL_1 = 0x00;
const uint8_t REG_CONTROL_2 = 0x01;
const uint8_t REG_OFFSET = 0x02;
const uint8_t REG_RAM_BYTE = 0x03;
const uint8_t REG_SECONDS = 0x04;
const uint8_t REG_MINUTES = 0x05;
const uint8_t REG_HOURS = 0x06;
const uint8_t REG_DAYS = 0x07;
const uint8_t REG_WEEKDAYS = 0x08;
const uint8_t REG_MONTHS = 0x09;
const uint8_t REG_YEARS = 0x0a;
const uint8_t REG_SECOND_ALARM = 0x0b;
const uint8_t REG_MINUTE_ALARM = 0x0c;
const uint8_t REG_HOUR_ALARM = 0x0d;
const uint8_t REG_DAY_ALARM = 0x0e;
const uint8_t REG_WEEKDAY_ALARM = 0x0f;
const uint8_t REG_TIMER_VALUE = 0x10;
const uint8_t REG_TIMER_MODE = 0x11;
void PCF85063A::init() {
// configure i2c interface and pins
i2c_init(i2c, i2c_baud);
gpio_set_function(sda, GPIO_FUNC_I2C);
gpio_set_function(scl, GPIO_FUNC_I2C);
if(interrupt != PIN_UNUSED) {
gpio_set_function(interrupt, GPIO_FUNC_SIO);
gpio_set_dir(interrupt, GPIO_IN);
gpio_set_pulls(interrupt, false, true);
}
}
void PCF85063A::configure(uint8_t flags) {
static uint8_t command[2] = {REG_CONTROL_2, flags};
i2c_write_blocking(i2c, I2C_ADDRESS, command, 2, false);
reset();
}
void PCF85063A::reset() {
static uint8_t command[2] = {REG_CONTROL_1, COMMAND_RESET};
i2c_write_blocking(i2c, I2C_ADDRESS, command, 2, false);
// magic soft reset command
i2c->reg_write_uint8(address, Registers::CONTROL_1, 0x58);
// read the oscillator status bit until it is cleared
uint8_t status = 0x80;
while(status & 0x80) {
// attempt to clear oscillator stop flag, then read it back
i2c->reg_write_uint8(address, Registers::OSCILLATOR_STATUS, 0x00);
status = i2c->reg_read_uint8(address, Registers::OSCILLATOR_STATUS);
}
}
uint8_t bcd_encode(uint v) {
uint v10 = v / 10;
uint v1 = v - (v10 * 10);
return v1 | (v10 << 4);
// set the speed of (or turn off) the clock output
void PCF85063A::set_clock_output(ClockOut co) {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
bits = (bits & ~0x07) | uint8_t(co);
i2c->reg_write_uint8(
address, Registers::CONTROL_2, bits);
}
int8_t bcd_decode(uint v) {
uint v10 = (v >> 4) & 0x0f;
uint v1 = v & 0x0f;
return v1 + (v10 * 10);
}
void PCF85063A::set_datetime(datetime_t *t) {
static uint8_t data[7] = {
bcd_encode((uint)t->sec),
bcd_encode((uint)t->min),
bcd_encode((uint)t->hour),
bcd_encode((uint)t->day),
bcd_encode((uint)t->dotw),
bcd_encode((uint)t->month),
bcd_encode((uint)t->year - 2000) // offset year
};
bool PCF85063A::set_datetime(datetime_t *t) {
/*if (!valid_datetime(t)) {
return false;
} */
static uint8_t command[8] = {0};
command[0] = REG_SECONDS;
command[1] = bcd_encode((uint)t->sec);
command[2] = bcd_encode((uint)t->min);
command[3] = bcd_encode((uint)t->hour);
command[4] = bcd_encode((uint)t->dotw);
command[5] = bcd_encode((uint)t->day);
command[6] = bcd_encode((uint)t->month);
command[7] = bcd_encode((uint)t->year - 2000);
i2c_write_blocking(i2c, I2C_ADDRESS, command, 8, false);
return true;
i2c->write_bytes(address, Registers::SECONDS, data, 7);
}
datetime_t PCF85063A::get_datetime() {
static uint8_t result[7] = {0};
i2c_write_blocking(i2c, I2C_ADDRESS, &REG_SECONDS, 1, false);
i2c_read_blocking(i2c, I2C_ADDRESS, result, 7, false);
i2c->read_bytes(address, Registers::SECONDS, result, 7);
datetime_t dt = {
.year = (int16_t)(bcd_decode(result[6]) + 2000),
.month = (int8_t)bcd_decode(result[5]),
.day = (int8_t)bcd_decode(result[4]),
.dotw = (int8_t)bcd_decode(result[3]),
.hour = (int8_t)bcd_decode(result[2]),
.min = (int8_t)bcd_decode(result[1]),
.sec = (int8_t)bcd_decode(result[0] & 0x7f)
.year = (int16_t)(bcd_decode(result[6]) + 2000), // offset year
.month = ( int8_t) bcd_decode(result[5]),
.day = ( int8_t) bcd_decode(result[3]),
.dotw = ( int8_t) bcd_decode(result[4]),
.hour = ( int8_t) bcd_decode(result[2]),
.min = ( int8_t) bcd_decode(result[1]),
.sec = ( int8_t) bcd_decode(result[0] & 0x7f) // mask out status bit
};
return dt;
}
void PCF85063A::set_second_alarm(uint sec) {
uint8_t se = bcd_encode((uint)sec);
se |= 0x80; // enable alarm bit
static uint8_t command[2] = {REG_SECOND_ALARM, se};
i2c_write_blocking(i2c, I2C_ADDRESS, command, 2, false);
void PCF85063A::enable_alarm_interrupt(bool enable) {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
bits = enable ? (bits | 0x80) : (bits & ~0x80);
bits |= 0x40; // ensure alarm flag isn't reset
i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
}
void PCF85063A::reset_timer() {
static uint8_t command[2] = {REG_TIMER_MODE, 0b00000000};
i2c_write_blocking(i2c, I2C_ADDRESS, command, 2, false);
void PCF85063A::clear_alarm_flag() {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
bits &= ~0x40;
i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
}
void PCF85063A::set_seconds_timer(uint8_t sec) {
reset_timer();
static uint8_t command1[2] = {REG_TIMER_VALUE, sec};
i2c_write_blocking(i2c, I2C_ADDRESS, command1, 2, false);
static uint8_t command2[2] = {REG_TIMER_MODE, 0b00010110};
i2c_write_blocking(i2c, I2C_ADDRESS, command2, 2, false);
bool PCF85063A::read_alarm_flag() {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
return bits & 0x40;
}
void PCF85063A::set_alarm(int second, int minute, int hour, int day) {
uint8_t alarm[5] = {
uint8_t(second != -1 ? bcd_encode(second) : 0x80),
uint8_t(minute != -1 ? bcd_encode(minute) : 0x80),
uint8_t(hour != -1 ? bcd_encode( hour) : 0x80),
uint8_t(day != -1 ? bcd_encode( day) : 0x80),
uint8_t(0x80)
};
i2c->write_bytes(address, Registers::SECOND_ALARM, alarm, 5);
}
void PCF85063A::set_weekday_alarm(
int second, int minute, int hour, DayOfWeek dotw) {
uint8_t alarm[5] = {
uint8_t(second != -1 ? bcd_encode(second) : 0x80),
uint8_t(minute != -1 ? bcd_encode(minute) : 0x80),
uint8_t(hour != -1 ? bcd_encode( hour) : 0x80),
uint8_t(0x80),
uint8_t(dotw != DayOfWeek::NONE ? bcd_encode( dotw) : 0x80)
};
i2c->write_bytes(address, Registers::SECOND_ALARM, alarm, 5);
}
void PCF85063A::unset_alarm() {
uint8_t dummy[5] = {0};
i2c->write_bytes(address, Registers::SECOND_ALARM, dummy, 5);
}
void PCF85063A::enable_timer_interrupt(bool enable, bool flag_only) {
uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);
bits = (bits & ~0x03) | (enable ? 0x02 : 0x00) | (flag_only ? 0x01 : 0x00);
i2c->reg_write_uint8(address, Registers::TIMER_MODE, bits);
}
void PCF85063A::unset_timer() {
uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);
bits &= ~0x04;
i2c->reg_write_uint8(address, Registers::TIMER_MODE, bits);
}
void PCF85063A::set_timer(uint8_t ticks, TimerTickPeriod ttp) {
uint8_t bits = i2c->reg_read_uint8(address, Registers::TIMER_MODE);
// mask out timer tick period value and set new value
bits = (bits & ~0x18) | ttp;
// enable timer
bits |= 0x04;
i2c->reg_write_uint8(address, Registers::TIMER_MODE, bits);
}
bool PCF85063A::read_timer_flag() {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
return bits & 0x08;
}
void PCF85063A::clear_timer_flag() {
uint8_t bits = i2c->reg_read_uint8(address, Registers::CONTROL_2);
bits &= ~0x08;
i2c->reg_write_uint8(address, Registers::CONTROL_2, bits);
}
// i2c helper methods
i2c_inst_t* PCF85063A::get_i2c() const {
return i2c;
return i2c->get_i2c();
}
int PCF85063A::get_address() const {
return address;
}
int PCF85063A::get_sda() const {
return sda;
return i2c->get_sda();
}
int PCF85063A::get_scl() const {
return scl;
return i2c->get_sda();
}
int PCF85063A::get_interrupt() const {
int PCF85063A::get_int() const {
return interrupt;
}

Wyświetl plik

@ -1,7 +1,10 @@
#pragma once
#include "hardware/i2c.h"
#include "../../common/pimoroni_common.hpp"
#include "hardware/rtc.h"
#include "common/pimoroni_common.hpp"
#include "common/pimoroni_i2c.hpp"
namespace pimoroni {
@ -10,51 +13,68 @@ namespace pimoroni {
// Constants
//--------------------------------------------------
public:
static const uint DEFAULT_SDA_PIN = 4;
static const uint DEFAULT_SCL_PIN = 5;
static const uint8_t I2C_ADDRESS = 0x51;
static const uint DEFAULT_I2C_ADDRESS = 0x51;
static const uint8_t COMMAND_RESET = 0b01011000;
enum ClockOut : uint8_t {
CLOCK_OUT_32768HZ = 0,
CLOCK_OUT_16384HZ = 1,
CLOCK_OUT_8192HZ = 2,
CLOCK_OUT_4096HZ = 3,
CLOCK_OUT_2048HZ = 4,
CLOCK_OUT_1024HZ = 5,
CLOCK_OUT_1HZ = 6,
CLOCK_OUT_OFF = 7
};
static const uint8_t ALARM_INTERRUPT = 0b10000000;
static const uint8_t CLEAR_ALARM_FLAG = 0b01000000;
static const uint8_t MINUTE_INTERRUPT = 0b00100000;
static const uint8_t HALF_MINUTE_INTERRUPT = 0b00010000;
static const uint8_t TIMER_FLAG = 0b00001000;
enum DayOfWeek : int8_t {
SUNDAY = 0,
MONDAY = 1,
TUESDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6,
NONE = -1
};
static const uint8_t CLOCK_OUT_32768HZ = 0b00000000;
static const uint8_t CLOCK_OUT_16384HZ = 0b00000001;
static const uint8_t CLOCK_OUT_8192HZ = 0b00000010;
static const uint8_t CLOCK_OUT_4096HZ = 0b00000011;
static const uint8_t CLOCK_OUT_2048HZ = 0b00000100;
static const uint8_t CLOCK_OUT_1024HZ = 0b00000101;
static const uint8_t CLOCK_OUT_1HZ = 0b00000110;
static const uint8_t CLOCK_OUT_OFF = 0b00000111;
static const uint8_t RTC_STOP = 0b00100000;
static const uint8_t MODE_12_HOUR = 0b00000010;
static const uint8_t MODE_24_HOUR = 0b00000000;
static const uint8_t CAP_7PF = 0b00000000;
static const uint8_t CAP_12_5PF = 0b00000001;
enum TimerTickPeriod : int8_t {
TIMER_TICK_4096HZ = 0,
TIMER_TICK_64HZ = 1,
TIMER_TICK_1HZ = 2,
TIMER_TICK_1_OVER_60HZ = 3
};
//--------------------------------------------------
// Variables
//--------------------------------------------------
private:
// ????
public:
// ????
private:
i2c_inst_t *i2c = i2c0;
I2C *i2c;
// interface pins with our standard defaults where appropriate
uint sda = DEFAULT_SDA_PIN;
uint scl = DEFAULT_SCL_PIN;
uint address = DEFAULT_I2C_ADDRESS;
uint interrupt = PIN_UNUSED;
uint32_t i2c_baud = 400000;
enum Registers : uint8_t {
CONTROL_1 = 0x00,
CONTROL_2 = 0x01,
OFFSET = 0x02,
RAM_BYTE = 0x03,
OSCILLATOR_STATUS = 0x04, // flag embedded in seconds register (see below)
SECONDS = 0x04, // contains oscillator status flag (see above)
MINUTES = 0x05,
HOURS = 0x06,
DAYS = 0x07,
WEEKDAYS = 0x08,
MONTHS = 0x09,
YEARS = 0x0A,
SECOND_ALARM = 0x0B,
MINUTE_ALARM = 0x0C,
HOUR_ALARM = 0x0D,
DAY_ALARM = 0x0E,
WEEKDAY_ALARM = 0x0F,
TIMER_VALUE = 0x10,
TIMER_MODE = 0x11
};
//--------------------------------------------------
// Constructors/Destructor
@ -62,8 +82,8 @@ namespace pimoroni {
public:
PCF85063A() {}
PCF85063A(i2c_inst_t *i2c, uint sda, uint scl, uint interrupt = PIN_UNUSED) :
i2c(i2c), sda(sda), scl(scl), interrupt(interrupt) {}
PCF85063A(I2C *i2c, uint interrupt = PIN_UNUSED) :
i2c(i2c), interrupt(interrupt) {}
//--------------------------------------------------
// Methods
@ -72,19 +92,38 @@ namespace pimoroni {
void init();
void reset();
bool set_datetime(datetime_t *t);
// set and get the date and time
// uses datetime_t from pico sdk (hardware/rtc) for compatibility
void set_datetime(datetime_t *t);
datetime_t get_datetime();
void set_second_alarm(uint sec);
void configure(uint8_t flags);
// alarm manipulation methods
void enable_alarm_interrupt(bool enable);
void clear_alarm_flag();
bool read_alarm_flag();
void unset_alarm();
void set_alarm(int second = -1, int minute = -1, int hour = -1, int day = -1);
void set_weekday_alarm(int second = -1, int minute = -1, int hour = -1,
DayOfWeek dotw = DayOfWeek::NONE);
void reset_timer();
void set_seconds_timer(uint8_t sec);
// timer manipulation methods
void enable_timer_interrupt(bool enable, bool flag_only = false);
void unset_timer();
void set_timer(uint8_t ticks,
TimerTickPeriod ttp = TimerTickPeriod::TIMER_TICK_1HZ);
bool read_timer_flag();
void clear_timer_flag();
// clock output
void set_clock_output(ClockOut co);
// i2c instance details access methods
i2c_inst_t* get_i2c() const;
int get_address() const;
int get_sda() const;
int get_scl() const;
int get_interrupt() const;
int get_int() const;
};
}

Wyświetl plik

@ -7,63 +7,114 @@
using namespace pimoroni;
PCF85063A rtc;
void led_on() {
}
void led_off() {
}
int main() {
// keep the pico awake by holding vsys_en high
gpio_set_function(2, GPIO_FUNC_SIO);
gpio_set_dir(2, GPIO_OUT);
gpio_put(2, true);
stdio_init_all();
printf("woken up!\n");
// turn on led to show we're awake
gpio_set_function(6, GPIO_FUNC_SIO);
gpio_set_dir(6, GPIO_OUT);
gpio_set_function(6, GPIO_FUNC_SIO);
gpio_set_dir(6, GPIO_OUT);
gpio_put(6, true);
// keep the pico awake by holding vsys_en high
gpio_set_function(2, GPIO_FUNC_SIO);
gpio_set_dir(2, GPIO_OUT);
gpio_put(2, true);
// init usb serial for debugging and give our terminal monitor a bit of
// time to connect
stdio_init_all();
sleep_ms(500);
printf("\n\n\n");
printf("PCF85063A rtc demo!\n");
printf("===================\n");
sleep_ms(500);
printf("initialise rtc.. ");
I2C i2c(4, 5);
PCF85063A rtc(&i2c);
rtc.init();
rtc.configure(PCF85063A::CLEAR_ALARM_FLAG);
// rtc.setup(false);
printf("done!\n");
printf("\n");
printf("check clock function\n");
printf("\n");
// Make sure we have 24-hour time (smaller display!)
// if(rtc.is_12_hour())
//rtc.set_24_hour();
printf("- setting date and time\n");
printf(" > 30/5/2022 08:07:12\n");
datetime_t now = {
.year = 2022, .month = 5, .day = 27,
.hour = 12, .min = 29, .sec = 14
.year = 2022, .month = 5, .day = 30, .hour = 8, .min = 7, .sec = 12
};
rtc.set_datetime(&now);
// stay awake for 1 second
sleep_ms(1000);
printf("\n");
printf("- sleeping for 3 seconds\n");
sleep_ms(3000);
// shuold wake up 4 seconds after going to sleep
rtc.set_seconds_timer(3);
printf("\n");
printf("- reading date and time\n");
now = rtc.get_datetime();
printf(" > %02d/%02d/%04d %02d:%02d:%02d\n",
now.day, now.month, now.year, now.hour, now.min, now.sec);
printf("\n");
printf("check alarm function\n");
printf("\n");
// printf("going to sleep!\n");
printf("- setting alarm for in 6 seconds\n");
printf(" > 08:07:21\n");
rtc.set_alarm(21, 7, 8);
// go to sleep by driving vsys_en low
//gpio_put(2, false);
gpio_set_dir(2, GPIO_IN);
printf("\n");
printf("- waiting for alarm flag\n");
/*
while(true) {
datetime_t dt = rtc.get_datetime();
printf(
"%04d-%02d-%02d %02d:%02d:%02d\n",
dt.year, dt.month, dt.day,
dt.hour, dt.min, dt.sec
);
sleep_ms(1000);
while(!rtc.read_alarm_flag()) {
tight_loop_contents();
}
*/
printf("\n");
printf("- alarm triggered at\n");
now = rtc.get_datetime();
printf(" > %02d/%02d/%04d %02d:%02d:%02d\n",
now.day, now.month, now.year, now.hour, now.min, now.sec);
printf("\n");
printf("- clear alarm flag\n");
rtc.clear_alarm_flag();
printf("check timer function\n");
printf("\n");
printf("- setting timer for in 3 seconds\n");
rtc.set_timer(3);
printf("\n");
printf("- waiting for timer flag\n");
while(!rtc.read_timer_flag()) {
tight_loop_contents();
}
printf("\n");
printf("- timer triggered at\n");
now = rtc.get_datetime();
printf(" > %02d/%02d/%04d %02d:%02d:%02d\n",
now.day, now.month, now.year, now.hour, now.min, now.sec);
printf("\n");
printf("- clear timer flag\n");
rtc.clear_alarm_flag();
printf("\n");
printf("tests complete\n");
return 0;
}