Reputation: 51
I am following the tutorial SerialEvent and am trying this code:
However, when I send my Arduino Nano serial data, it does not seem to be received. When I send it a message over serial, it should print it back out to the Arduino Serial Monitor (serial console), but it doesn't. Why? What am I doing wrong?
More details:
I need communication from my PC to the Arduino in real-time. Therefore, I cannot use delay()
in my Arduino loop. The project consists of sending a message from a computer to the Arduino and receiving and parsing it in the Arduino. This example code seems perfect since it does not use the delay()
function. Unfortunately, it does not work. I send a text string from my computer to the Arduino over serial and the Arduino does not print anything to the serial monitor. My Arduino is the Nano ATmega328.
Also, I don't see this example code calling the serialEvent()
function anywhere. Where do I have to call serialEvent()
? Or, how does it otherwise ever run and get called?
Here is the code, copy-pasted from the example above:
String inputString = ""; // A string to hold incoming data
boolean stringComplete = false; // Whether the string is complete
void setup() {
// Initialize serial:
Serial.begin(9600);
// Reserve 200 bytes for the inputString:
inputString.reserve(200);
}
void loop() {
// Print the string when a newline arrives:
if (stringComplete) {
Serial.println(inputString);
// Clear the string:
inputString = "";
stringComplete = false;
}
}
/*
SerialEvent occurs whenever a new data comes in the
hardware serial RX. This routine is run between each
time loop() runs, so using delay inside loop can delay
response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// Get the new byte:
char inChar = (char)Serial.read();
// Add it to the inputString:
inputString += inChar;
// If the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (inChar == '\n') {
stringComplete = true;
}
}
}
Upvotes: 2
Views: 4234
Reputation: 52659
\n
at the end of any string you send over serial TO the Arduino:Notice this part of the serialEvent()
function:
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (inChar == '\n') {
stringComplete = true;
}
As you can see, it is looking for the \n
(newline) character to indicate the full string has been received. So, you must include a newline (\n
) at the end of the string you are sending to the Arduino.
The tutorial (https://www.arduino.cc/en/Tutorial/BuiltInExamples/SerialEvent) also states (emphasis added):
This example demonstrates use of the
serialEvent()
function. This function is automatically called at the end ofloop()
when there is serial data available in the buffer. In this case, each character found is added to a string until a newline is found. Then the string is printed and set back to null.
If using the Arduino Serial Monitor, be sure to choose the "Newline" option from the dropdown at the bottom. Then, whenever you type something in the bar at the top and press Enter, it will automatically append a \n
to the end of your string to the Arduino when you send it.
If sending data from a C, C++, Python, Java, or other program to the Arduino over serial, again, be sure to include a \n
at the end of your string TO the Arduino.
serialEvent()
get called anyway?To answer this:
Also, I don't see this example code calling the
serialEvent()
function anywhere. Where do I have to callserialEvent()
? Or, how does it otherwise ever run and get called?
In short, the fact that you defined the serialEvent()
function at all makes it get called automatically by the Arduino core code. If you don't define it, it won't get called. If you do define it, it will. This is because your definition of serialEvent()
is strong and overrides the weak
declaration here in HardwareSerial.cpp:
// SerialEvent functions are weak, so when the user doesn't define them,
// the linker just sets their address to 0 (which is checked below).
// The Serialx_available is just a wrapper around Serialx.available(),
// but we can refer to it weakly so we don't pull in the entire
// HardwareSerial instance if the user doesn't also refer to it.
#if defined(HAVE_HWSERIAL0)
void serialEvent() __attribute__((weak)); // <======
bool Serial0_available() __attribute__((weak));
#endif
Explanation of weak and strong definitions and declarations:
The way weak and strong function definitions work is that at link-time in the compile process, the linker looks for all definitions of each function, and if it finds both a strong and a weak definition for a function of a particular name, it uses the strong one and discards the weak one, linking it into the final binary executable which gets flashed to the microcontroller.
Furthermore, a function with a weak declaration but no definition is given a program space memory address of 0
(zero) by the linker, meaning that in C or C++ code, my_declared_but_undefined_func == 0;
would be true
. This is because the weak declaration is just a placeholder, and the linker knows that if it doesn't find any definition for that function, it should just give it a null address of 0
(zero) and not link it into the final binary executable.
So, if you define a serialEvent()
function at all, that makes the address to that function non-zero (whereas if it was simply the gcc weak
declaration, with no weak definition, it would remain zero), making this if
statement true, so serialEvent()
gets called inside the serialEventRun()
function:
void serialEventRun(void)
{
#if defined(HAVE_HWSERIAL0)
if (Serial0_available && serialEvent && Serial0_available()) serialEvent();
// ...
#endif
}
And, that in-turn gets called in the Arduino main()
function here:
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
So, let's go over that again. If you do NOT define serialEvent()
, then its only declaration is this weak
one, and it has no definition at all:
void serialEvent() __attribute__((weak));
That's a weak declaration, so it will simply give the function a program space memory address of 0
(zero) in the event it is never defined. That means that this if
statement is false, so serialEvent()
is never called inside serialEventRun()
:
if (Serial0_available && serialEvent && Serial0_available()) serialEvent();
If that's the case, then serialEventRun()
contains nothing and apparently is also given a program space address of 0
(zero) since it has nothing to do. That makes this if
statement in the main()
function false, and serialEventRun()
never gets called either:
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
serialEvent()
at allPersonally, I don't think you should use serialEvent
at all. Just add the logic to the loop()
function directly, like this. You get the exact same effect and get to control things manually yourself instead. Notice that the only changes I made were:
serialEvent()
function to readSerial()
.readSerial()
call to the top of your loop()
function.That's it! Here is the code with those changes. I tested it on an ATmega328-based Pro Mini just now and it works fine, properly printing out any newline (\n
)-terminated string I send to it, same as the unmodified code for this example as well.
String inputString = ""; // a String to hold incoming data
bool stringComplete = false; // whether the string is complete
void setup() {
// initialize serial:
Serial.begin(9600);
// reserve 200 bytes for the inputString:
inputString.reserve(200);
}
void loop() {
readSerial();
// print the string when a newline arrives:
if (stringComplete) {
Serial.println(inputString);
// clear the string:
inputString = "";
stringComplete = false;
}
}
void readSerial() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if (inChar == '\n') {
stringComplete = true;
}
}
}
Why did they make the weak serialEvent()
function at all when that's all you need to do to control this manually as it is? I don't know. It's probably a convenience thing: it saves you a few lines of code if you are doing this (overriding weak
function calls) for multiple serial event calls, such as serialEvent()
, serialEvent1()
, serialEvent2()
, and serialEvent3()
. For more-obvious control, however, I'd just manually make my own functions and call them inside the loop()
function instead, as I do just above.
gcc weak
function attribute: https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html (the most-recent gcc documentation on this is now here: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-weak-function-attribute):
weak
The
weak
attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.
From my eRCaGuy_hello_world repo, here is my check_addr_of_weak_undefined_funcs.cpp file where I prove to myself that weak
functions which are declared but not defined really do have addresses equal to nil (ie: zero, or 0
).
Upvotes: 4
Reputation: 51
I found the basic problem is in the function you may have on your PC which publishes data over serial to the Arduino. It is NOT in the serialEvent()
function on the Arduino which receives that data. The problem is that when you send a message by serial to the Arduino, you have to include a newline (\n
char), so that the end-of-message detection on the Arduino in the serialEvent()
function works.
Examples in different programming languages:
Sending message to the Arduino from your PC by C:
String Message = "Hello world.\n";
Sending message to the Arduino from your PC by C#:
string Message = "Hello world." + System.Environment.NewLine;
Sending message to the Arduino from your PC by JavaScript via nwjs:
var Message = "Hello world.\n";
Sending message to the Arduino from your PC by Python:
Message = "Hello world.\n";
# OR
Message = """Hello world.
""";
The situation is similar in other programming languages.
Upvotes: 1