Skip to content

Commit ef44211

Browse files
everslickd-a-v
authored andcommitted
emulation on host: Add full UART driver emulation. (#5785)
* emulation on host: Add full UART driver emulation. This PR replaces the high level Serial mock with a more complete UART driver. This way the HardwareSerial works without any modifications. Additionally the driver supports UART0 and UART1 at the same time (UART0 is directed to stdout and UART1 writes to stderr). RX is implemented by switching the terminal into raw non-blocking mode and injecting each key-press directly into the FIFO of UART0. A new command line switch -c was added to ignore CTRL-C and send it via serial as well. The decumentation was updated accordingly. Reading and setting of GPIOs does only write to stderr, when compiled with D=1. But this is subject to be replaced with a proper GPIO emu anyway. Reading from GPIO0 now returns 1 instead of 0 because this is most likely a low active input. * Fixed unused variable. * Remove unused functions, as long as there are no debug macros using them. * Move user_interface.cc from MOCK_CPP_FILES_EMU to MOCK_CPP_FILES. * Move optimistic_yield() from user_interface.cpp to Arduino.cpp * Remove atexit() weak declaration (fixes segfault when calling atexit). * Improve resetting of terminal after program exit, convert \r to \r\n. * Show error, when STDOUT is not a TTY, minor code cleanung.
1 parent 53f51b5 commit ef44211

10 files changed

+545
-159
lines changed

tests/host/Makefile

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\
6565
libb64/cencode.cpp \
6666
libb64/cdecode.cpp \
6767
Schedule.cpp \
68+
HardwareSerial.cpp \
6869
)
6970

7071
CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
@@ -74,7 +75,7 @@ MOCK_CPP_FILES_COMMON := $(addprefix common/,\
7475
Arduino.cpp \
7576
spiffs_mock.cpp \
7677
WMath.cpp \
77-
MockSerial.cpp \
78+
MockUART.cpp \
7879
MockTools.cpp \
7980
MocklwIP.cpp \
8081
)
@@ -95,7 +96,6 @@ MOCK_C_FILES := $(addprefix common/,\
9596
noniso.c \
9697
)
9798

98-
9999
INC_PATHS += $(addprefix -I, \
100100
. \
101101
common \
@@ -231,7 +231,7 @@ ARDUINO_LIBS := \
231231
WiFiServerSecureBearSSL.cpp \
232232
BearSSLHelpers.cpp \
233233
CertStoreBearSSL.cpp \
234-
) \
234+
)
235235

236236
OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\
237237
$(addprefix ESP8266WebServer/src/,\
@@ -251,7 +251,7 @@ OPT_ARDUINO_LIBS ?= $(addprefix ../../libraries/,\
251251
DNSServer/src/DNSServer.cpp \
252252
ESP8266AVRISP/src/ESP8266AVRISP.cpp \
253253
ESP8266HTTPClient/src/ESP8266HTTPClient.cpp \
254-
) \
254+
)
255255

256256
MOCK_ARDUINO_LIBS := $(addprefix common/,\
257257
ClientContextSocket.cpp \
@@ -263,7 +263,7 @@ MOCK_ARDUINO_LIBS := $(addprefix common/,\
263263
MockEsp.cpp \
264264
MockEEPROM.cpp \
265265
MockSPI.cpp \
266-
) \
266+
)
267267

268268
CPP_SOURCES_CORE_EMU = \
269269
$(MOCK_CPP_FILES_EMU) \

tests/host/README.txt

+11-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ WiFiClient+WiFiServer/ClientContext and WifiUdp/UdpContext using socket
1919
posix API. Further work will optionally propose native lwIP library
2020
instead.
2121

22+
Serial UART0 and UART1 are emulated via stdin/stdout/stderr. Therefor
23+
stdin is connected to UART0(RX) and stdout is connected to UART0(TX).
24+
UART1(TX) writes to stderr. Reading from stdin happens in non-blocking
25+
raw mode, that means each character is directly injected into the UART
26+
FIFO without any buffering in the console. The command line switch -c
27+
can be used to stop the emulation from intersepting CTRL-C (SIGINT).
28+
2229
How to compile and run a sketch
2330
-------------------------------
2431

@@ -73,7 +80,10 @@ Options are available:
7380
-h
7481
-i eth0 bind servers to this interface (WIP)
7582
-l bind Multicast to the above interface (WIP)
76-
-f no throttle (possibly 100%CPU)
83+
-c ignore CTRL-C (send it over serial device)
84+
-f no throttle (possibly 100%CPU)
85+
-S spiffs size in KBytes (default: 1024)
86+
(negative value will force mismatched size)
7787

7888
TODO
7989
----

tests/host/common/Arduino.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ extern "C" void yield()
3737
{
3838
}
3939

40+
extern "C" void optimistic_yield (uint32_t interval_us)
41+
{
42+
usleep(interval_us);
43+
}
44+
4045
extern "C" void esp_yield()
4146
{
4247
}

tests/host/common/Arduino.h

-2
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,6 @@ extern "C" {
191191
void init(void);
192192
void initVariant(void);
193193

194-
int atexit(void (*func)()) __attribute__((weak));
195-
196194
void pinMode(uint8_t pin, uint8_t mode);
197195
void digitalWrite(uint8_t pin, uint8_t val);
198196
int digitalRead(uint8_t pin);

tests/host/common/ArduinoMain.cpp

+69-8
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,58 @@
3333
#include <user_interface.h> // wifi_get_ip_info()
3434

3535
#include <signal.h>
36-
#include <unistd.h> // usleep
36+
#include <unistd.h>
3737
#include <getopt.h>
38+
#include <termios.h>
39+
#include <stdarg.h>
40+
#include <stdio.h>
3841

3942
bool user_exit = false;
4043
const char* host_interface = nullptr;
4144
size_t spiffs_kb = 1024;
45+
bool ignore_sigint = false;
46+
bool restore_tty = false;
47+
48+
#define STDIN STDIN_FILENO
49+
50+
static struct termios initial_settings;
51+
52+
static int mock_start_uart(void)
53+
{
54+
struct termios settings;
55+
56+
if (!isatty(STDIN)) return 0;
57+
if (tcgetattr(STDIN, &initial_settings) < 0) return -1;
58+
settings = initial_settings;
59+
settings.c_lflag &= ~(ignore_sigint ? ISIG : 0);
60+
settings.c_lflag &= ~(ECHO | ICANON);
61+
settings.c_iflag &= ~(ICRNL | INLCR | ISTRIP | IXON);
62+
settings.c_oflag |= (ONLCR);
63+
settings.c_cc[VMIN] = 0;
64+
settings.c_cc[VTIME] = 0;
65+
if (tcsetattr(STDIN, TCSANOW, &settings) < 0) return -2;
66+
tty_restore = true;
67+
return 0;
68+
}
69+
70+
static int mock_stop_uart(void)
71+
{
72+
if (!restore_tty) return 0;
73+
if (!isatty(STDIN)) {
74+
perror("isatty(STDIN)");
75+
//system("stty sane"); <- same error message "Inappropriate ioctl for device"
76+
return 0;
77+
}
78+
if (tcsetattr(STDIN, TCSANOW, &initial_settings) < 0) return -1;
79+
printf("\e[?25h"); // show cursor
80+
return (0);
81+
}
82+
83+
static uint8_t mock_read_uart(void)
84+
{
85+
uint8_t ch = 0;
86+
return (read(STDIN, &ch, 1) == 1) ? ch : 0;
87+
}
4288

4389
void help (const char* argv0, int exitcode)
4490
{
@@ -48,9 +94,10 @@ void help (const char* argv0, int exitcode)
4894
" -h\n"
4995
" -i <interface> - use this interface for IP address\n"
5096
" -l - bind tcp/udp servers to interface only (not 0.0.0.0)\n"
97+
" -c - ignore CTRL-C (send it via Serial)\n"
5198
" -f - no throttle (possibly 100%%CPU)\n"
5299
" -S - spiffs size in KBytes (default: %zd)\n"
53-
" (negative value will force mismatched size)\n"
100+
" (negative value will force mismatched size)\n"
54101
, argv0, spiffs_kb);
55102
exit(exitcode);
56103
}
@@ -60,13 +107,15 @@ static struct option options[] =
60107
{ "help", no_argument, NULL, 'h' },
61108
{ "fast", no_argument, NULL, 'f' },
62109
{ "local", no_argument, NULL, 'l' },
110+
{ "sigint", no_argument, NULL, 'c' },
63111
{ "interface", required_argument, NULL, 'i' },
64112
{ "spiffskb", required_argument, NULL, 'S' },
65113
};
66114

67-
void save ()
115+
void cleanup ()
68116
{
69117
mock_stop_spiffs();
118+
mock_stop_uart();
70119
}
71120

72121
void control_c (int sig)
@@ -76,7 +125,7 @@ void control_c (int sig)
76125
if (user_exit)
77126
{
78127
fprintf(stderr, MOCK "stuck, killing\n");
79-
save();
128+
cleanup();
80129
exit(1);
81130
}
82131
user_exit = true;
@@ -90,7 +139,7 @@ int main (int argc, char* const argv [])
90139

91140
for (;;)
92141
{
93-
int n = getopt_long(argc, argv, "hlfi:S:", options, NULL);
142+
int n = getopt_long(argc, argv, "hlcfi:S:", options, NULL);
94143
if (n < 0)
95144
break;
96145
switch (n)
@@ -104,6 +153,9 @@ int main (int argc, char* const argv [])
104153
case 'l':
105154
global_ipv4_netfmt = NO_GLOBAL_BINDING;
106155
break;
156+
case 'c':
157+
ignore_sigint = true;
158+
break;
107159
case 'f':
108160
fast = true;
109161
break;
@@ -128,16 +180,25 @@ int main (int argc, char* const argv [])
128180
// setup global global_ipv4_netfmt
129181
wifi_get_ip_info(0, nullptr);
130182

183+
// set stdin to non blocking mode
184+
mock_start_uart();
185+
186+
// install exit handler in case Esp.restart() is called
187+
atexit(cleanup);
188+
131189
setup();
132190
while (!user_exit)
133191
{
192+
uint8_t data = mock_read_uart();
193+
194+
if (data)
195+
uart_new_data(UART0, data);
134196
if (!fast)
135-
usleep(10000); // not 100% cpu
197+
usleep(1000); // not 100% cpu, ~1000 loops per second
136198
loop();
137199
check_incoming_udp();
138200
}
139-
140-
save();
201+
cleanup();
141202

142203
return 0;
143204
}

tests/host/common/HostWiring.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,23 @@ void pinMode (uint8_t pin, uint8_t mode)
4646
case WAKEUP_PULLDOWN: m="WAKEUP_PULLDOWN"; break;
4747
default: m="(special)";
4848
}
49+
#ifdef DEBUG_ESP_CORE
4950
fprintf(stderr, MOCK "gpio%d: mode='%s'\n", pin, m);
51+
#endif
5052
}
5153

5254
void digitalWrite(uint8_t pin, uint8_t val)
5355
{
56+
#ifdef DEBUG_ESP_CORE
5457
fprintf(stderr, MOCK "digitalWrite(pin=%d val=%d)\n", pin, val);
58+
#endif
5559
}
5660

5761
void analogWrite(uint8_t pin, int val)
5862
{
63+
#ifdef DEBUG_ESP_CORE
5964
fprintf(stderr, MOCK "analogWrite(pin=%d, val=%d\n", pin, val);
65+
#endif
6066
}
6167

6268
int analogRead(uint8_t pin)
@@ -67,11 +73,17 @@ int analogRead(uint8_t pin)
6773

6874
void analogWriteRange(uint32_t range)
6975
{
76+
#ifdef DEBUG_ESP_CORE
7077
fprintf(stderr, MOCK "analogWriteRange(range=%d)\n", range);
78+
#endif
7179
}
7280

7381
int digitalRead(uint8_t pin)
7482
{
83+
#ifdef DEBUG_ESP_CORE
7584
fprintf(stderr, MOCK "digitalRead(%d)\n", pin);
76-
return 0;
85+
#endif
86+
87+
// pin 0 is most likely a low active input
88+
return pin ? 0 : 1;
7789
}

0 commit comments

Comments
 (0)