Skip to content

Commit c505217

Browse files
authored
Merge pull request #18 from bcmi-labs/serial-read
Threadsafe reading from `Serial`
2 parents 5000a58 + 5ee1bb2 commit c505217

File tree

5 files changed

+281
-5
lines changed

5 files changed

+281
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**************************************************************************************
2+
* INCLUDE
3+
**************************************************************************************/
4+
5+
#include <Arduino_ThreadsafeIO.h>
6+
7+
/**************************************************************************************
8+
* CONSTANTS
9+
**************************************************************************************/
10+
11+
static size_t constexpr NUM_THREADS = 5;
12+
13+
/**************************************************************************************
14+
* FUNCTION DECLARATION
15+
**************************************************************************************/
16+
17+
void serial_thread_func();
18+
19+
/**************************************************************************************
20+
* GLOBAL VARIABLES
21+
**************************************************************************************/
22+
23+
static char thread_name[NUM_THREADS][32];
24+
#undef Serial
25+
#ifdef ARDUINO_PORTENTA_H7_M4
26+
SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
27+
#else
28+
SerialDispatcher Serial(SerialUSB);
29+
#endif
30+
31+
/**************************************************************************************
32+
* SETUP/LOOP
33+
**************************************************************************************/
34+
35+
void setup()
36+
{
37+
/* Fire up some threads all accessing the LSM6DSOX */
38+
for(size_t i = 0; i < NUM_THREADS; i++)
39+
{
40+
snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
41+
rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
42+
t->start(serial_thread_func);
43+
}
44+
}
45+
46+
void loop()
47+
{
48+
49+
}
50+
51+
/**************************************************************************************
52+
* FUNCTION DEFINITION
53+
**************************************************************************************/
54+
55+
void serial_thread_func()
56+
{
57+
Serial.begin(9600);
58+
59+
char const * thread_name = rtos::ThisThread::get_name();
60+
Serial.prefix([thread_name]() -> String
61+
{
62+
char msg[64] = {0};
63+
snprintf(msg, sizeof(msg), "[%05lu] %s ", millis(), thread_name);
64+
return String(msg);
65+
});
66+
Serial.suffix([]() -> String
67+
{
68+
return String("\r\n");
69+
});
70+
71+
for(;;)
72+
{
73+
/* Sleep between 5 and 500 ms */
74+
rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
75+
/* Print a unbroken log message including thread name and timestamp as a prefix. */
76+
Serial.block();
77+
Serial.print("My ");
78+
Serial.print("unbroken ");
79+
Serial.print("thread-safe ");
80+
Serial.print("message!");
81+
Serial.unblock();
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**************************************************************************************
2+
* INCLUDE
3+
**************************************************************************************/
4+
5+
#include <Arduino_ThreadsafeIO.h>
6+
7+
/**************************************************************************************
8+
* CONSTANTS
9+
**************************************************************************************/
10+
11+
static size_t constexpr NUM_THREADS = 5;
12+
13+
/**************************************************************************************
14+
* FUNCTION DECLARATION
15+
**************************************************************************************/
16+
17+
void serial_thread_func();
18+
19+
/**************************************************************************************
20+
* GLOBAL VARIABLES
21+
**************************************************************************************/
22+
23+
static char thread_name[NUM_THREADS][32];
24+
#undef Serial
25+
#ifdef ARDUINO_PORTENTA_H7_M4
26+
SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
27+
#else
28+
SerialDispatcher Serial(SerialUSB);
29+
#endif
30+
31+
/**************************************************************************************
32+
* SETUP/LOOP
33+
**************************************************************************************/
34+
35+
void setup()
36+
{
37+
/* Fire up some threads all accessing the LSM6DSOX */
38+
for(size_t i = 0; i < NUM_THREADS; i++)
39+
{
40+
snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
41+
rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
42+
t->start(serial_thread_func);
43+
}
44+
}
45+
46+
void loop()
47+
{
48+
49+
}
50+
51+
/**************************************************************************************
52+
* FUNCTION DEFINITION
53+
**************************************************************************************/
54+
55+
void serial_thread_func()
56+
{
57+
Serial.begin(9600);
58+
59+
for(;;)
60+
{
61+
/* Sleep between 5 and 500 ms */
62+
rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
63+
64+
/* Read data from the serial interface into a String. */
65+
String serial_msg;
66+
while (Serial.available())
67+
serial_msg += (char)Serial.read();
68+
69+
/* Print thread id and chip id value to serial. */
70+
if (serial_msg.length())
71+
{
72+
char msg[64] = {0};
73+
snprintf(msg, sizeof(msg), "[%05lu] %s: %s ...", millis(), rtos::ThisThread::get_name(), serial_msg.c_str());
74+
Serial.block();
75+
Serial.println(msg);
76+
Serial.unblock();
77+
}
78+
}
79+
}

keywords.txt

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ transfer KEYWORD2
2222
create KEYWORD2
2323
block KEYWORD2
2424
unblock KEYWORD2
25+
prefix KEYWORD2
26+
suffix KEYWORD2
2527

2628
#######################################
2729
# Constants (LITERAL1)

src/serial/SerialDispatcher.cpp

+103-4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ void SerialDispatcher::begin(unsigned long baudrate, uint16_t config)
6969
ThreadCustomerData data;
7070
data.thread_id = current_thread_id;
7171
data.block_tx_buffer = false;
72+
data.prefix_func = nullptr;
73+
data.suffix_func = nullptr;
7274
_thread_customer_list.push_back(data);
7375
}
7476
}
@@ -99,19 +101,37 @@ void SerialDispatcher::end()
99101
int SerialDispatcher::available()
100102
{
101103
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
102-
return _serial.available();
104+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
105+
if (iter == std::end(_thread_customer_list)) return 0;
106+
107+
prepareSerialReader(iter);
108+
handleSerialReader();
109+
110+
return iter->rx_buffer->available();
103111
}
104112

105113
int SerialDispatcher::peek()
106114
{
107115
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
108-
return _serial.peek();
116+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
117+
if (iter == std::end(_thread_customer_list)) return 0;
118+
119+
prepareSerialReader(iter);
120+
handleSerialReader();
121+
122+
return iter->rx_buffer->peek();
109123
}
110124

111125
int SerialDispatcher::read()
112126
{
113127
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
114-
return _serial.read();
128+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
129+
if (iter == std::end(_thread_customer_list)) return 0;
130+
131+
prepareSerialReader(iter);
132+
handleSerialReader();
133+
134+
return iter->rx_buffer->read_char();
115135
}
116136

117137
void SerialDispatcher::flush()
@@ -166,6 +186,22 @@ void SerialDispatcher::unblock()
166186
_cond.notify_one();
167187
}
168188

189+
void SerialDispatcher::prefix(PrefixInjectorCallbackFunc func)
190+
{
191+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
192+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
193+
if (iter == std::end(_thread_customer_list)) return;
194+
iter->prefix_func = func;
195+
}
196+
197+
void SerialDispatcher::suffix(SuffixInjectorCallbackFunc func)
198+
{
199+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
200+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
201+
if (iter == std::end(_thread_customer_list)) return;
202+
iter->suffix_func = func;
203+
}
204+
169205
/**************************************************************************************
170206
* PRIVATE MEMBER FUNCTIONS
171207
**************************************************************************************/
@@ -190,10 +226,46 @@ void SerialDispatcher::threadFunc()
190226
if (d.block_tx_buffer)
191227
return;
192228

229+
/* Return if there's no data to be written to the
230+
* serial interface. This statement is necessary
231+
* because otherwise the prefix/suffix functions
232+
* will be invoked and will be printing something,
233+
* even though no data is actually to be printed for
234+
* most threads.
235+
*/
236+
if (!d.tx_buffer.available())
237+
return;
238+
239+
/* The prefix callback function allows the
240+
* user to insert a custom message before
241+
* a new message is written to the serial
242+
* driver. This is useful e.g. for wrapping
243+
* protocol (e.g. the 'AT' protocol) or providing
244+
* a timestamp, a log level, ...
245+
*/
246+
if (d.prefix_func)
247+
{
248+
String const prefix_str = d.prefix_func();
249+
_serial.write(prefix_str.c_str());
250+
}
251+
252+
/* Now it's time to actually write the message
253+
* conveyed by the user via Serial.print/println.
254+
*/
193255
while(d.tx_buffer.available())
194256
{
195257
_serial.write(d.tx_buffer.read_char());
196258
}
259+
260+
/* Similar to the prefix function this callback
261+
* allows the user to specify a specific message
262+
* to be appended to each message, e.g. '\r\n'.
263+
*/
264+
if (d.suffix_func)
265+
{
266+
String const suffix_str = d.suffix_func();
267+
_serial.write(suffix_str.c_str());
268+
}
197269
});
198270
}
199271
}
@@ -203,4 +275,31 @@ std::list<SerialDispatcher::ThreadCustomerData>::iterator SerialDispatcher::find
203275
return std::find_if(std::begin(_thread_customer_list),
204276
std::end (_thread_customer_list),
205277
[thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == thread_id); });
206-
}
278+
}
279+
280+
void SerialDispatcher::prepareSerialReader(std::list<ThreadCustomerData>::iterator & iter)
281+
{
282+
if (!iter->rx_buffer)
283+
iter->rx_buffer.reset(new arduino::RingBuffer());
284+
}
285+
286+
void SerialDispatcher::handleSerialReader()
287+
{
288+
while (_serial.available())
289+
{
290+
int const c = _serial.read();
291+
292+
std::for_each(std::begin(_thread_customer_list),
293+
std::end (_thread_customer_list),
294+
[c](ThreadCustomerData & d)
295+
{
296+
if (!d.rx_buffer)
297+
return;
298+
299+
if (!d.rx_buffer->availableForStore())
300+
return;
301+
302+
d.rx_buffer->store_char(c);
303+
});
304+
}
305+
}

src/serial/SerialDispatcher.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include <mbed.h>
2929

3030
#include <list>
31+
#include <functional>
32+
33+
#include <SharedPtr.h>
3134

3235
/**************************************************************************************
3336
* CLASS DECLARATION
@@ -56,6 +59,12 @@ class SerialDispatcher : public arduino::HardwareSerial
5659
void block();
5760
void unblock();
5861

62+
typedef std::function<String(void)> PrefixInjectorCallbackFunc;
63+
typedef PrefixInjectorCallbackFunc SuffixInjectorCallbackFunc;
64+
void prefix(PrefixInjectorCallbackFunc func);
65+
void suffix(SuffixInjectorCallbackFunc func);
66+
67+
5968
private:
6069

6170
bool _is_initialized;
@@ -72,13 +81,17 @@ class SerialDispatcher : public arduino::HardwareSerial
7281
osThreadId_t thread_id;
7382
arduino::RingBuffer tx_buffer;
7483
bool block_tx_buffer;
84+
mbed::SharedPtr<arduino::RingBuffer> rx_buffer; /* Only when a thread has expressed interested to read from serial a receive ringbuffer is allocated. */
85+
PrefixInjectorCallbackFunc prefix_func;
86+
SuffixInjectorCallbackFunc suffix_func;
7587
} ThreadCustomerData;
7688

7789
std::list<ThreadCustomerData> _thread_customer_list;
7890

7991
void threadFunc();
8092
std::list<ThreadCustomerData>::iterator findThreadCustomerDataById(osThreadId_t const thread_id);
81-
93+
void prepareSerialReader(std::list<ThreadCustomerData>::iterator & iter);
94+
void handleSerialReader();
8295
};
8396

8497
#endif /* SERIAL_DISPATCHER_H_ */

0 commit comments

Comments
 (0)