Arduino Serial Input

Overview

PyPi module N/A
git repository https://bitbucket.org/arrizza-public/arduino-serial-input
git command git clone git@bitbucket.org:arrizza-public/arduino-serial-input.git
Verification Report https://arrizza.com/web-ver/arduino-serial-input-report.html
Version Info
  • macOS 14.5, Python 3.10
  • Ubuntu 20.04 focal, Python 3.10
  • Ubuntu 22.04 jammy, Python 3.10
  • Ubuntu 24.04 noble, Python 3.10

Summary

Accept text from the Arduino Serial Port.

It gets a single character from the serial port and prints some information about it.

If it's a 'b' or a digit ('0' to '9') then it also blinks the LED.

Configure cmake

See Arduino Setup for details on how to set up CMakeLists.txt for your arduino and port.

Setup

The setup function is roughly the same as that for Arduino Serial Output except the initial message is now "serial input" and the LED is internalized too, see Arduino LED Blink

Main Loop

The main loop checks to see if a character is available on the serial port. If not, it exits the loop() function immediately. The loop() function is called continually, so this bit of code is run at maximum speed.

The Serial.available() call returns the number of bytes waiting in the serial port buffer

if (Serial.available() <= 0)
    {
    return;
    }

If there is at least one byte waiting, it's read. The read() function simply returns the next byte from the serial port buffer. the "char ch" declares that byte to be a character and initially sets it to 0 (a "null character")

char ch = 0;
// ... other code ...
ch = Serial.read();

Then the value of the byte is printed in hexadecimal (aka "hex" - base 16) and in decimal (our normal base 10). the character itself is also printed.

Serial.print("recv: 0x");
Serial.print(ch, HEX);
Serial.print("=");
Serial.print(ch, DEC);
Serial.print("=");
Serial.println(ch);

In the CLion Serial Monitor (see "Install CLion" Arduino Setup, enter "abc" in the input text area. Make sure " Send EOL" is not checked. Then press Send. You should see the character information for 'a', 'b' and 'c'.

recv: 0x61=97=a
recv: 0x62=98=b
recv: 0x62=98=b
blink 1 of 3
blink 2 of 3
blink 3 of 3
recv: 0x63=99=c

This output shows that the 'a' is hex 0x61 (the "0x" prefix is just a reminder that it is in hex). It is decimal 97. And it is displayed on the screen like so: "a". To see all the character values, you can see https://www.asciitable.com or type them in to the Serial Monitor.

When the code sees the character 'b', it prints :"blink" and blinks the LED 3 times. If you enter a digit '0' thru '9', that gets converted to an integer and the led is flashed that many times.

int num_blinks = 0;
if (ch == 'b') {
    num_blinks = 3;
}

if (isdigit(ch)) {
    num_blinks = ch - '0';
}

// blink the LED
for (int i = 0; i < 3; ++i)
  {
  digitalWrite(13, HIGH);
  delay(200);
  digitalWrite(13, LOW);
  delay(200);
  }

Notes

  • on the Mega, any serial port communication already blinks an LED. It is right next to LED attached to pin 13. The pin 13 LED blinks a little slower than the communication LED.

  • the Serial Monitor in CLion is ok when you want to send a string of characters down to the Arduino. You enter the characters and then press Send.

  • If you want it more interactive, use PuTTY. Set up putty as described here "Setup putty" in Arduino Setup and then open up a PuTTY session. As you type in characters one-by-one, they'll be sent to the Arduino and it will respond immediately.

Automate it with Python

Install Python

  • ensure the pyserial module is installed: run ./do_install

    • if you want to use a different module update tools/requirements.txt and rerun ./do_install
  • activate the environment

source tools/set_env.sh
source venv/bin/activate
$pyexe -m pip list

Run the python script

(venv) $pyexe auto_test.py
recv: 0x48=72=H
recv: 0x65=101=e
recv: 0x6C=108=l
recv: 0x6C=108=l
recv: 0x6F=111=o
recv: 0x20=32= 
recv: 0x57=87=W
recv: 0x6F=111=o
recv: 0x72=114=r
recv: 0x6C=108=l
recv: 0x64=100=d
recv: 0x21=33=!

How does it work?

  • init() opens the serial port. If it can't be opened you get an exception
  • term() closes the serial port.
  • run() reads the message to send one character at a time, converts into a byte and sends it.
    • after each character it checks if there is any incoming bytes
    • if so it (see _read_response)
      • if the incoming byte is a linefeed ('\r'), it returns back to send another character
      • otherwise it converts the incoming byte to a character
      • prints it on the screen

Automate it with Ruby

Install Ruby

  • if you don't have ruby installed, install it now
  $ sudo apt install ruby
  $ ruby -v
  ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
  # your version may be different
  • install the ruby serial port gem
$ sudo gem install serialport
$ gem list serialport
*** LOCAL GEMS ***
serialport (1.3.2)
# your version may be different

Run the ruby script

  • edit auto_test.rb with the correct port id:
@port            = '/dev/ttyUSB0')
  • run auto_test.rb
$ ruby auto_test.rb
recv: 0x48=72=H
recv: 0x65=101=e
recv: 0x6C=108=l
recv: 0x6C=108=l
recv: 0x6F=111=o
recv: 0x20=32=
recv: 0x57=87=W
recv: 0x6F=111=o
recv: 0x72=114=r
recv: 0x6C=108=l
recv: 0x64=100=d
recv: 0x21=33=!
  • Note: the first time you run, the initial "H" line might be missing.
$ ruby auto_test.rb
serial input
recv: 0x65=101=e
recv: 0x6C=108=l
# ... skip ...

That's because the arduino isn't ready for serial communication yet. Rerun it and it will work fine.

How does it work?

  • It sets up the serial port
@port = '/dev/ttyUSB0'
parity = SerialPort::NONE
@mc    = SerialPort.new(@port, 115_200, 8, 1, parity)
  • it's unlikely, but there could be characters in the serial port from a previous session, so clean them out
@mc.read_timeout = -1
@mc.flush_input
@mc.flush_output

# read a couple of times to get rid of garbage
@mc.getbyte
@mc.getbyte

# reset the read_time to 1s
@mc.read_timeout = 1000
  • it takes a string "Hello World!" and sends it character by character to the Arduino
s = "Hello World!"
s.each_byte do |ch|
  @mc.putc(ch)
  read_response()
end
  • after each character it waits for the Arduino's response. The function read_response() does that work with a simple loop to read the input a character at a time until a linefeed. The linefeed has ASCII code 0x0A and is sent by the Arduino by the println() function (see above)

The .chr() ruby function converts a byte (an integer) into a character (a letter)

  loop do
    # get the next byte
    ch = @mc.getbyte

    # if we got nothing from the Arduino,
    # it may have timed out, try again
    continue if ch.nil?

    # the response ends on a linefeed from the Arduino
    if ch == 0x0A
      puts
      return
    end

    # print the character as a character (not a byte) on your PC
    print(ch.chr)
  end
  • we're done with it, so close the serial port
@mc.close

- John Arrizza