Skip to content

Commit 9c9357c

Browse files
author
jfpoilpret
committed
Improve async I2C callbacks and add related example
1 parent 38e7956 commit 9c9357c

File tree

3 files changed

+340
-15
lines changed

3 files changed

+340
-15
lines changed

cores/fastarduino/new_i2c_handler_atmega.h

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@
3939

4040
#define I2C_TRUE_ASYNC 1
4141

42-
// OPEN POINTS:
43-
//TODO - add callback registration including proxy/future reference (when future is done)
44-
// -> this will allow pushing events to be handled by main event loop
45-
// - maybe also need some device reference?
46-
4742
/**
4843
* Register the necessary ISR (Interrupt Service Routine) for an asynchronous
4944
* I2CManager to work properly.
@@ -64,7 +59,8 @@ ISR(TWI_vect) \
6459
*
6560
* @param MANAGER one of many asynchronous I2C managers defined in this header
6661
* @param CALLBACK the function that will be called when the interrupt is
67-
* triggered
62+
* triggered; it should follow this prototype:
63+
* `void f(I2CCallback, typename MANAGER::FUTURE_PROXY)`
6864
*
6965
* @sa i2c::I2CCallback
7066
*/
@@ -83,7 +79,8 @@ ISR(TWI_vect) \
8379
* @param MANAGER one of many asynchronous I2C managers defined in this header
8480
* @param HANDLER the class holding the callback method
8581
* @param CALLBACK the method of @p HANDLER that will be called when the interrupt
86-
* is triggered; this must be a proper PTMF (pointer to member function).
82+
* is triggered; this must be a proper PTMF (pointer to member function); it should
83+
* follow this prototype: `void f(I2CCallback, typename MANAGER::FUTURE_PROXY)`
8784
*
8885
* @sa i2c::I2CCallback
8986
*/
@@ -93,6 +90,16 @@ ISR(TWI_vect) \
9390
i2c::isr_handler::i2c_change_method<MANAGER, HANDLER, CALLBACK>(); \
9491
}
9592

93+
/**
94+
* This macro shall be used in a class containing a private callback method,
95+
* registered by `REGISTER_I2C_ISR_METHOD`.
96+
* It declares the class where it is used as a friend of all necessary functions
97+
* so that the private callback method can be called properly.
98+
*/
99+
#define DECL_I2C_ISR_HANDLERS_FRIEND \
100+
friend struct i2c::isr_handler; \
101+
DECL_TWI_FRIENDS
102+
96103
namespace i2c
97104
{
98105
/**
@@ -385,11 +392,21 @@ namespace i2c
385392
template<typename OUT, typename IN> using FUTURE = future::Future<OUT, IN>;
386393

387394
public:
395+
/**
396+
* The type passed to callback functions registered alongside ISR.
397+
* Callbacks cam use it to check the status of the Future used for the
398+
* current I2C transaction.
399+
*
400+
* @sa REGISTER_I2C_ISR_FUNCTION
401+
* @sa REGISTER_I2C_ISR_METHOD
402+
*/
403+
using FUTURE_PROXY = PROXY<ABSTRACT_FUTURE>;
404+
388405
/**
389406
* The type of I2CCommand to use in the buffer passed to the constructor
390407
* of this AbstractI2CAsyncManager.
391408
*/
392-
using I2CCOMMAND = I2CCommand<PROXY<ABSTRACT_FUTURE>>;
409+
using I2CCOMMAND = I2CCommand<FUTURE_PROXY>;
393410

394411
/**
395412
* Prepare and enable the MCU for I2C transmission.
@@ -465,7 +482,7 @@ namespace i2c
465482
}
466483

467484
bool push_command_(
468-
I2CLightCommand command, uint8_t target, PROXY<ABSTRACT_FUTURE> proxy)
485+
I2CLightCommand command, uint8_t target, FUTURE_PROXY proxy)
469486
{
470487
return commands_.push_(I2CCOMMAND{command, target, proxy});
471488
}
@@ -508,6 +525,12 @@ namespace i2c
508525
return lc_.resolve(command_.future());
509526
}
510527

528+
FUTURE_PROXY current_proxy() const
529+
{
530+
//TODO shall we convert to a full Proxy<> or keep it as-is?
531+
return command_.future();
532+
}
533+
511534
void send_byte(uint8_t data)
512535
{
513536
TWDR_ = data;
@@ -1382,23 +1405,35 @@ namespace i2c
13821405
interrupt::HandlerHolder<MANAGER>::handler()->i2c_change();
13831406
}
13841407

1385-
template<typename MANAGER, void (*CALLBACK_)(I2CCallback)>
1408+
template<typename MANAGER, void (*CALLBACK_)(I2CCallback, typename MANAGER::FUTURE_PROXY)>
13861409
static void i2c_change_function()
13871410
{
1411+
using namespace interrupt;
13881412
static_assert(I2CManager_trait<MANAGER>::IS_I2CMANAGER, "MANAGER must be an I2CManager");
13891413
static_assert(I2CManager_trait<MANAGER>::IS_ASYNC, "MANAGER must be an asynchronous I2CManager");
1390-
I2CCallback callback = interrupt::HandlerHolder<MANAGER>::handler()->i2c_change();
1391-
if (callback != I2CCallback::NONE) CALLBACK_(callback);
1414+
I2CCallback callback = HandlerHolder<MANAGER>::handler()->i2c_change();
1415+
if (callback != I2CCallback::NONE)
1416+
{
1417+
auto proxy = HandlerHolder<MANAGER>::handler()->current_proxy();
1418+
CALLBACK_(callback, proxy);
1419+
}
13921420
}
13931421

1394-
template<typename MANAGER, typename HANDLER_, void (HANDLER_::*CALLBACK_)(I2CCallback)>
1422+
template<typename MANAGER, typename HANDLER_,
1423+
void (HANDLER_::*CALLBACK_)(I2CCallback, typename MANAGER::FUTURE_PROXY)>
13951424
static void i2c_change_method()
13961425
{
1426+
using namespace interrupt;
13971427
static_assert(I2CManager_trait<MANAGER>::IS_I2CMANAGER, "MANAGER must be an I2CManager");
13981428
static_assert(I2CManager_trait<MANAGER>::IS_ASYNC, "MANAGER must be an asynchronous I2CManager");
1399-
I2CCallback callback = interrupt::HandlerHolder<MANAGER>::handler()->i2c_change();
1429+
I2CCallback callback = HandlerHolder<MANAGER>::handler()->i2c_change();
14001430
if (callback != I2CCallback::NONE)
1401-
interrupt::CallbackHandler<void (HANDLER_::*)(I2CCallback), CALLBACK_>::call(callback);
1431+
{
1432+
auto proxy = HandlerHolder<MANAGER>::handler()->current_proxy();
1433+
using HANDLER =
1434+
CallbackHandler<void (HANDLER_::*)(I2CCallback, typename MANAGER::FUTURE_PROXY), CALLBACK_>;
1435+
HANDLER::call(callback, proxy);
1436+
}
14021437
}
14031438
};
14041439
/// @endcond

examples/i2c/DS1307RTC4/DS1307.cpp

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright 2016-2020 Jean-Francois Poilpret
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/*
16+
* Check DS1307 I2C device (real-time clock) and display output to the UART console.
17+
* This program uses FastArduino DS1307 support API in asynchronous mode (ATmega only)
18+
* along with an ISR callback.
19+
*
20+
* Wiring:
21+
* NB: you should add pullup resistors (10K-22K typically) on both SDA and SCL lines.
22+
* WARNING: wiring is very sensitive for I2C connections! When using breadboard, ensure
23+
* wires connections are tight and stable.
24+
* - on ATmega328P based boards (including Arduino UNO):
25+
* - A4 (PC4, SDA): connected to DS1307 SDA pin
26+
* - A5 (PC5, SCL): connected to DS1307 SCL pin
27+
* - direct USB access
28+
* - on Arduino LEONARDO:
29+
* - D2 (PD1, SDA): connected to DS1307 SDA pin
30+
* - D3 (PD0, SCL): connected to DS1307 SDA pin
31+
* - direct USB access
32+
* - on Arduino MEGA:
33+
* - D20 (PD1, SDA): connected to DS1307 SDA pin
34+
* - D21 (PD0, SCL): connected to DS1307 SDA pin
35+
* - direct USB access
36+
*/
37+
38+
#include <fastarduino/time.h>
39+
#include <fastarduino/new_i2c_handler.h>
40+
#include <fastarduino/devices/new_ds1307.h>
41+
#include <fastarduino/future.h>
42+
#include <fastarduino/iomanip.h>
43+
#include <fastarduino/new_i2c_debug.h>
44+
#include <fastarduino/new_i2c_status.h>
45+
46+
#if defined(ARDUINO_UNO) || defined(ARDUINO_NANO) || defined(BREADBOARD_ATMEGA328P)
47+
#define HARDWARE_UART 1
48+
#include <fastarduino/uart.h>
49+
static constexpr const board::USART UART = board::USART::USART0;
50+
static constexpr const uint8_t OUTPUT_BUFFER_SIZE = 64;
51+
static constexpr uint8_t I2C_BUFFER_SIZE = 32;
52+
// Define vectors we need in the example
53+
REGISTER_UATX_ISR(0)
54+
#elif defined(ARDUINO_MEGA)
55+
#define HARDWARE_UART 1
56+
#include <fastarduino/uart.h>
57+
static constexpr const board::USART UART = board::USART::USART0;
58+
static constexpr const uint8_t OUTPUT_BUFFER_SIZE = 64;
59+
static constexpr uint8_t I2C_BUFFER_SIZE = 32;
60+
// Define vectors we need in the example
61+
REGISTER_UATX_ISR(0)
62+
#elif defined(ARDUINO_LEONARDO)
63+
#define HARDWARE_UART 1
64+
#include <fastarduino/uart.h>
65+
static constexpr const board::USART UART = board::USART::USART1;
66+
static constexpr const uint8_t OUTPUT_BUFFER_SIZE = 64;
67+
static constexpr uint8_t I2C_BUFFER_SIZE = 32;
68+
// Define vectors we need in the example
69+
REGISTER_UATX_ISR(1)
70+
#else
71+
#error "Current target is not yet supported!"
72+
#endif
73+
74+
#define DEBUG_I2C
75+
76+
// UART buffer for traces
77+
static char output_buffer[OUTPUT_BUFFER_SIZE];
78+
79+
using devices::rtc::DS1307;
80+
using devices::rtc::WeekDay;
81+
using devices::rtc::tm;
82+
using devices::rtc::SquareWaveFrequency;
83+
using future::FutureStatus;
84+
using namespace streams;
85+
86+
#ifdef DEBUG_I2C
87+
static constexpr const uint8_t DEBUG_SIZE = 32;
88+
using DEBUGGER = i2c::debug::I2CDebugStatusRecorder<DEBUG_SIZE, DEBUG_SIZE>;
89+
using MANAGER = i2c::I2CAsyncStatusDebugManager<
90+
i2c::I2CMode::STANDARD, i2c::I2CErrorPolicy::CLEAR_ALL_COMMANDS, DEBUGGER&, DEBUGGER&>;
91+
static MANAGER::I2CCOMMAND i2c_buffer[I2C_BUFFER_SIZE];
92+
#define DEBUG(OUT) debugger.trace(OUT)
93+
#define SHOW_STATUS(OUT)
94+
#else
95+
using STATUS = i2c::status::I2CLatestStatusHolder;
96+
using MANAGER = i2c::I2CAsyncStatusManager<
97+
i2c::I2CMode::STANDARD, i2c::I2CErrorPolicy::CLEAR_ALL_COMMANDS, STATUS&>;
98+
static MANAGER::I2CCOMMAND i2c_buffer[I2C_BUFFER_SIZE];
99+
#define DEBUG(OUT)
100+
#define SHOW_STATUS(OUT) OUT << streams::hex << status_holder.latest_status() << streams::endl
101+
#endif
102+
103+
using RTC = DS1307<MANAGER>;
104+
105+
class RTCAsyncHandler
106+
{
107+
public:
108+
RTCAsyncHandler()
109+
{
110+
interrupt::register_handler(*this);
111+
}
112+
113+
FutureStatus status()
114+
{
115+
return status_;
116+
}
117+
118+
void reset()
119+
{
120+
status_ = FutureStatus::NOT_READY;
121+
}
122+
123+
private:
124+
void i2c_change(UNUSED i2c::I2CCallback callback, MANAGER::FUTURE_PROXY proxy)
125+
{
126+
status_ = proxy()->status();
127+
}
128+
129+
FutureStatus status_ = FutureStatus::NOT_READY;
130+
131+
DECL_I2C_ISR_HANDLERS_FRIEND
132+
};
133+
134+
REGISTER_I2C_ISR_METHOD(MANAGER, RTCAsyncHandler, &RTCAsyncHandler::i2c_change)
135+
136+
void display_ram(ostream& out, const uint8_t* data, uint8_t size)
137+
{
138+
out << hex << F("RAM content\n");
139+
for (uint8_t i = 0; i < size; ++i)
140+
{
141+
if (!(i % 8)) out << endl;
142+
out << setw(2) << data[i] << ' ';
143+
}
144+
out << endl;
145+
}
146+
147+
void display_time(ostream& out, const tm& time)
148+
{
149+
out << dec << F("RTC: [")
150+
<< uint8_t(time.tm_wday) << ']'
151+
<< time.tm_mday << '.'
152+
<< time.tm_mon << '.'
153+
<< time.tm_year << ' '
154+
<< time.tm_hour << ':'
155+
<< time.tm_min << ':'
156+
<< time.tm_sec
157+
<< endl;
158+
}
159+
160+
int main() __attribute__((OS_main));
161+
int main()
162+
{
163+
board::init();
164+
sei();
165+
#if HARDWARE_UART
166+
serial::hard::UATX<UART> uart{output_buffer};
167+
#else
168+
serial::soft::UATX<TX> uart{output_buffer};
169+
#endif
170+
uart.begin(115200);
171+
ostream out = uart.out();
172+
out << F("Start") << endl;
173+
174+
// Start TWI interface
175+
//====================
176+
#ifdef DEBUG_I2C
177+
DEBUGGER debugger;
178+
#else
179+
STATUS status_holder;
180+
#endif
181+
182+
#if I2C_TRUE_ASYNC and not defined(FORCE_SYNC)
183+
# ifdef DEBUG_I2C
184+
MANAGER manager{i2c_buffer, debugger, debugger};
185+
# else
186+
MANAGER manager{i2c_buffer, status_holder};
187+
# endif
188+
#else
189+
# ifdef DEBUG_I2C
190+
MANAGER manager{debugger, debugger};
191+
# else
192+
MANAGER manager{status_holder};
193+
# endif
194+
#endif
195+
196+
RTCAsyncHandler handler;
197+
198+
manager.begin();
199+
out << F("I2C interface started") << endl;
200+
SHOW_STATUS(out);
201+
time::delay_ms(1000);
202+
203+
RTC rtc{manager};
204+
205+
// Initialize clock date
206+
//=======================
207+
tm time1;
208+
time1.tm_hour = 8;
209+
time1.tm_min = 45;
210+
time1.tm_sec = 30;
211+
time1.tm_wday = WeekDay::TUESDAY;
212+
time1.tm_mday = 13;
213+
time1.tm_mon = 6;
214+
time1.tm_year = 17;
215+
RTC::SetDatetimeFuture set_date_future{time1};
216+
int error = rtc.set_datetime(set_date_future);
217+
out << F("set_datetime called asynchronously, error = ") << error << endl;
218+
219+
out << F("await asynchronous set_datetime...") << endl;
220+
FutureStatus status;
221+
while ((status = handler.status()) == FutureStatus::NOT_READY)
222+
{
223+
time::delay_us(100);
224+
}
225+
out << F("set_datetime status = ") << status << endl;
226+
SHOW_STATUS(out);
227+
DEBUG(out);
228+
229+
time::delay_ms(2000);
230+
231+
// Read clock
232+
//============
233+
RTC::GetDatetimeFuture get_date_future;
234+
handler.reset();
235+
error = rtc.get_datetime(get_date_future);
236+
out << F("get_datetime called asynchronously, error = ") << error << endl;
237+
238+
out << F("await asynchronous get_datetime...") << endl;
239+
while ((status = handler.status()) == FutureStatus::NOT_READY)
240+
{
241+
time::delay_us(100);
242+
}
243+
out << F("get_datetime status = ") << status << endl;
244+
245+
tm time2;
246+
bool ok = get_date_future.get(time2);
247+
out << F("get() return ") << ok << endl;
248+
display_time(out, time2);
249+
SHOW_STATUS(out);
250+
DEBUG(out);
251+
252+
// Stop TWI interface
253+
//===================
254+
manager.end();
255+
out << F("End") << endl;
256+
}

0 commit comments

Comments
 (0)