Compare commits

..

28 Commits

Author SHA1 Message Date
cdca5219d0 Rewrite on libavr
Same driver surface as the yazoalfa version — clock and alarm get/set,
alarm interrupts, flag check/clear — plus oscillator-stop detection and
die temperature. One source for tiny85 (software I2C) and mega328P (TWI),
built against libavr in both generated and reflect mode, byte-identical
.text across modes. Errors surface as std::expected instead of being
dropped; weekday-rate alarms now really set the DY bit (legacy cleared
it); multi-register access is one coherent bus transaction. Legacy stays
on master.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 01:09:43 +02:00
8a6170cb10 Add get alarm helper to reduce code duplication 2020-05-17 20:11:35 +02:00
21f9215bba Implement getting alarm times 2020-05-17 20:06:13 +02:00
dfd2289aef Reduce code duplication 2020-05-17 19:54:46 +02:00
14e608d397 Implement setting alarms 2020-05-17 19:41:53 +02:00
1bc7e66389 Move alarm rate from inside details to visible namespace 2020-05-17 19:19:28 +02:00
ff52f4f152 Implement checking and clearing alarm 2020-05-17 19:10:16 +02:00
3029c3cfe0 Add wrapper to write complete register 2020-05-17 18:59:38 +02:00
c3f9aa6a13 Automatically deduce register address by type 2020-05-17 18:59:19 +02:00
db5197b3b1 Add more flag operator overloads 2020-05-17 18:57:26 +02:00
2a90cdee18 Add time equality check operators 2020-05-17 11:49:30 +02:00
1388412d70 Change interface to only pass i2c backend driver into class 2020-05-16 19:52:22 +02:00
a946746960 Remove legacy rtc lib 2020-05-16 17:59:38 +02:00
16249914c2 Adapt to moved type submodule 2020-05-16 17:43:55 +02:00
11211be9b9 Implement setting date-time 2020-05-16 17:25:00 +02:00
8c50aa4688 Implement setting time 2020-05-16 17:19:54 +02:00
a65b30f9df Fix partial writing to only write to a range instead of until the end 2020-05-16 17:19:25 +02:00
cd5317db5b Implement setting RTC date 2020-05-16 17:03:30 +02:00
92096b6101 Add helper to allow writing partial register data 2020-05-16 17:02:54 +02:00
80cce4671f Implement optional automatic setting of day of week 2020-05-16 17:01:19 +02:00
c728d99f97 Fix default value for day field in time register 2020-05-16 16:59:54 +02:00
8e653ebd44 Add default init for registers 2020-05-16 16:00:27 +02:00
54b8917705 Implement modern C++ driver base 2020-05-15 19:47:44 +02:00
2768009720 Remove twi library and switch to i2c library submodule 2020-05-15 11:50:11 +02:00
04af54e7c8 Remove C time API glue from driver 2020-05-15 10:20:56 +02:00
9303fbf5b5 Fix clock path 2020-05-15 09:40:08 +02:00
727a974504 Fix warnings 2020-05-15 09:25:48 +02:00
b3364f0b88 Add gitignore, clang-format and license file 2020-05-15 09:20:23 +02:00
17 changed files with 472 additions and 1653 deletions

13
.clang-format Normal file
View File

@@ -0,0 +1,13 @@
---
BasedOnStyle: LLVM
ColumnLimit: 120
IndentWidth: 4
TabWidth: 4
UseTab: ForIndentation
AlignEscapedNewlines: DontAlign
AllowShortFunctionsOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: true
...

7
.gitignore vendored
View File

@@ -1,5 +1,2 @@
.vs build/
Release .cache/
Debug
*.componentinfo.xml
avrdude.bat

25
CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.28)
project(ds3231 LANGUAGES CXX)
# libavr comes from a local checkout (LIBAVR_ROOT cache/env variable) or,
# failing that, straight from the forge. The toolchain file also lives in
# that checkout — see CMakePresets.json.
include(FetchContent)
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
endif()
if(LIBAVR_ROOT)
FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT})
else()
FetchContent_Declare(libavr GIT_REPOSITORY git@git.blackmark.me:avr/libavr.git GIT_TAG main)
endif()
FetchContent_MakeAvailable(libavr)
add_library(ds3231 INTERFACE)
target_include_directories(ds3231 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(ds3231 INTERFACE libavr)
if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(example)
endif()

43
CMakePresets.json Normal file
View File

@@ -0,0 +1,43 @@
{
"version": 8,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"toolchainFile": "$env{LIBAVR_ROOT}/cmake/avr-toolchain.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_COLOR_DIAGNOSTICS": "ON"
}
},
{
"name": "attiny85-generated",
"inherits": "base",
"cacheVariables": { "LIBAVR_MCU": "attiny85", "LIBAVR_REFLECT": "OFF" }
},
{
"name": "attiny85-reflect",
"inherits": "base",
"cacheVariables": { "LIBAVR_MCU": "attiny85", "LIBAVR_REFLECT": "ON" }
},
{
"name": "atmega328p-generated",
"inherits": "base",
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "OFF" }
},
{
"name": "atmega328p-reflect",
"inherits": "base",
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "ON" }
}
],
"buildPresets": [
{ "name": "attiny85-generated", "configurePreset": "attiny85-generated" },
{ "name": "attiny85-reflect", "configurePreset": "attiny85-reflect" },
{ "name": "atmega328p-generated", "configurePreset": "atmega328p-generated" },
{ "name": "atmega328p-reflect", "configurePreset": "atmega328p-reflect" }
]
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 BlackMark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# ds3231
Maxim DS3231 RTC driver on [libavr](https://git.blackmark.me/avr/libavr):
clock and alarm read/write, alarm interrupts, oscillator-stop detection,
die temperature. One source runs on every libavr chip — TWI hardware on
the mega328P, open-drain software I2C on the tinies. All bus errors
surface as `std::expected`.
```cpp
using bus = dev::i2c<{.frequency = 100_kHz}>;
using rtc = ds3231::device<bus>;
auto now = rtc::read_clock(); // result<date_time>
(void)rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second);
```
`example/main.cpp` is the full tour. Build with a libavr checkout:
```sh
LIBAVR_ROOT=/path/to/libavr cmake --preset attiny85-generated
cmake --build --preset attiny85-generated
```
Presets cover attiny85/atmega328p in both libavr modes (generated and
reflect). The legacy yazoalfa-based driver lives on the `master` branch.
Differences from legacy: weekday-rate alarms now actually set the DY bit
(the old `setAlarmHelper` always cleared it), reads/writes are single
coherent bus transactions, and errors are reported instead of ignored.

3
example/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
add_executable(clock main.cpp)
target_link_libraries(clock PRIVATE ds3231)
add_custom_command(TARGET clock POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:clock>)

32
example/main.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include <ds3231/ds3231.hpp>
#include <libavr/libavr.hpp>
using namespace avr::literals;
// One source for the tiny85 (open-drain software I2C on the USI pins) and
// the mega328P (TWI hardware). On first power-up the clock is seeded and
// alarm 1 armed; then the LED mirrors the seconds parity — a stuck LED
// means bus errors.
using dev = avr::device<{.clock = 8_MHz}>;
using bus = dev::i2c<{.frequency = 100_kHz}>;
using rtc = ds3231::device<bus>;
using led = dev::output<avr::pb3>;
int main()
{
avr::init<bus, led>();
if (auto stopped = rtc::oscillator_stopped(); stopped.value_or(false)) {
(void)rtc::write_clock({{2026, 1, 1}, {12, 0, 0}});
(void)rtc::set_alarm1({}, ds3231::alarm1_rate::once_per_second);
(void)rtc::clear_oscillator_stopped();
}
while (true) {
if (auto now = rtc::read_clock(); now.has_value())
led::write(now->second % 2 == 0);
if (auto fired = rtc::alarm1_fired(); fired.value_or(false))
(void)rtc::clear_alarm1();
dev::delay<100_ms>();
}
}

304
include/ds3231/ds3231.hpp Normal file
View File

@@ -0,0 +1,304 @@
#pragma once
#include <array>
#include <cstdint>
#include <expected>
#include <libavr/i2c.hpp>
namespace ds3231 {
using avr::i2c::error;
using avr::i2c::status;
template <typename T>
using result = std::expected<T, error>;
struct date {
std::uint16_t year; // 2000..2099
std::uint8_t month; // 1..12
std::uint8_t day; // 1..31 (weekday 1..7 in weekday-alarm reads)
friend constexpr bool operator==(const date &, const date &) = default;
};
struct time_of_day {
std::uint8_t hour; // 0..23
std::uint8_t minute;
std::uint8_t second;
friend constexpr bool operator==(const time_of_day &, const time_of_day &) = default;
};
struct date_time : date, time_of_day {
friend constexpr bool operator==(const date_time &, const date_time &) = default;
};
// Alarm match rates (datasheet Table 2). Alarm 2 has no seconds register
// and fires at :00 of the matching minute.
enum class alarm1_rate : std::uint8_t {
once_per_second = 0b01111,
seconds_match = 0b01110,
minutes_seconds_match = 0b01100,
time_match = 0b01000,
date_time_match = 0b00000,
weekday_time_match = 0b10000,
};
enum class alarm2_rate : std::uint8_t {
once_per_minute = 0b0111,
minutes_match = 0b0110,
time_match = 0b0100,
date_time_match = 0b0000,
weekday_time_match = 0b1000,
};
namespace detail {
constexpr std::uint8_t to_bcd(std::uint8_t value)
{
return static_cast<std::uint8_t>(((value / 10) << 4) | (value % 10));
}
constexpr std::uint8_t from_bcd(std::uint8_t value)
{
return static_cast<std::uint8_t>((value >> 4) * 10 + (value & 0x0f));
}
// Hours register: bit 6 selects 12-hour mode, bit 5 is then PM. Writes
// always use 24-hour form; reads accept either.
constexpr std::uint8_t hours_from_reg(std::uint8_t reg)
{
if (reg & 0x40) {
auto hour = from_bcd(reg & 0x1f);
bool pm = reg & 0x20;
if (hour == 12)
return pm ? 12 : 0;
return static_cast<std::uint8_t>(pm ? hour + 12 : hour);
}
return from_bcd(reg & 0x3f);
}
// Sakamoto's method; the device counts weekdays 1..7 with a free epoch,
// this maps Sunday to 1.
constexpr std::uint8_t weekday(std::uint16_t year, std::uint8_t month, std::uint8_t day)
{
constexpr std::uint8_t offsets[]{0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
if (month < 3)
--year;
return static_cast<std::uint8_t>((year + year / 4 - year / 100 + year / 400 + offsets[month - 1] + day) % 7 + 1);
}
} // namespace detail
// Maxim DS3231 RTC on any libavr i2c master (register map: datasheet
// 19-5170 Table 1). Every call is one bus transaction; errors surface as
// std::expected. Weekday registers maintain themselves from the date when
// SetWeekday is on.
template <typename Bus, bool SetWeekday = true>
class device {
using dev = avr::i2c::device<Bus, 0x68>;
static constexpr std::uint8_t reg_clock = 0x00;
static constexpr std::uint8_t reg_alarm1 = 0x07;
static constexpr std::uint8_t reg_alarm2 = 0x0b;
static constexpr std::uint8_t reg_control = 0x0e;
static constexpr std::uint8_t reg_status = 0x0f;
static constexpr std::uint8_t reg_temp = 0x11;
static constexpr std::uint8_t a1ie = 0x01, a2ie = 0x02, intcn = 0x04, bbsqw = 0x40;
static constexpr std::uint8_t a1f = 0x01, a2f = 0x02, osf = 0x80;
public:
[[nodiscard]] static result<date_time> read_clock()
{
std::array<std::uint8_t, 7> raw;
if (auto s = dev::read_regs(reg_clock, raw); !s)
return std::unexpected(s.error());
date_time now;
now.second = detail::from_bcd(raw[0] & 0x7f);
now.minute = detail::from_bcd(raw[1] & 0x7f);
now.hour = detail::hours_from_reg(raw[2]);
now.day = detail::from_bcd(raw[4] & 0x3f);
now.month = detail::from_bcd(raw[5] & 0x1f);
now.year = static_cast<std::uint16_t>(2000 + detail::from_bcd(raw[6]));
return now;
}
[[nodiscard]] static result<date> read_date()
{
auto now = read_clock();
if (!now)
return std::unexpected(now.error());
return static_cast<date>(*now);
}
[[nodiscard]] static result<time_of_day> read_time()
{
std::array<std::uint8_t, 3> raw;
if (auto s = dev::read_regs(reg_clock, raw); !s)
return std::unexpected(s.error());
return time_of_day{detail::hours_from_reg(raw[2]), detail::from_bcd(raw[1] & 0x7f),
detail::from_bcd(raw[0] & 0x7f)};
}
[[nodiscard]] static status write_clock(const date_time &now)
{
std::array<std::uint8_t, 7> raw{detail::to_bcd(now.second),
detail::to_bcd(now.minute),
detail::to_bcd(now.hour),
SetWeekday ? detail::weekday(now.year, now.month, now.day) : std::uint8_t{1},
detail::to_bcd(now.day),
detail::to_bcd(now.month),
detail::to_bcd(static_cast<std::uint8_t>(now.year % 100))};
return dev::write_regs(reg_clock, raw);
}
[[nodiscard]] static status write_date(const date &value)
{
if constexpr (SetWeekday) {
std::array<std::uint8_t, 4> raw{detail::weekday(value.year, value.month, value.day),
detail::to_bcd(value.day), detail::to_bcd(value.month),
detail::to_bcd(static_cast<std::uint8_t>(value.year % 100))};
return dev::write_regs(reg_clock + 3, raw);
} else {
std::array<std::uint8_t, 3> raw{detail::to_bcd(value.day), detail::to_bcd(value.month),
detail::to_bcd(static_cast<std::uint8_t>(value.year % 100))};
return dev::write_regs(reg_clock + 4, raw);
}
}
[[nodiscard]] static status write_time(const time_of_day &value)
{
std::array<std::uint8_t, 3> raw{detail::to_bcd(value.second), detail::to_bcd(value.minute),
detail::to_bcd(value.hour)};
return dev::write_regs(reg_clock, raw);
}
// Alarm times use .day as the date of month, or as weekday 1..7 with
// the weekday_time_match rates.
[[nodiscard]] static status set_alarm1(const date_time &at, alarm1_rate rate, bool enable_interrupt = true)
{
auto m = static_cast<std::uint8_t>(rate);
std::array<std::uint8_t, 4> raw{
static_cast<std::uint8_t>(detail::to_bcd(at.second) | ((m & 1) << 7)),
static_cast<std::uint8_t>(detail::to_bcd(at.minute) | (((m >> 1) & 1) << 7)),
static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 2) & 1) << 7)),
static_cast<std::uint8_t>(day_date(at.day, rate == alarm1_rate::weekday_time_match) |
(((m >> 3) & 1) << 7))};
if (auto s = dev::write_regs(reg_alarm1, raw); !s)
return s;
return enable_interrupt ? enable_alarm_interrupt(a1ie) : status{};
}
[[nodiscard]] static status set_alarm2(const date_time &at, alarm2_rate rate, bool enable_interrupt = true)
{
auto m = static_cast<std::uint8_t>(rate);
std::array<std::uint8_t, 3> raw{
static_cast<std::uint8_t>(detail::to_bcd(at.minute) | ((m & 1) << 7)),
static_cast<std::uint8_t>(detail::to_bcd(at.hour) | (((m >> 1) & 1) << 7)),
static_cast<std::uint8_t>(day_date(at.day, rate == alarm2_rate::weekday_time_match) |
(((m >> 2) & 1) << 7))};
if (auto s = dev::write_regs(reg_alarm2, raw); !s)
return s;
return enable_interrupt ? enable_alarm_interrupt(a2ie) : status{};
}
[[nodiscard]] static result<date_time> read_alarm1()
{
std::array<std::uint8_t, 4> raw;
if (auto s = dev::read_regs(reg_alarm1, raw); !s)
return std::unexpected(s.error());
date_time at{};
at.second = detail::from_bcd(raw[0] & 0x7f);
at.minute = detail::from_bcd(raw[1] & 0x7f);
at.hour = detail::hours_from_reg(raw[2] & 0x7f);
at.day = (raw[3] & 0x40) ? (raw[3] & 0x0f) : detail::from_bcd(raw[3] & 0x3f);
return at;
}
[[nodiscard]] static result<date_time> read_alarm2()
{
std::array<std::uint8_t, 3> raw;
if (auto s = dev::read_regs(reg_alarm2, raw); !s)
return std::unexpected(s.error());
date_time at{};
at.minute = detail::from_bcd(raw[0] & 0x7f);
at.hour = detail::hours_from_reg(raw[1] & 0x7f);
at.day = (raw[2] & 0x40) ? (raw[2] & 0x0f) : detail::from_bcd(raw[2] & 0x3f);
return at;
}
[[nodiscard]] static result<bool> alarm1_fired()
{
return flag_set(a1f);
}
[[nodiscard]] static result<bool> alarm2_fired()
{
return flag_set(a2f);
}
[[nodiscard]] static status clear_alarm1()
{
return clear_flag(a1f);
}
[[nodiscard]] static status clear_alarm2()
{
return clear_flag(a2f);
}
// True after the oscillator was ever stopped (first power-up, battery
// ran out): the clock needs to be set.
[[nodiscard]] static result<bool> oscillator_stopped()
{
return flag_set(osf);
}
[[nodiscard]] static status clear_oscillator_stopped()
{
return clear_flag(osf);
}
// Die temperature in quarter °C (updated every 64 s by the device).
[[nodiscard]] static result<std::int16_t> temperature_quarters()
{
std::array<std::uint8_t, 2> raw;
if (auto s = dev::read_regs(reg_temp, raw); !s)
return std::unexpected(s.error());
return static_cast<std::int16_t>((static_cast<std::int16_t>(static_cast<std::int8_t>(raw[0])) << 2) |
(raw[1] >> 6));
}
private:
static constexpr std::uint8_t day_date(std::uint8_t day, bool weekday_mode)
{
if (weekday_mode)
return static_cast<std::uint8_t>(0x40 | (day & 0x0f));
return detail::to_bcd(day) & std::uint8_t{0x3f};
}
static status enable_alarm_interrupt(std::uint8_t enable_bit)
{
auto control = dev::read_reg(reg_control);
if (!control)
return std::unexpected(control.error());
return dev::write_reg(reg_control, static_cast<std::uint8_t>((*control & ~bbsqw) | intcn | enable_bit));
}
static result<bool> flag_set(std::uint8_t bit)
{
auto flags = dev::read_reg(reg_status);
if (!flags)
return std::unexpected(flags.error());
return (*flags & bit) != 0;
}
static status clear_flag(std::uint8_t bit)
{
auto flags = dev::read_reg(reg_status);
if (!flags)
return std::unexpected(flags.error());
return dev::write_reg(reg_status, static_cast<std::uint8_t>(*flags & ~bit));
}
};
} // namespace ds3231

608
rtc.cpp
View File

@@ -1,608 +0,0 @@
/*
* DS RTC Library: DS1307 and DS3231 driver library
* (C) 2011 Akafugu Corporation
*
* 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 2 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.
*
*/
/*
* DS1307 register map
*
* 00h-06h: seconds, minutes, hours, day-of-week, date, month, year (all in BCD)
* bit 7 of seconds enables/disables clock
* bit 6 of hours toggles 12/24h mode (1 for 12h, 0 for 24h)
* when 12h mode is selected bit 5 is high for PM, low for AM
* 07h: control
* bit7: OUT
* bit6: 0
* bit5: 0
* bit4: SQWE
* bit3: 0
* bit2: 0
* bit1: RS0
* bit0: RS1
* 08h-3fh: 56 bytes of SRAM
*
* DS3231 register map
*
* 00h-06h: seconds, minutes, hours, day-of-week, date, month, year (all in BCD)
* bit 7 should be set to zero: The DS3231 clock is always running
* 07h: A1M1 Alarm 1 seconds
* 08h: A1M2 Alarm 1 minutes
* 09h: A1M3 Alarm 1 hour (bit6 is am/pm flag in 12h mode)
* 0ah: A1M4 Alarm 1 day/date (bit6: 1 for day, 0 for date)
* 0bh: A2M2 Alarm 2 minutes
* 0ch: A2M3 Alarm 2 hour (bit6 is am/pm flag in 12h mode)
* 0dh: A2M4 Alarm 2 day/data (bit6: 1 for day, 0 for date)
* <see data sheet page12 for Alarm register mask bit tables:
* for alarm when hours, minutes and seconds match set 1000 for alarm 1>
* 0eh: control
* bit7: !EOSC
* bit6: BBSQW
* bit5: CONV
* bit4: RS2
* bit3: RS1
* bit2: INTCN
* bit1: A2IE
* bit0: A1IE
* 0fh: control/status
* bit7: OSF
* bit6: 0
* bit5: 0
* bit4: 0
* bit3: EN32kHz
* bit2: BSY
* bit1: A2F alarm 2 flag
* bit0: A1F alarm 1 flag
* 10h: aging offset (signed)
* 11h: MSB of temp (signed)
* 12h: LSB of temp in bits 7 and 6 (0.25 degrees for each 00, 01, 10, 11)
*
*/
#include <avr/io.h>
#define TRUE 1
#define FALSE 0
#include "rtc.h"
#define RTC_ADDR 0x68 // I2C address
#define CH_BIT 7 // clock halt bit
// statically allocated structure for time value
struct rtc_tm _rtc_tm;
uint8_t dec2bcd(uint8_t d)
{
return ((d/10 * 16) + (d % 10));
}
uint8_t bcd2dec(uint8_t b)
{
return ((b/16 * 10) + (b % 16));
}
uint8_t rtc_read_byte(uint8_t offset)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(offset);
twi_end_transmission();
twi_request_from(RTC_ADDR, 1);
return twi_receive();
}
void rtc_write_byte(uint8_t b, uint8_t offset)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(offset);
twi_send_byte(b);
twi_end_transmission();
}
static bool s_is_ds1307 = false;
static bool s_is_ds3231 = false;
void rtc_init(void)
{
// Attempt autodetection:
// 1) Read and save temperature register
// 2) Write a value to temperature register
// 3) Read back the value
// equal to the one written: DS1307, write back saved value and return
// different from written: DS3231
uint8_t temp1 = rtc_read_byte(0x11);
uint8_t temp2 = rtc_read_byte(0x12);
rtc_write_byte(0xee, 0x11);
rtc_write_byte(0xdd, 0x12);
if (rtc_read_byte(0x11) == 0xee && rtc_read_byte(0x12) == 0xdd) {
s_is_ds1307 = true;
// restore values
rtc_write_byte(temp1, 0x11);
rtc_write_byte(temp2, 0x12);
}
else {
s_is_ds3231 = true;
}
}
// Autodetection
bool rtc_is_ds1307(void) { return s_is_ds1307; }
bool rtc_is_ds3231(void) { return s_is_ds3231; }
// Autodetection override
void rtc_set_ds1307(void) { s_is_ds1307 = true; s_is_ds3231 = false; }
void rtc_set_ds3231(void) { s_is_ds1307 = false; s_is_ds3231 = true; }
struct rtc_tm* rtc_get_time(void)
{
uint8_t rtc[9];
uint8_t century = 0;
// read 7 bytes starting from register 0
// sec, min, hour, day-of-week, date, month, year
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0);
twi_end_transmission();
twi_request_from(RTC_ADDR, 7);
for (uint8_t i = 0; i < 7; i++) {
rtc[i] = twi_receive();
}
twi_end_transmission();
// Clear clock halt bit from read data
// This starts the clock for a DS1307, and has no effect for a DS3231
rtc[0] &= ~(_BV(CH_BIT)); // clear bit
_rtc_tm.sec = bcd2dec(rtc[0]);
_rtc_tm.min = bcd2dec(rtc[1]);
_rtc_tm.hour = bcd2dec(rtc[2]);
_rtc_tm.mday = bcd2dec(rtc[4]);
_rtc_tm.mon = bcd2dec(rtc[5] & 0x1F); // returns 1-12
century = (rtc[5] & 0x80) >> 7;
_rtc_tm.year = century == 1 ? 2000 + bcd2dec(rtc[6]) : 1900 + bcd2dec(rtc[6]); // year 0-99
_rtc_tm.wday = bcd2dec(rtc[3]); // returns 1-7
if (_rtc_tm.hour == 0) {
_rtc_tm.twelveHour = 0;
_rtc_tm.am = 1;
} else if (_rtc_tm.hour < 12) {
_rtc_tm.twelveHour = _rtc_tm.hour;
_rtc_tm.am = 1;
} else {
_rtc_tm.twelveHour = _rtc_tm.hour - 12;
_rtc_tm.am = 0;
}
return &_rtc_tm;
}
void rtc_get_time_s(uint8_t* hour, uint8_t* min, uint8_t* sec)
{
uint8_t rtc[9];
// read 7 bytes starting from register 0
// sec, min, hour, day-of-week, date, month, year
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0);
twi_end_transmission();
twi_request_from(RTC_ADDR, 7);
for(uint8_t i=0; i<7; i++) {
rtc[i] = twi_receive();
}
twi_end_transmission();
if (sec) *sec = bcd2dec(rtc[0]);
if (min) *min = bcd2dec(rtc[1]);
if (hour) *hour = bcd2dec(rtc[2]);
}
// fixme: support 12-hour mode for setting time
void rtc_set_time(struct rtc_tm* tm_)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0);
uint8_t century;
if (tm_->year > 2000) {
century = 0x80;
tm_->year = tm_->year - 2000;
} else {
century = 0;
tm_->year = tm_->year - 1900;
}
// clock halt bit is 7th bit of seconds: this is always cleared to start the clock
twi_send_byte(dec2bcd(tm_->sec)); // seconds
twi_send_byte(dec2bcd(tm_->min)); // minutes
twi_send_byte(dec2bcd(tm_->hour)); // hours
twi_send_byte(dec2bcd(tm_->wday)); // day of week
twi_send_byte(dec2bcd(tm_->mday)); // day
twi_send_byte(dec2bcd(tm_->mon) + century); // month
twi_send_byte(dec2bcd(tm_->year)); // year
twi_end_transmission();
}
void rtc_set_time_s(uint8_t hour, uint8_t min, uint8_t sec)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0);
// clock halt bit is 7th bit of seconds: this is always cleared to start the clock
twi_send_byte(dec2bcd(sec)); // seconds
twi_send_byte(dec2bcd(min)); // minutes
twi_send_byte(dec2bcd(hour)); // hours
twi_end_transmission();
}
// DS1307 only (has no effect when run on DS3231)
// halt/start the clock
// 7th bit of register 0 (second register)
// 0 = clock is running
// 1 = clock is not running
void rtc_run_clock(bool run)
{
if (s_is_ds3231) return;
uint8_t b = rtc_read_byte(0x0);
if (run)
b &= ~(_BV(CH_BIT)); // clear bit
else
b |= _BV(CH_BIT); // set bit
rtc_write_byte(b, 0x0);
}
// DS1307 only
// Returns true if the clock is running, false otherwise
// For DS3231, it always returns true
bool rtc_is_clock_running(void)
{
if (s_is_ds3231) return true;
uint8_t b = rtc_read_byte(0x0);
if (b & _BV(CH_BIT)) return false;
return true;
}
void ds3231_get_temp_int(int8_t* i, uint8_t* f)
{
uint8_t msb, lsb;
*i = 0;
*f = 0;
if (s_is_ds1307) return; // only valid on DS3231
twi_begin_transmission(RTC_ADDR);
// temp registers 0x11 and 0x12
twi_send_byte(0x11);
twi_end_transmission();
twi_request_from(RTC_ADDR, 2);
if (twi_available()) {
msb = twi_receive(); // integer part (in twos complement)
lsb = twi_receive(); // fraction part
// integer part in entire byte
*i = msb;
// fractional part in top two bits (increments of 0.25)
*f = (lsb >> 6) * 25;
// float value can be read like so:
// float temp = ((((short)msb << 8) | (short)lsb) >> 6) / 4.0f;
}
}
void rtc_force_temp_conversion(uint8_t block)
{
if (s_is_ds1307) return; // only valid on DS3231
// read control register (0x0E)
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_end_transmission();
twi_request_from(RTC_ADDR, 1);
uint8_t ctrl = twi_receive();
ctrl |= 0b00100000; // Set CONV bit
// write new control register value
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_send_byte(ctrl);
twi_end_transmission();
if (!block) return;
// Temp conversion is ready when control register becomes 0
do {
// Block until CONV is 0
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_end_transmission();
twi_request_from(RTC_ADDR, 1);
} while ((twi_receive() & 0b00100000) != 0);
}
#define DS1307_SRAM_ADDR 0x08
// SRAM: 56 bytes from address 0x08 to 0x3f (DS1307-only)
void rtc_get_sram(uint8_t* data)
{
// cannot receive 56 bytes in one go, because of the TWI library buffer limit
// so just receive one at a time for simplicity
for(int i=0;i<56;i++)
data[i] = rtc_get_sram_byte(i);
}
void rtc_set_sram(uint8_t *data)
{
// cannot send 56 bytes in one go, because of the TWI library buffer limit
// so just send one at a time for simplicity
for(int i=0;i<56;i++)
rtc_set_sram_byte(data[i], i);
}
uint8_t rtc_get_sram_byte(uint8_t offset)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(DS1307_SRAM_ADDR + offset);
twi_end_transmission();
twi_request_from(RTC_ADDR, 1);
return twi_receive();
}
void rtc_set_sram_byte(uint8_t b, uint8_t offset)
{
twi_begin_transmission(RTC_ADDR);
twi_send_byte(DS1307_SRAM_ADDR + offset);
twi_send_byte(b);
twi_end_transmission();
}
void rtc_SQW_enable(bool enable)
{
if (s_is_ds1307) {
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x07);
twi_end_transmission();
// read control
twi_request_from(RTC_ADDR, 1);
uint8_t control = twi_receive();
if (enable)
control |= 0b00010000; // set SQWE to 1
else
control &= ~0b00010000; // set SQWE to 0
// write control back
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x07);
twi_send_byte(control);
twi_end_transmission();
}
else { // DS3231
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_end_transmission();
// read control
twi_request_from(RTC_ADDR, 1);
uint8_t control = twi_receive();
if (enable) {
control |= 0b01000000; // set BBSQW to 1
control &= ~0b00000100; // set INTCN to 0
}
else {
control &= ~0b01000000; // set BBSQW to 0
}
// write control back
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_send_byte(control);
twi_end_transmission();
}
}
void rtc_SQW_set_freq(enum RTC_SQW_FREQ freq)
{
if (s_is_ds1307) {
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x07);
twi_end_transmission();
// read control (uses bits 0 and 1)
twi_request_from(RTC_ADDR, 1);
uint8_t control = twi_receive();
control &= ~0b00000011; // Set to 0
control |= freq; // Set freq bitmask
// write control back
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x07);
twi_send_byte(control);
twi_end_transmission();
}
else { // DS3231
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_end_transmission();
// read control (uses bits 3 and 4)
twi_request_from(RTC_ADDR, 1);
uint8_t control = twi_receive();
control &= ~0b00011000; // Set to 0
control |= (freq << 4); // Set freq bitmask
// write control back
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0E);
twi_send_byte(control);
twi_end_transmission();
}
}
void rtc_osc32kHz_enable(bool enable)
{
if (!s_is_ds3231) return;
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0F);
twi_end_transmission();
// read status
twi_request_from(RTC_ADDR, 1);
uint8_t status = twi_receive();
if (enable)
status |= 0b00001000; // set to 1
else
status &= ~0b00001000; // Set to 0
// write status back
twi_begin_transmission(RTC_ADDR);
twi_send_byte(0x0F);
twi_send_byte(status);
twi_end_transmission();
}
// Alarm functionality
// fixme: should decide if "alarm disabled" mode should be available, or if alarm should always be enabled
// at 00:00:00. Currently, "alarm disabled" only works for ds3231
void rtc_reset_alarm(void)
{
if (s_is_ds1307) {
rtc_set_sram_byte(0, 0); // hour
rtc_set_sram_byte(0, 1); // minute
rtc_set_sram_byte(0, 2); // second
}
else {
// writing 0 to bit 7 of all four alarm 1 registers disables alarm
rtc_write_byte(0, 0x07); // second
rtc_write_byte(0, 0x08); // minute
rtc_write_byte(0, 0x09); // hour
rtc_write_byte(0, 0x0a); // day
}
}
// fixme: add an option to set whether or not the INTCN and Interrupt Enable flag is set when setting the alarm
void rtc_set_alarm_s(uint8_t hour, uint8_t min, uint8_t sec)
{
if (hour > 23) return;
if (min > 59) return;
if (sec > 59) return;
if (s_is_ds1307) {
rtc_set_sram_byte(hour, 0); // hour
rtc_set_sram_byte(min, 1); // minute
rtc_set_sram_byte(sec, 2); // second
}
else {
/*
* 07h: A1M1:0 Alarm 1 seconds
* 08h: A1M2:0 Alarm 1 minutes
* 09h: A1M3:0 Alarm 1 hour (bit6 is am/pm flag in 12h mode)
* 0ah: A1M4:1 Alarm 1 day/date (bit6: 1 for day, 0 for date)
* Sets alarm to fire when hour, minute and second matches
*/
rtc_write_byte(dec2bcd(sec), 0x07); // second
rtc_write_byte(dec2bcd(min), 0x08); // minute
rtc_write_byte(dec2bcd(hour), 0x09); // hour
rtc_write_byte(0b10000001, 0x0a); // day (upper bit must be set)
// clear alarm flag
uint8_t val = rtc_read_byte(0x0f);
rtc_write_byte(val & ~0b00000001, 0x0f);
}
}
void rtc_set_alarm(struct rtc_tm* tm_)
{
if (!tm_) return;
rtc_set_alarm_s(tm_->hour, tm_->min, tm_->sec);
}
void rtc_get_alarm_s(uint8_t* hour, uint8_t* min, uint8_t* sec)
{
if (s_is_ds1307) {
if (hour) *hour = rtc_get_sram_byte(0);
if (min) *min = rtc_get_sram_byte(1);
if (sec) *sec = rtc_get_sram_byte(2);
}
else {
*sec = bcd2dec(rtc_read_byte(0x07) & ~0b10000000);
*min = bcd2dec(rtc_read_byte(0x08) & ~0b10000000);
*hour = bcd2dec(rtc_read_byte(0x09) & ~0b10000000);
}
}
struct rtc_tm* rtc_get_alarm(void)
{
uint8_t hour, min, sec;
rtc_get_alarm_s(&hour, &min, &sec);
_rtc_tm.hour = hour;
_rtc_tm.min = min;
_rtc_tm.sec = sec;
return &_rtc_tm;
}
bool rtc_check_alarm(void)
{
if (s_is_ds1307) {
uint8_t hour = rtc_get_sram_byte(0);
uint8_t min = rtc_get_sram_byte(1);
uint8_t sec = rtc_get_sram_byte(2);
uint8_t cur_hour, cur_min, cur_sec;
rtc_get_time_s(&cur_hour, &cur_min, &cur_sec);
if (cur_hour == hour && cur_min == min && cur_sec == sec)
return true;
return false;
}
else {
// Alarm 1 flag (A1F) in bit 0
uint8_t val = rtc_read_byte(0x0f);
// clear flag when set
if (val & 1)
rtc_write_byte(val & ~0b00000001, 0x0f);
return val & 1 ? 1 : 0;
}
}

107
rtc.h
View File

@@ -1,107 +0,0 @@
/*
* DS RTC Library: DS1307 and DS3231 driver library
* (C) 2011 Akafugu Corporation
*
* 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 2 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.
*
*/
#ifndef DS1307_H
#define DS1307_H
#include <stdbool.h>
#include <avr/io.h>
#include "twi.h"
#define DS1307_SLAVE_ADDR 0b11010000
/** Time structure
*
* Both 24-hour and 12-hour time is stored, and is always updated when rtc_get_time is called.
*
* When setting time and alarm, 24-hour mode is always used.
*
* If you run your clock in 12-hour mode:
* - set time hour to store in twelveHour and set am to true or false.
* - call rtc_12h_translate (this will put the correct value in hour, so you don't have to
* calculate it yourself.
* - call rtc_set_alarm or rtc_set_clock
*
* Note that rtc_set_clock_s, rtc_set_alarm_s, rtc_get_time_s, rtc_set_alarm_s always operate in 24-hour mode
* and translation has to be done manually (you can call rtc_24h_to_12h to perform the calculation)
*
*/
struct rtc_tm {
int sec; // 0 to 59
int min; // 0 to 59
int hour; // 0 to 23
int mday; // 1 to 31
int mon; // 1 to 12
int year; // year-99
int wday; // 1-7
// 12-hour clock data
bool am; // true for AM, false for PM
int twelveHour; // 12 hour clock time
};
// statically allocated
extern struct rtc_tm _rtc_tm;
// Initialize the RTC and autodetect type (DS1307 or DS3231)
void rtc_init(void);
// Autodetection
bool rtc_is_ds1307(void);
bool rtc_is_ds3231(void);
void rtc_set_ds1307(void);
void rtc_set_ds3231(void);
// Get/set time
// Gets the time: Supports both 24-hour and 12-hour mode
struct rtc_tm* rtc_get_time(void);
// Gets the time: 24-hour mode only
void rtc_get_time_s(uint8_t* hour, uint8_t* min, uint8_t* sec);
// Sets the time: Supports both 24-hour and 12-hour mode
void rtc_set_time(struct rtc_tm* tm_);
// Sets the time: Supports 12-hour mode only
void rtc_set_time_s(uint8_t hour, uint8_t min, uint8_t sec);
// start/stop clock running (DS1307 only)
void rtc_run_clock(bool run);
bool rtc_is_clock_running(void);
// Read Temperature (DS3231 only)
void ds3231_get_temp_int(int8_t* i, uint8_t* f);
void rtc_force_temp_conversion(uint8_t block);
// SRAM read/write DS1307 only
void rtc_get_sram(uint8_t* data);
void rtc_set_sram(uint8_t *data);
uint8_t rtc_get_sram_byte(uint8_t offset);
void rtc_set_sram_byte(uint8_t b, uint8_t offset);
// Auxillary functions
enum RTC_SQW_FREQ { FREQ_1 = 0, FREQ_1024, FREQ_4096, FREQ_8192 };
void rtc_SQW_enable(bool enable);
void rtc_SQW_set_freq(enum RTC_SQW_FREQ freq);
void rtc_osc32kHz_enable(bool enable);
// Alarm functionality
void rtc_reset_alarm(void);
void rtc_set_alarm(struct rtc_tm* tm_);
void rtc_set_alarm_s(uint8_t hour, uint8_t min, uint8_t sec);
struct rtc_tm* rtc_get_alarm(void);
void rtc_get_alarm_s(uint8_t* hour, uint8_t* min, uint8_t* sec);
bool rtc_check_alarm(void);
#endif

View File

@@ -1,107 +0,0 @@
#include "systime.h"
//////////////////////////////////////////////////////////////////////////
// DST magic by Edgar Bonet
int SysTime::euDST( const time_t *pTime, int32_t *pZ )
{
static_cast<void>( pZ );
uint32_t t = *pTime;
if( static_cast<uint8_t>( t >> 24 ) >= 194 )
t -= 3029443200U;
t = ( t + 655513200 ) / 604800 * 28;
if( static_cast<uint16_t>( t % 1461 ) < 856 )
return ONE_HOUR;
return 0;
}
//////////////////////////////////////////////////////////////////////////
bool SysTime::init()
{
twi_init_master();
rtc_init();
if( !rtc_is_ds3231() )
return false;
set_zone( 1 * ONE_HOUR );
set_dst( euDST );
syncSysTime();
return true;
}
//////////////////////////////////////////////////////////////////////////
void SysTime::syncSysTime()
{
tm sTime = getTime();
set_system_time( mk_gmtime( &sTime ) );
}
//////////////////////////////////////////////////////////////////////////
bool SysTime::checkSync()
{
time_t timeNow = time( nullptr );
tm *ptmUTC = gmtime( &timeNow );
rtc_tm *ptmRtcTime = rtc_get_time();
if( ptmUTC->tm_sec != ptmRtcTime->sec || ptmUTC->tm_min != ptmRtcTime->min || ptmUTC->tm_hour != ptmRtcTime->hour )
return false;
if( ptmUTC->tm_mday != ptmRtcTime->mday || ( ptmUTC->tm_mon + 1 ) != ptmRtcTime->mon || ( ptmUTC->tm_year + 1900 ) != ptmRtcTime->year )
return false;
return true;
}
//////////////////////////////////////////////////////////////////////////
void SysTime::tick()
{
system_tick();
}
//////////////////////////////////////////////////////////////////////////
tm SysTime::getTime()
{
tm sTime;
rtc_tm *ptmTime = rtc_get_time();
sTime.tm_sec = ptmTime->sec;
sTime.tm_min = ptmTime->min;
sTime.tm_hour = ptmTime->hour;
sTime.tm_mday = ptmTime->mday;
sTime.tm_mon = ptmTime->mon - 1;
sTime.tm_year = ptmTime->year - 1900;
sTime.tm_isdst = 0;
time_t timeUTC = mk_gmtime( &sTime );
sTime = *( gmtime( &timeUTC ) );
return sTime;
}
//////////////////////////////////////////////////////////////////////////
void SysTime::setTime( const tm &sTime )
{
rtc_tm *pRtcTime = rtc_get_time();
pRtcTime->sec = sTime.tm_sec;
pRtcTime->min = sTime.tm_min;
pRtcTime->hour = sTime.tm_hour;
pRtcTime->mday = sTime.tm_mday;
pRtcTime->mon = sTime.tm_mon + 1;
pRtcTime->year = sTime.tm_year + 1900;
pRtcTime->wday = sTime.tm_wday + 1;
pRtcTime->am = ( sTime.tm_hour < 12 ) ? true : false;
pRtcTime->twelveHour = ( sTime.tm_hour == 0 ) ? 12 : ( ( sTime.tm_hour > 12 ) ? ( sTime.tm_hour - 12 ) : sTime.tm_hour );
rtc_set_time( pRtcTime );
syncSysTime();
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright (c) by BlackMark 2017
* Date 17/12/2016
* Version 1.0
*/
#ifndef SYSTIME_H
#define SYSTIME_H
#include <time.h>
#include "twi.h"
#include "rtc.h"
class SysTime
{
private:
static int euDST( const time_t *pTime, int32_t *pZ );
public:
static bool init();
static void syncSysTime();
static bool checkSync();
static void tick();
static tm getTime();
static void setTime( const tm &sTime );
};
#endif

View File

@@ -1,476 +0,0 @@
/*
twi.c - TWI/I2C library for Wiring & Arduino
Copyright (c) 2006 Nicholas Zambetti. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <math.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <compat/twi.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#include "twi-lowlevel.h"
static volatile uint8_t twi_state;
static uint8_t twi_slarw;
static void (*twi_onSlaveTransmit)(void);
static void (*twi_onSlaveReceive)(uint8_t*, int);
static uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_masterBufferIndex;
static uint8_t twi_masterBufferLength;
static uint8_t twi_txBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_txBufferIndex;
static volatile uint8_t twi_txBufferLength;
static uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH];
static volatile uint8_t twi_rxBufferIndex;
static volatile uint8_t twi_error;
/*
* Function twi_init
* Desc readys twi pins and sets twi bitrate
* Input none
* Output none
*/
void twi_init(void)
{
// initialize state
twi_state = TWI_READY;
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
// activate internal pull-ups for twi
// as per note from atmega8 manual pg167
sbi(PORTC, 4);
sbi(PORTC, 5);
#else
// activate internal pull-ups for twi
// as per note from atmega128 manual pg204
sbi(PORTD, 0);
sbi(PORTD, 1);
#endif
// initialize twi prescaler and bit rate
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);
TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;
/* twi bit rate formula from atmega128 manual pg 204
SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
note: TWBR should be 10 or higher for master mode
It is 72 for a 16mhz Wiring board with 100kHz TWI */
// enable twi module, acks, and twi interrupt
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}
/*
* Function twi_slaveInit
* Desc sets slave address and enables interrupt
* Input none
* Output none
*/
void twi_setAddress(uint8_t address)
{
// set twi slave address (skip over TWGCE bit)
TWAR = address << 1;
}
/*
* Function twi_readFrom
* Desc attempts to become twi bus master and read a
* series of bytes from a device on the bus
* Input address: 7bit i2c device address
* data: pointer to byte array
* length: number of bytes to read into array
* Output number of bytes read
*/
uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 0;
}
// wait until twi is ready, become master receiver
while(TWI_READY != twi_state){
continue;
}
twi_state = TWI_MRX;
// reset error state (0xFF.. no error occured)
twi_error = 0xFF;
// initialize buffer iteration vars
twi_masterBufferIndex = 0;
twi_masterBufferLength = length-1; // This is not intuitive, read on...
// On receive, the previously configured ACK/NACK setting is transmitted in
// response to the received byte before the interrupt is signalled.
// Therefor we must actually set NACK when the _next_ to last byte is
// received, causing that NACK to be sent in response to receiving the last
// expected byte of data.
// build sla+w, slave device address + w bit
twi_slarw = TW_READ;
twi_slarw |= address << 1;
// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
// wait for read operation to complete
while(TWI_MRX == twi_state){
continue;
}
if (twi_masterBufferIndex < length)
length = twi_masterBufferIndex;
// copy twi buffer to data
for(i = 0; i < length; ++i){
data[i] = twi_masterBuffer[i];
}
return length;
}
/*
* Function twi_writeTo
* Desc attempts to become twi bus master and write a
* series of bytes to a device on the bus
* Input address: 7bit i2c device address
* data: pointer to byte array
* length: number of bytes in array
* wait: boolean indicating to wait for write or not
* Output 0 .. success
* 1 .. length to long for buffer
* 2 .. address send, NACK received
* 3 .. data send, NACK received
* 4 .. other twi error (lost bus arbitration, bus error, ..)
*/
uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 1;
}
// wait until twi is ready, become master transmitter
while(TWI_READY != twi_state){
continue;
}
twi_state = TWI_MTX;
// reset error state (0xFF.. no error occured)
twi_error = 0xFF;
// initialize buffer iteration vars
twi_masterBufferIndex = 0;
twi_masterBufferLength = length;
// copy data to twi buffer
for(i = 0; i < length; ++i){
twi_masterBuffer[i] = data[i];
}
// build sla+w, slave device address + w bit
twi_slarw = TW_WRITE;
twi_slarw |= address << 1;
// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
// wait for write operation to complete
while(wait && (TWI_MTX == twi_state)){
continue;
}
if (twi_error == 0xFF)
return 0; // success
else if (twi_error == TW_MT_SLA_NACK)
return 2; // error: address send, nack received
else if (twi_error == TW_MT_DATA_NACK)
return 3; // error: data send, nack received
else
return 4; // other twi error
}
/*
* Function twi_transmit
* Desc fills slave tx buffer with data
* must be called in slave tx event callback
* Input data: pointer to byte array
* length: number of bytes in array
* Output 1 length too long for buffer
* 2 not slave transmitter
* 0 ok
*/
uint8_t twi_transmit(uint8_t* data, uint8_t length)
{
uint8_t i;
// ensure data will fit into buffer
if(TWI_BUFFER_LENGTH < length){
return 1;
}
// ensure we are currently a slave transmitter
if(TWI_STX != twi_state){
return 2;
}
// set length and copy data into tx buffer
twi_txBufferLength = length;
for(i = 0; i < length; ++i){
twi_txBuffer[i] = data[i];
}
return 0;
}
/*
* Function twi_attachSlaveRxEvent
* Desc sets function called before a slave read operation
* Input function: callback function to use
* Output none
*/
void twi_attachSlaveRxEvent( void (*function)(uint8_t*, int) )
{
twi_onSlaveReceive = function;
}
/*
* Function twi_attachSlaveTxEvent
* Desc sets function called before a slave write operation
* Input function: callback function to use
* Output none
*/
void twi_attachSlaveTxEvent( void (*function)(void) )
{
twi_onSlaveTransmit = function;
}
/*
* Function twi_reply
* Desc sends byte or readys receive line
* Input ack: byte indicating to ack or to nack
* Output none
*/
void twi_reply(uint8_t ack)
{
// transmit master read ready signal, with or without ack
if(ack){
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
}else{
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
}
}
/*
* Function twi_stop
* Desc relinquishes bus master status
* Input none
* Output none
*/
void twi_stop(void)
{
// send stop condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);
// wait for stop condition to be exectued on bus
// TWINT is not set after a stop condition!
while(TWCR & _BV(TWSTO)){
continue;
}
// update twi state
twi_state = TWI_READY;
}
/*
* Function twi_releaseBus
* Desc releases bus control
* Input none
* Output none
*/
void twi_releaseBus(void)
{
// release bus
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
// update twi state
twi_state = TWI_READY;
}
SIGNAL(TWI_vect)
{
switch(TW_STATUS){
// All Master
case TW_START: // sent start condition
case TW_REP_START: // sent repeated start condition
// copy device address and r/w bit to output register and ack
TWDR = twi_slarw;
twi_reply(1);
break;
// Master Transmitter
case TW_MT_SLA_ACK: // slave receiver acked address
case TW_MT_DATA_ACK: // slave receiver acked data
// if there is data to send, send it, otherwise stop
if(twi_masterBufferIndex < twi_masterBufferLength){
// copy data to output register and ack
TWDR = twi_masterBuffer[twi_masterBufferIndex++];
twi_reply(1);
}else{
twi_stop();
}
break;
case TW_MT_SLA_NACK: // address sent, nack received
twi_error = TW_MT_SLA_NACK;
twi_stop();
break;
case TW_MT_DATA_NACK: // data sent, nack received
twi_error = TW_MT_DATA_NACK;
twi_stop();
break;
case TW_MT_ARB_LOST: // lost bus arbitration
twi_error = TW_MT_ARB_LOST;
twi_releaseBus();
break;
// Master Receiver
case TW_MR_DATA_ACK: // data received, ack sent
// put byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
case TW_MR_SLA_ACK: // address sent, ack received
// ack if more bytes are expected, otherwise nack
if(twi_masterBufferIndex < twi_masterBufferLength){
twi_reply(1);
}else{
twi_reply(0);
}
break;
case TW_MR_DATA_NACK: // data received, nack sent
// put final byte into buffer
twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
case TW_MR_SLA_NACK: // address sent, nack received
twi_stop();
break;
// TW_MR_ARB_LOST handled by TW_MT_ARB_LOST case
// Slave Receiver
case TW_SR_SLA_ACK: // addressed, returned ack
case TW_SR_GCALL_ACK: // addressed generally, returned ack
case TW_SR_ARB_LOST_SLA_ACK: // lost arbitration, returned ack
case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
// enter slave receiver mode
twi_state = TWI_SRX;
// indicate that rx buffer can be overwritten and ack
twi_rxBufferIndex = 0;
twi_reply(1);
break;
case TW_SR_DATA_ACK: // data received, returned ack
case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
// if there is still room in the rx buffer
if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
// put byte in buffer and ack
twi_rxBuffer[twi_rxBufferIndex++] = TWDR;
twi_reply(1);
}else{
// otherwise nack
twi_reply(0);
}
break;
case TW_SR_STOP: // stop or repeated start condition received
// put a null char after data if there's room
if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
twi_rxBuffer[twi_rxBufferIndex] = '\0';
}
// sends ack and stops interface for clock stretching
twi_stop();
// callback to user defined callback
twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
// since we submit rx buffer to "wire" library, we can reset it
twi_rxBufferIndex = 0;
// ack future responses and leave slave receiver state
twi_releaseBus();
break;
case TW_SR_DATA_NACK: // data received, returned nack
case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
// nack back at master
twi_reply(0);
break;
// Slave Transmitter
case TW_ST_SLA_ACK: // addressed, returned ack
case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
// enter slave transmitter mode
twi_state = TWI_STX;
// ready the tx buffer index for iteration
twi_txBufferIndex = 0;
// set tx buffer length to be zero, to verify if user changes it
twi_txBufferLength = 0;
// request for txBuffer to be filled and length to be set
// note: user must call twi_transmit(bytes, length) to do this
twi_onSlaveTransmit();
// if they didn't change buffer & length, initialize it
if(0 == twi_txBufferLength){
twi_txBufferLength = 1;
twi_txBuffer[0] = 0x00;
}
// transmit first byte from buffer, fall
case TW_ST_DATA_ACK: // byte sent, ack returned
// copy data to output register
TWDR = twi_txBuffer[twi_txBufferIndex++];
// if there is more to send, ack, otherwise nack
if(twi_txBufferIndex < twi_txBufferLength){
twi_reply(1);
}else{
twi_reply(0);
}
break;
case TW_ST_DATA_NACK: // received nack, we are done
case TW_ST_LAST_DATA: // received ack, but we are done already!
// ack future responses
twi_reply(1);
// leave slave receiver state
twi_state = TWI_READY;
break;
// All
case TW_NO_INFO: // no state information
break;
case TW_BUS_ERROR: // bus error, illegal stop/start
twi_error = TW_BUS_ERROR;
twi_stop();
break;
}
}

View File

@@ -1,55 +0,0 @@
/*
twi.h - TWI/I2C library for Wiring & Arduino
Copyright (c) 2006 Nicholas Zambetti. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef twi_h
#define twi_h
#include <inttypes.h>
#include "../clock.h"
//#define ATMEGA8
#ifndef TWI_FREQ
#define TWI_FREQ 100000L
#endif
#ifndef TWI_BUFFER_LENGTH
#define TWI_BUFFER_LENGTH 32
#endif
#define TWI_READY 0
#define TWI_MRX 1
#define TWI_MTX 2
#define TWI_SRX 3
#define TWI_STX 4
void twi_init(void);
void twi_setAddress(uint8_t);
uint8_t twi_readFrom(uint8_t, uint8_t*, uint8_t);
uint8_t twi_writeTo(uint8_t, uint8_t*, uint8_t, uint8_t);
uint8_t twi_transmit(uint8_t*, uint8_t);
void twi_attachSlaveRxEvent( void (*)(uint8_t*, int) );
void twi_attachSlaveTxEvent( void (*)(void) );
void twi_reply(uint8_t);
void twi_stop(void);
void twi_releaseBus(void);
#endif

226
twi.cpp
View File

@@ -1,226 +0,0 @@
/*
TwoWire.cpp - TWI/I2C library for Wiring & Arduino
Copyright (c) 2006 Nicholas Zambetti. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "twi-lowlevel.h"
#include "twi.h"
// local variables
uint8_t rxBuffer[BUFFER_LENGTH];
uint8_t rxBufferIndex = 0;
uint8_t rxBufferLength = 0;
uint8_t txAddress = 0;
uint8_t txBuffer[BUFFER_LENGTH];
uint8_t txBufferIndex = 0;
uint8_t txBufferLength = 0;
uint8_t transmitting = 0;
void (*user_onRequest)(void);
void (*user_onReceive)(int);
void onRequestService(void);
void onReceiveService(uint8_t*, int);
void twi_init_master(void)
{
rxBufferIndex = 0;
rxBufferLength = 0;
txBufferIndex = 0;
txBufferLength = 0;
twi_init();
}
void twi_init_slave(uint8_t address)
{
twi_setAddress(address);
twi_attachSlaveTxEvent(onRequestService);
twi_attachSlaveRxEvent(onReceiveService);
twi_init_master();
}
uint8_t twi_request_from(uint8_t address, uint8_t quantity)
{
// clamp to buffer length
if(quantity > BUFFER_LENGTH){
quantity = BUFFER_LENGTH;
}
// perform blocking read into buffer
uint8_t read = twi_readFrom(address, rxBuffer, quantity);
// set rx buffer iterator vars
rxBufferIndex = 0;
rxBufferLength = read;
return read;
}
void twi_begin_transmission(uint8_t address)
{
// indicate that we are transmitting
transmitting = 1;
// set address of targeted slave
txAddress = address;
// reset tx buffer iterator vars
txBufferIndex = 0;
txBufferLength = 0;
}
uint8_t twi_end_transmission(void)
{
// transmit buffer (blocking)
int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1);
// reset tx buffer iterator vars
txBufferIndex = 0;
txBufferLength = 0;
// indicate that we are done transmitting
transmitting = 0;
return ret;
}
// must be called in:
// slave tx event callback
// or after beginTransmission(address)
void twi_send_byte(uint8_t data)
{
if(transmitting){
// in master transmitter mode
// don't bother if buffer is full
if(txBufferLength >= BUFFER_LENGTH){
return;
}
// put byte in tx buffer
txBuffer[txBufferIndex] = data;
++txBufferIndex;
// update amount in buffer
txBufferLength = txBufferIndex;
}else{
// in slave send mode
// reply to master
twi_transmit(&data, 1);
}
}
// must be called in:
// slave tx event callback
// or after beginTransmission(address)
void twi_send(uint8_t* data, uint8_t quantity)
{
if(transmitting){
// in master transmitter mode
for(uint8_t i = 0; i < quantity; ++i){
twi_send_byte(data[i]);
}
}else{
// in slave send mode
// reply to master
twi_transmit(data, quantity);
}
}
// must be called in:
// slave tx event callback
// or after beginTransmission(address)
void twi_send_char(char* data)
{
twi_send((uint8_t*)data, strlen(data));
}
// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
uint8_t twi_available(void)
{
return rxBufferLength - rxBufferIndex;
}
// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
uint8_t twi_receive(void)
{
// default to returning null char
// for people using with char strings
uint8_t value = '\0';
// get each successive byte on each call
if(rxBufferIndex < rxBufferLength){
value = rxBuffer[rxBufferIndex];
++rxBufferIndex;
}
return value;
}
// behind the scenes function that is called when data is received
void onReceiveService(uint8_t* inBytes, int numBytes)
{
// don't bother if user hasn't registered a callback
if(!user_onReceive){
return;
}
// don't bother if rx buffer is in use by a master requestFrom() op
// i know this drops data, but it allows for slight stupidity
// meaning, they may not have read all the master requestFrom() data yet
if(rxBufferIndex < rxBufferLength){
return;
}
// copy twi rx buffer into local read buffer
// this enables new reads to happen in parallel
for(uint8_t i = 0; i < numBytes; ++i){
rxBuffer[i] = inBytes[i];
}
// set rx iterator vars
rxBufferIndex = 0;
rxBufferLength = numBytes;
// alert user program
user_onReceive(numBytes);
}
// behind the scenes function that is called when data is requested
void onRequestService(void)
{
// don't bother if user hasn't registered a callback
if(!user_onRequest){
return;
}
// reset tx buffer iterator vars
// !!! this will kill any pending pre-master sendTo() activity
txBufferIndex = 0;
txBufferLength = 0;
// alert user program
user_onRequest();
}
// sets function called on slave write
void twi_set_on_receive( void (*function)(int) )
{
user_onReceive = function;
}
// sets function called on slave read
void twi_set_on_request( void (*function)(void) )
{
user_onRequest = function;
}

40
twi.h
View File

@@ -1,40 +0,0 @@
/*
TwoWire.h - TWI/I2C library for Arduino & Wiring
Copyright (c) 2006 Nicholas Zambetti. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef TwoWire_h
#define TwoWire_h
#include <inttypes.h>
#define BUFFER_LENGTH 32
void twi_init_master(void);
void twi_init_slave(uint8_t);
void twi_begin_transmission(uint8_t);
uint8_t twi_end_transmission(void);
uint8_t twi_request_from(uint8_t, uint8_t);
void twi_send_byte(uint8_t);
void twi_send(uint8_t*, uint8_t);
void twi_send_char(char*);
uint8_t twi_available(void);
uint8_t twi_receive(void);
void twi_set_on_receive( void (*)(int) );
void twi_set_on_request( void (*)(void) );
#endif