Skip to content

Commit 2b00b68

Browse files
authored
Merge pull request #7 from bcmi-labs/serial
Threadsafe serial communication.
2 parents e20752a + 94c6c0f commit 2b00b68

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed

examples/ts_serial/ts_serial.ino

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
/* Print thread id and chip id value to serial. */
64+
char msg[64] = {0};
65+
snprintf(msg, sizeof(msg), "[%05lu] %s: Lorem ipsum ...", millis(), rtos::ThisThread::get_name());
66+
/* Every Serial.print/println() encapsulated between
67+
* block/unblock statements will only be printed after
68+
* a block statement has occurred.
69+
*/
70+
Serial.block();
71+
Serial.println(msg);
72+
Serial.unblock();
73+
}
74+
}

keywords.txt

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ BusDevice KEYWORD1
2020

2121
transfer KEYWORD2
2222
create KEYWORD2
23+
block KEYWORD2
24+
unblock KEYWORD2
2325

2426
#######################################
2527
# Constants (LITERAL1)

src/Arduino_ThreadsafeIO.h

+1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
#include "BusDevice.h"
2727
#include "spi/SpiBusDevice.h"
2828
#include "wire/WireBusDevice.h"
29+
#include "serial/SerialDispatcher.h"
2930

3031
#endif /* ARDUINO_THREADSAFE_IO_H_ */

src/serial/SerialDispatcher.cpp

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
* This file is part of the Arduino_ThreadsafeIO library.
3+
* Copyright (c) 2021 Arduino SA.
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17+
*/
18+
19+
/**************************************************************************************
20+
* INCLUDE
21+
**************************************************************************************/
22+
23+
#include "SerialDispatcher.h"
24+
25+
/**************************************************************************************
26+
* CTOR/DTOR
27+
**************************************************************************************/
28+
29+
SerialDispatcher::SerialDispatcher(arduino::HardwareSerial & serial)
30+
: _is_initialized{false}
31+
, _mutex{}
32+
, _cond{_mutex}
33+
, _serial{serial}
34+
, _thread(osPriorityRealtime, 4096, nullptr, "SerialDispatcher")
35+
, _has_tread_started{false}
36+
, _terminate_thread{false}
37+
{
38+
39+
}
40+
41+
/**************************************************************************************
42+
* PUBLIC MEMBER FUNCTIONS
43+
**************************************************************************************/
44+
45+
void SerialDispatcher::begin(unsigned long baudrate)
46+
{
47+
begin(baudrate, SERIAL_8N1);
48+
}
49+
50+
void SerialDispatcher::begin(unsigned long baudrate, uint16_t config)
51+
{
52+
if (!_is_initialized)
53+
{
54+
_serial.begin(baudrate, config);
55+
_is_initialized = true;
56+
_thread.start(mbed::callback(this, &SerialDispatcher::threadFunc)); /* TODO: Check return code */
57+
while (!_has_tread_started) { }
58+
}
59+
60+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
61+
62+
/* Check if the thread calling begin is already in the list. */
63+
osThreadId_t const current_thread_id = rtos::ThisThread::get_id();
64+
if (findThreadCustomerDataById(rtos::ThisThread::get_id()) == std::end(_thread_customer_list))
65+
{
66+
/* Since the thread is not in the list yet we are
67+
* going to create a new entry to the list.
68+
*/
69+
ThreadCustomerData data;
70+
data.thread_id = current_thread_id;
71+
data.block_tx_buffer = false;
72+
_thread_customer_list.push_back(data);
73+
}
74+
}
75+
76+
void SerialDispatcher::end()
77+
{
78+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
79+
80+
/* Retrieve the current thread id and remove
81+
* the thread data from the thread data list.
82+
*/
83+
osThreadId_t const current_thread_id = rtos::ThisThread::get_id();
84+
std::remove_if(std::begin(_thread_customer_list),
85+
std::end (_thread_customer_list),
86+
[current_thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == current_thread_id); });
87+
88+
/* If no thread consumers are left also end
89+
* the serial device altogether.
90+
*/
91+
if (_thread_customer_list.size() == 0)
92+
{
93+
_terminate_thread = true;
94+
_thread.join();
95+
_serial.end();
96+
}
97+
}
98+
99+
int SerialDispatcher::available()
100+
{
101+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
102+
return _serial.available();
103+
}
104+
105+
int SerialDispatcher::peek()
106+
{
107+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
108+
return _serial.peek();
109+
}
110+
111+
int SerialDispatcher::read()
112+
{
113+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
114+
return _serial.read();
115+
}
116+
117+
void SerialDispatcher::flush()
118+
{
119+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
120+
_serial.flush();
121+
}
122+
123+
size_t SerialDispatcher::write(uint8_t const b)
124+
{
125+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
126+
127+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
128+
129+
/* If this thread hasn't registered yet
130+
* with the SerialDispatcher via 'begin'.
131+
*/
132+
if (iter == std::end(_thread_customer_list))
133+
return 0;
134+
135+
if (iter->tx_buffer.availableForStore())
136+
iter->tx_buffer.store_char(b);
137+
138+
/* Inform the worker thread that new data has
139+
* been written to a Serial transmit buffer.
140+
*/
141+
_cond.notify_one();
142+
143+
return 1;
144+
}
145+
146+
size_t SerialDispatcher::write(const uint8_t * data, size_t len)
147+
{
148+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
149+
150+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
151+
152+
/* If this thread hasn't registered yet
153+
* with the SerialDispatcher via 'begin'.
154+
*/
155+
if (iter == std::end(_thread_customer_list))
156+
return 0;
157+
158+
size_t bytes_written = 0;
159+
for (; (bytes_written < len) && iter->tx_buffer.availableForStore(); bytes_written++)
160+
iter->tx_buffer.store_char(data[bytes_written]);
161+
162+
/* Inform the worker thread that new data has
163+
* been written to a Serial transmit buffer.
164+
*/
165+
_cond.notify_one();
166+
167+
return bytes_written;
168+
}
169+
170+
void SerialDispatcher::block()
171+
{
172+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
173+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
174+
iter->block_tx_buffer = true;
175+
}
176+
177+
void SerialDispatcher::unblock()
178+
{
179+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
180+
auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
181+
iter->block_tx_buffer = false;
182+
_cond.notify_one();
183+
}
184+
185+
/**************************************************************************************
186+
* PRIVATE MEMBER FUNCTIONS
187+
**************************************************************************************/
188+
189+
void SerialDispatcher::threadFunc()
190+
{
191+
_has_tread_started = true;
192+
193+
while(!_terminate_thread)
194+
{
195+
/* Prevent race conditions by multi-threaded
196+
* access to shared data.
197+
*/
198+
mbed::ScopedLock<rtos::Mutex> lock(_mutex);
199+
/* Wait for new data to be available */
200+
_cond.wait();
201+
/* Iterate over all list entries. */
202+
std::for_each(std::begin(_thread_customer_list),
203+
std::end (_thread_customer_list),
204+
[this](ThreadCustomerData & d)
205+
{
206+
if (!d.block_tx_buffer)
207+
{
208+
while(d.tx_buffer.available())
209+
{
210+
_serial.write(d.tx_buffer.read_char());
211+
}
212+
}
213+
});
214+
}
215+
}
216+
217+
std::list<SerialDispatcher::ThreadCustomerData>::iterator SerialDispatcher::findThreadCustomerDataById(osThreadId_t const thread_id)
218+
{
219+
return std::find_if(std::begin(_thread_customer_list),
220+
std::end (_thread_customer_list),
221+
[thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == thread_id); });
222+
}

src/serial/SerialDispatcher.h

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* This file is part of the Arduino_ThreadsafeIO library.
3+
* Copyright (c) 2021 Arduino SA.
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17+
*/
18+
19+
#ifndef SERIAL_DISPATCHER_H_
20+
#define SERIAL_DISPATCHER_H_
21+
22+
/**************************************************************************************
23+
* INCLUDE
24+
**************************************************************************************/
25+
26+
#include "api/HardwareSerial.h"
27+
28+
#include <mbed.h>
29+
30+
#include <list>
31+
32+
/**************************************************************************************
33+
* CLASS DECLARATION
34+
**************************************************************************************/
35+
36+
class SerialDispatcher : public arduino::HardwareSerial
37+
{
38+
39+
public:
40+
41+
SerialDispatcher(arduino::HardwareSerial & serial);
42+
43+
44+
virtual void begin(unsigned long baudrate) override;
45+
virtual void begin(unsigned long baudrate, uint16_t config) override;
46+
virtual void end() override;
47+
virtual int available() override;
48+
virtual int peek() override;
49+
virtual int read() override;
50+
virtual void flush() override;
51+
virtual size_t write(uint8_t const b) override;
52+
virtual size_t write(const uint8_t * data, size_t len) override;
53+
using Print::write;
54+
virtual operator bool() override { return _serial; }
55+
56+
void block();
57+
void unblock();
58+
59+
private:
60+
61+
bool _is_initialized;
62+
rtos::Mutex _mutex;
63+
rtos::ConditionVariable _cond;
64+
arduino::HardwareSerial & _serial;
65+
66+
rtos::Thread _thread;
67+
bool _has_tread_started;
68+
bool _terminate_thread;
69+
70+
typedef struct
71+
{
72+
osThreadId_t thread_id;
73+
arduino::RingBuffer tx_buffer;
74+
bool block_tx_buffer;
75+
} ThreadCustomerData;
76+
77+
std::list<ThreadCustomerData> _thread_customer_list;
78+
79+
void threadFunc();
80+
std::list<ThreadCustomerData>::iterator findThreadCustomerDataById(osThreadId_t const thread_id);
81+
82+
};
83+
84+
#endif /* SERIAL_DISPATCHER_H_ */

0 commit comments

Comments
 (0)