Skip to content

Commit c4b11b1

Browse files
committed
Allow multiple CDCs.
You must: 1) increase CFG_TUD_CDC in tusb_config.c (currently, in the core.) 2) define the additional Vserial ports "Adafruit_USBD_CDC portName(n)" (where n goes from 1 to CFG_TUD_CDC-1) (0 is "Serial") "n" is a new addition to the constructor. 3) Use the "begin method" on the additional ports in "n" order. There is a new example ../CDC/multiCDC that exercises the features.
1 parent 8f4bc1f commit c4b11b1

File tree

7 files changed

+469
-25
lines changed

7 files changed

+469
-25
lines changed

examples/CDC/multiCDC/multiCDC.ino

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
This example demonstrates the use of multiple USB CDC/ACM "Virtual Serial" ports,
3+
using Ha Thach's tinyUSB library, and the port of that library to the Arduino environment
4+
https://github.com/hathach/tinyusb
5+
https://github.com/adafruit/Adafruit_TinyUSB_Arduino
6+
7+
Written by Bill Westfield (aka WestfW), June 2021.
8+
This code is released to the public domain (note that this is a different
9+
license than the rest of the TinyUSB library and examples.)
10+
11+
The example creates three virtual serial ports, runs a non-blocking parser on
12+
each one, and allows them to send messages to each other. This is pretty useless,
13+
and is relatively complex for an example, but it exercises a bunch of the CDC features.
14+
15+
The max number of CDC ports (CFG_TUD_CDC) has to be changed to at least 3, changed in
16+
the core tusb_config.h file.
17+
*/
18+
19+
#include <Adafruit_TinyUSB.h>
20+
#include "simpleparser.h"
21+
22+
#define LED LED_BUILTIN
23+
24+
/*
25+
Create extra USB Serial Ports. "Serial" is already created.
26+
*/
27+
Adafruit_USBD_CDC USBSer2(1);
28+
Adafruit_USBD_CDC USBSer3(2);
29+
30+
Adafruit_USBD_CDC *ports[CFG_TUD_CDC] = { &Serial, &USBSer2, &USBSer3, NULL };
31+
32+
void setup() {
33+
pinMode(LED, OUTPUT);
34+
// start up all of the USB Vitual ports, and wait for them to enumerate.
35+
Serial.begin(115200); // Do these in order, or
36+
USBSer2.begin(115200); // "bad things will happen"
37+
USBSer3.begin(115200);
38+
while (!Serial || !USBSer2 || !USBSer3) {
39+
if (Serial) {
40+
Serial.println("Waiting for other USB ports");
41+
}
42+
if (USBSer2) {
43+
USBSer2.println("Waiting for other USB ports");
44+
}
45+
if (USBSer3) {
46+
USBSer3.println("Waiting for other USB ports");
47+
}
48+
delay(1000);
49+
}
50+
Serial.print("You are TTY0\n\r\n0> ");
51+
USBSer2.print("You are TTY1\n\r\n1> ");
52+
USBSer3.print("You are TTY2\n\r\n2> ");
53+
}
54+
55+
56+
// We need a parser for each Virtual Serial port
57+
simpleParser<80> line0(Serial);
58+
simpleParser<80> line1(USBSer2);
59+
simpleParser<80> line2(USBSer3);
60+
61+
int LEDstate = 0;
62+
63+
// Given an input port and an output port and a message, send it off.
64+
65+
void sendmsg(Adafruit_USBD_CDC &out, Adafruit_USBD_CDC &in, char *msg) {
66+
out.print("\r\nTTY");
67+
out.print(in.getInstance());
68+
out.print(": ");
69+
out.println(msg);
70+
out.flush();
71+
}
72+
73+
// we've received a line on some port. Check if it's a valid comand,
74+
// parse the arguments, and take appropriate action
75+
void parseMsg(Adafruit_USBD_CDC &in, parserCore &parser) {
76+
int target;
77+
enum { CMD_SEND, CMD_HELP, CMD_HELP2 };
78+
int cmd = parser.keyword("send help ? ");
79+
switch (cmd) {
80+
case CMD_SEND:
81+
target = parser.number();
82+
if (target < 0 || target >= CFG_TUD_CDC || ports[target] == NULL) {
83+
in.println("Bad target line");
84+
return;
85+
}
86+
sendmsg(*ports[target], in, parser.restOfLine());
87+
break;
88+
89+
case CMD_HELP:
90+
case CMD_HELP2:
91+
in.println("Available commands:\r\n SEND N msg");
92+
break;
93+
default:
94+
in.println("invalid command");
95+
break;
96+
}
97+
}
98+
99+
void loop() {
100+
if (line0.getLine()) {
101+
parseMsg(Serial, line0);
102+
line0.reset();
103+
Serial.print("0> ");
104+
105+
}
106+
if (line1.getLine()) {
107+
parseMsg(USBSer2, line1);
108+
line1.reset();
109+
USBSer2.print("1> ");
110+
}
111+
if (line2.getLine()) {
112+
parseMsg(USBSer3, line2);
113+
line2.reset();
114+
USBSer3.print("2> ");
115+
}
116+
if (delay_without_delaying(500)) {
117+
// blink LED to show we're still alive
118+
LEDstate = !LEDstate;
119+
digitalWrite(LED, LEDstate);
120+
}
121+
}
122+
123+
// Helper: non-blocking "delay" alternative.
124+
boolean delay_without_delaying(unsigned long time) {
125+
// return false if we're still "delaying", true if time ms has passed.
126+
// this should look a lot like "blink without delay"
127+
static unsigned long previousmillis = 0;
128+
unsigned long currentmillis = millis();
129+
if (currentmillis - previousmillis >= time) {
130+
previousmillis = currentmillis;
131+
return true;
132+
}
133+
return false;
134+
}
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
* simpleParser
3+
* Implement a command-line parser.
4+
* Written 2014 by Bill Westfield (WestfW)
5+
* refactored 2020
6+
* Released to the public domain.
7+
*/
8+
9+
#include "Arduino.h"
10+
#include "simpleParser.h"
11+
#include <avr/pgmspace.h>
12+
13+
14+
/*
15+
* Reset the line buffer
16+
*/
17+
void parserCore::reset ()
18+
{
19+
memset(buffer, 0, lineLen);
20+
inptr = 0;
21+
parsePtr = 0;
22+
termchar = 0;
23+
}
24+
25+
/*
26+
* getLine
27+
* Read a line of text from Serial into the internal line buffer.
28+
* With echoing and editing!
29+
* Non-blocking. Returns 0 until end-of-line seen.
30+
*/
31+
uint8_t parserCore::getLine ()
32+
{
33+
int c;
34+
35+
c = S->read();
36+
switch (c) {
37+
case 127:
38+
case CTRL('H'):
39+
/*
40+
Destructive backspace: remove last character
41+
*/
42+
if (inptr > 0) {
43+
S->print("\010 \010");
44+
buffer[--inptr] = 0;
45+
}
46+
break;
47+
case CTRL('R'):
48+
/*
49+
Ctrl-R retypes the line
50+
*/
51+
S->print("\r\n");
52+
S->print(buffer);
53+
break;
54+
case CTRL('U'):
55+
/*
56+
Ctrl-U deletes the entire line and starts over.
57+
*/
58+
S->println("XXX");
59+
reset();
60+
break;
61+
case CTRL('J'):
62+
case CTRL('M'):
63+
buffer[inptr++] = '\n';
64+
S->println(); /* Echo newline too. */
65+
return inptr;
66+
case -1:
67+
/*
68+
No character present; don't do anything.
69+
*/
70+
return 0;
71+
default:
72+
/*
73+
Otherwise, echo the character and put it into the buffer
74+
*/
75+
buffer[inptr++] = c;
76+
S->write(c);
77+
}
78+
return 0;
79+
}
80+
81+
/*
82+
* getLineWait
83+
* like getLine, but block until a complete line is read
84+
*/
85+
86+
uint8_t parserCore::getLineWait (void)
87+
{
88+
uint8_t status;
89+
90+
do {
91+
status = getLine();
92+
} while (status == 0);
93+
return status;
94+
}
95+
96+
97+
bool parserCore::IsWhitespace (char c)
98+
{
99+
return (c == ' ' || c == CTRL('I'));
100+
}
101+
102+
103+
104+
bool parserCore::delim(char c)
105+
{
106+
static const char Delimiters[] PROGMEM = "\r\n ,;:=\t";
107+
if (c == 0 || strchr_P(Delimiters, c))
108+
return true;
109+
return false;
110+
}
111+
112+
/*
113+
* Number
114+
* Advance the token and parse a number. Accept decimal, hex, octal.
115+
*/
116+
117+
int parserCore::number()
118+
{
119+
char *p = token();
120+
if (p) {
121+
return strtol(p, 0, 0);
122+
}
123+
return -1;
124+
}
125+
126+
/*
127+
* eol
128+
* return true if we're at the end of the line.
129+
*/
130+
boolean parserCore::eol ()
131+
{
132+
while (IsWhitespace(buffer[parsePtr])) { /* skip leading whitespace */
133+
parsePtr++;
134+
}
135+
return buffer[parsePtr] == '\n' || buffer[parsePtr] == 0;
136+
}
137+
138+
/*
139+
* cliTermChar
140+
* return the termination character of the last token
141+
*/
142+
uint8_t parserCore::termChar ()
143+
{
144+
return termchar;
145+
}
146+
147+
/*
148+
* cliCharacter
149+
*/
150+
151+
/*
152+
* token
153+
* A token is a set of non-delimiter characters ending at a delimiter.
154+
* As a line is parsed from the internal buffer, parsePtr is advanced, and
155+
* the delimiters of parsed tokens are replaced with nulls.
156+
* Note that a line always ends with the newline character AND a null.
157+
*/
158+
159+
char *parserCore::token ()
160+
{
161+
uint8_t i;
162+
163+
if (eol()) { // reached the end of the line?
164+
return NULL;
165+
}
166+
i = parsePtr; // save start position of token
167+
while ((!delim(buffer[parsePtr])) && (parsePtr < lineLen)) {
168+
parsePtr++; // advance pointer till we hit a delimiter.
169+
}
170+
termchar = buffer[parsePtr];
171+
buffer[parsePtr++] = 0; // replace the delimiter with null
172+
return &buffer[i]; // convert position to pointer for retval
173+
}
174+
175+
/*
176+
* Match the next token with a list of keywords.
177+
* The list of keywords is in PROGMEM, separated by spaces.
178+
* returns either the position of the found keyword (0..n),
179+
* PARSER_NOMATCH, PARSER_AMB, or PARSER_EOL at the end of line
180+
*/
181+
int8_t parserCore::keyword (const char *keys)
182+
{
183+
char *p = token();
184+
char *thisKey = (char *)keys;
185+
int8_t i = 0, match, first=PARSER_NOMATCH;
186+
if (!p)
187+
return PARSER_EOL;
188+
189+
while (pgm_read_byte(thisKey)) {
190+
match = tokcasecmp(p, thisKey);
191+
#if defined(DEBUG) && DEBUG
192+
extern char spbuffer[];
193+
sprintf(spbuffer, "key='%s', p='%s', match = %d\n", thisKey, p, match);
194+
S->print(spbuffer);
195+
#endif
196+
if (match == CMP_MATCH) {
197+
return i;
198+
}
199+
while (pgm_read_byte(thisKey) > ' ') {
200+
thisKey++; // advance to next keyword
201+
}
202+
thisKey++; // skip delimiter
203+
if (match == CMP_PARTIALMATCH) {
204+
// There was a partial match; check for another...
205+
if (first != PARSER_NOMATCH) { // already another match?
206+
return (PARSER_AMB);
207+
} else {
208+
first = i;
209+
continue;
210+
}
211+
return i; // match
212+
}
213+
i++; // next keyword
214+
}
215+
return first;
216+
}
217+
218+
/*
219+
* tokcasecmp
220+
* tokcasecmp is like strcasecmp_P, except that the strings are terminated
221+
* by any char < 32. Return value is 0 for match, or pointer to the delimiter
222+
* for non-match (to expedite comparing against strings of targets.)
223+
*/
224+
uint8_t parserCore::tokcasecmp(const char *tok, const char *target)
225+
{
226+
char tokc, keyc;
227+
const char *t = (char *)target;
228+
229+
do {
230+
tokc = toupper(*tok++);
231+
keyc = toupper(pgm_read_byte(t++));
232+
// tok++; t++;
233+
if (tokc == 0) {
234+
// End of token; see if end of keyword as well
235+
if (keyc <= ' ') {
236+
return CMP_MATCH; // both ended - exact match
237+
}
238+
return CMP_PARTIALMATCH; // keyword is longer - partial
239+
}
240+
// Not end of token
241+
if (keyc <= ' ') {
242+
return CMP_NONMATCH; // key ended before tok - non match
243+
}
244+
} while (tokc == keyc);
245+
return CMP_NONMATCH; // neither string ended, but non-match
246+
}

0 commit comments

Comments
 (0)