Skip to content

Commit 4aa5d8d

Browse files
authored
Merge pull request m5stack#339 from ainyan03/rx8130_timer_pulse
Keep the RX8130 timer /IRQ pulse catchable and follow the datasheet re-arm sequence
2 parents 79ccb48 + 315d99b commit 4aa5d8d

3 files changed

Lines changed: 89 additions & 47 deletions

File tree

‎src/utility/RTC_Class.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace m5
4343

4444
/// Set timer IRQ
4545
/// @param timer_msec
46-
/// @return the set number of msec. (0 == disable)
46+
/// @return the set number of msec. 0 == disabled, or the timer could not be set (communication failure).
4747
std::uint32_t setTimerIRQ(std::uint32_t timer_msec);
4848

4949
// deprecated

‎src/utility/rtc/RTC_Base.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ namespace m5
8989

9090
/// Set timer IRQ
9191
/// @param milliseconds (0 == disable)
92-
/// @return the set number of milliseconds. (0 == disable)
92+
/// @return the set number of milliseconds. 0 == disabled, or the timer could not be set
93+
/// (communication failure; the driver then stops the timer on a best-effort basis).
9394
virtual std::uint32_t setTimerIRQ(std::uint32_t timer_msec) { return 0; };
9495

9596
/// Set alarm by time

‎src/utility/rtc/RX8130_Class.cpp‎

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -118,54 +118,95 @@ namespace m5
118118

119119
std::uint32_t RX8130_Class::setTimerIRQ(std::uint32_t msec)
120120
{
121-
// タイマー周期の除数
122-
uint32_t div = 1;
123-
// タイマー周期の乗数
124-
uint32_t mul = 1;
121+
// Source clocks in the order they are tried. period = mul_ms / div [ms].
122+
// max_ms = 65535 * period, so msec <= max_ms keeps msec * div within uint32.
123+
// max_cnt = min(65535, 0xFFFFFFFF / mul_ms), so cnt * mul_ms (the period) stays within uint32.
124+
struct clk_t { std::uint32_t mul_ms; std::uint32_t div; std::uint32_t max_ms; std::uint16_t max_cnt; std::uint8_t tsel; };
125+
static constexpr clk_t clks[] = {
126+
{ 1000, 64, 1023984, 65535, 0x01 }, // 64 Hz
127+
{ 1000, 1, 65535000, 65535, 0x02 }, // 1 Hz
128+
{ 60000, 1, 3932100000, 65535, 0x03 }, // 1/60 Hz
129+
{ 3600000, 1, 0xFFFFFFFF, 1193, 0x04 }, // 1/3600 Hz
130+
{ 1000, 4096, 15999, 65535, 0x00 }, // 4096 Hz (last resort: its /IRQ pulse is only 122us)
131+
};
132+
static constexpr std::size_t NCLK = sizeof(clks) / sizeof(clks[0]);
133+
// The /IRQ pulse auto-releases after 122us with the 4096Hz clock but 7.57ms with the others,
134+
// so take the finest non-4096Hz clock whose rounded count keeps the period error under 1/256
135+
// and has >= MIN_COUNT counts (the first countdown can be short by up to one source clock,
136+
// 1s for the 1/60Hz and 1/3600Hz clocks, so this bounds that to ~6% or less).
137+
static constexpr std::uint32_t MIN_COUNT = 16;
125138

126-
uint8_t tsel_bits = 0;
127-
if (msec < 65536 * 1000 / 4096) { // 約16秒
128-
tsel_bits = 0x00;
129-
div = 4096;
130-
} else if (msec < 65536 * 1000 / 64) { // 約1024秒(約17分)
131-
tsel_bits = 0x01;
132-
div = 64;
133-
} else if (msec < 65536 * 1000) { // 約65535秒(約18時間)
134-
tsel_bits = 0x02;
135-
} else if (msec < 65536 * 60) { // 約3,932,160秒(約45日)
136-
mul = 60;
137-
tsel_bits = 0x03;
138-
} else { // msec < 65536*3600 // 約39,321,600秒(約1年3ヶ月)
139-
mul = 3600;
140-
tsel_bits = 0x04;
139+
std::uint32_t cycle = 0;
140+
const clk_t* sel = nullptr;
141+
if (msec != 0) {
142+
bool overflowed = false; // a finer clock ran out of range: round up so the period never steps back
143+
for (std::size_t i = 0; i < NCLK; ++i) {
144+
const clk_t& c = clks[i];
145+
if (msec > c.max_ms) { overflowed = true; continue; }
146+
// Everything below is in units of msec * div: cnt counts of mul_ms each, err the remainder.
147+
std::uint32_t num = msec * c.div;
148+
std::uint32_t cnt = num / c.mul_ms;
149+
std::uint32_t err = num % c.mul_ms;
150+
if (overflowed ? (err != 0) : (err * 2 >= c.mul_ms)) { ++cnt; err = c.mul_ms - err; }
151+
if (cnt > c.max_cnt) { cnt = c.max_cnt; err = num - cnt * c.mul_ms; }
152+
// Accept when the error is within 1/256 (~0.39%) of the request (err < mul_ms, so no overflow).
153+
if (i + 1 == NCLK || (cnt >= MIN_COUNT && (err << 8) <= num)) {
154+
sel = &c; cycle = cnt; break;
155+
}
156+
}
157+
if (sel == nullptr) { return 0; } // unreachable (1/3600Hz covers all of uint32); fail safe = stay stopped
141158
}
142159

143-
std::uint32_t result = 0;
144-
std::uint8_t regdata[3];
145-
if (readRegister(0x1A, regdata, 3)) {
146-
mul *= 1000;
147-
uint32_t cycle = (msec * div + (mul >> 1)) / mul;
148-
if (cycle > 65535) { cycle = 65535; }
149-
result = cycle * mul / div;
150-
151-
regdata[0] = cycle & 0xff;
152-
regdata[1] = (cycle >> 8) & 0xff;
153-
if (cycle > 0) {
154-
// Clear timer select bits & TE flag
155-
std::uint8_t reg0x1C = regdata[2] & ~0x17;
156-
// Set timer select bits & TE flag
157-
reg0x1C |= 0x10 | tsel_bits;
158-
regdata[2] = reg0x1C;
159-
bitOn(0x1E, 0x10);
160-
} else {
161-
// Clear TE flag
162-
regdata[2] &= ~0x10;
163-
bitOff(0x1E, 0x10);
160+
// Sequence per datasheet Figure 48: TE=0 (+TSEL) -> clear TF -> TIE -> preset -> TE=1 last,
161+
// so the first event cannot precede TIE. On any I2C failure the timer is stopped (verified by
162+
// read-back where the bus allows it) and 0 is returned; the caller cannot tell that from a
163+
// requested stop, and if even the stop fails the hardware state is unknown.
164+
// 0x1D flags are write-0-to-clear (writing 1 is ignored, VBFF is read-only), so TF is cleared
165+
// with a single write that leaves the other flags untouched (a read-modify-write would drop
166+
// a flag raised in between).
167+
static constexpr std::uint8_t FLAG_CLEAR_TF = 0xAF;
168+
auto stop_timer = [this](void) -> bool {
169+
for (int retry = 0; retry < 3; ++retry) {
170+
std::uint8_t ext = 0, ctl = 0;
171+
if (bitOff(0x1C, 0x10) && bitOff(0x1E, 0x10)
172+
&& readRegister(0x1C, &ext, 1) && readRegister(0x1E, &ctl, 1)
173+
&& !(ext & 0x10) && !(ctl & 0x10)) { return true; }
164174
}
165-
writeRegister(0x1A, regdata, 3);
175+
return false;
176+
};
177+
std::uint8_t reg0x1C = 0;
178+
if (cycle == 0) {
179+
stop_timer();
180+
writeRegister8(0x1D, FLAG_CLEAR_TF);
181+
return 0;
166182
}
167-
168-
return result;
183+
bool ok = readRegister(0x1C, &reg0x1C, 1);
184+
if (ok) {
185+
reg0x1C = (reg0x1C & ~0x17) | sel->tsel;
186+
ok = writeRegister8(0x1C, reg0x1C)
187+
&& writeRegister8(0x1D, FLAG_CLEAR_TF)
188+
&& bitOn(0x1E, 0x10);
189+
}
190+
if (ok) {
191+
// While TE=0 the counter registers read back the preset, so verify the write took
192+
// (a corrupted preset was observed on a shared bus) and retry a few times.
193+
std::uint8_t regdata[2] = { (std::uint8_t)(cycle & 0xff), (std::uint8_t)((cycle >> 8) & 0xff) };
194+
ok = false;
195+
for (int retry = 0; retry < 3 && !ok; ++retry) {
196+
std::uint8_t verify[2] = { 0, 0 };
197+
ok = writeRegister(0x1A, regdata, 2)
198+
&& readRegister(0x1A, verify, 2)
199+
&& verify[0] == regdata[0] && verify[1] == regdata[1];
200+
}
201+
}
202+
if (ok) { ok = writeRegister8(0x1C, reg0x1C | 0x10); }
203+
if (!ok) {
204+
stop_timer();
205+
return 0;
206+
}
207+
// Actual period rounded to the nearest ms (cycle * mul_ms fits by max_cnt); never 0 while running.
208+
std::uint32_t result = (cycle * sel->mul_ms + (sel->div >> 1)) / sel->div;
209+
return result ? result : 1;
169210
}
170211

171212
int RX8130_Class::setAlarmIRQ(const rtc_date_t* date, const rtc_time_t* time)
@@ -239,15 +280,15 @@ namespace m5
239280
void RX8130_Class::clearIRQ(void)
240281
{
241282
if (isEnabled()) {
242-
bitOff(0x1D, 0x18);
283+
writeRegister8(0x1D, 0xA7); // W0C: clear TF and AF only
243284
}
244285
}
245286

246287
void RX8130_Class::disableIRQ(void)
247288
{
248289
if (isEnabled()) {
249290
bitOff(0x1E, 0x18);
250-
bitOff(0x1D, 0x18);
291+
writeRegister8(0x1D, 0xA7); // W0C: clear TF and AF only
251292
}
252293
}
253294

0 commit comments

Comments
 (0)