0

I have got an Arduino UNO. I wrote a script to control the lights connected to PIN 2, 3, 4 on my Arduino. The lights can be turned off or on using my laptop:

  • 0 turns off all the LEDS
  • 1 turns on LED on pin 1
  • 2 turns on LED on pin 2
  • 3 turns on LED on pin 1 and 2
  • 4 turns on LED on pin 3
  • 5 turns on LED on pin 1 and 3
  • 6 turns on LED on pin 2 and 3
  • 7 turns on all the LEDS.

I am actually interested in only sending data from my laptop and blink the lights accordingly.

Here's my program:

# pragma GCC optimize ("Ofast")

# define LED1 2
# define LED2 3
# define LED3 4

char buf[4] ;
unsigned char inp, len ;

void setup() {
    pinMode(LED1, OUTPUT) ;
    pinMode(LED2, OUTPUT) ;
    pinMode(LED3, OUTPUT) ;

    Serial.begin(115200) ;
    Serial.setTimeout(1) ;
}

void loop() {
    if (Serial.available() > 0) {
        len = Serial.readBytes(buf, 3) ;
    
        if (len) {
            buf[3] = '\0' ;
            inp = atoi(buf) ;
    
            // Illuminate LEDs
            digitalWrite(LED1, 1 & inp ? HIGH : LOW) ;
            digitalWrite(LED2, 2 & inp ? HIGH : LOW) ;
            digitalWrite(LED3, 4 & inp ? HIGH : LOW) ;
        }
    }
}

It works fine when I open the IDE's Serial Monitor and I write printf 1 > /dev/ttyUSB0.

But when I close the Serial monitor, and printf 1 > /dev/ttyUSB0, it doesn't work, and the only thing I get is the flashing onboard LED.

So every time I need to light up an LED, I must make sure the IDE is open.

Is there a way to write to the Arduino serial port from any Linux computer without opening the IDE?

15 Volts
  • 1,946
  • 15
  • 37
  • Uno resets on new USB connection. why would you send `printf 1 > /dev/ttyUSB0` with Serial Monitor if you want to send 1? – Juraj Apr 17 '21 at 09:40
  • Your code is waiting on 3 bytes. `printf 1` sends one byte. But, you receive 1 byte due to the 1ms timeout you have. So hard to say what `buf[1]` and `buf[2]` contain at that point. If all you need is 3 bits, you should be sending and receiving just one byte. – lurker Apr 17 '21 at 12:07
  • Did you try `echo 1 > /dev/ttyUSB0`? It adds a newline like the Serial Monitor. -- Did you check the level of the reset signal on the Uno? – the busybee Apr 17 '21 at 12:20
  • Hi, yes, if I echo, it doesn't work as well. Before echo I have to do this: `exec 3<> /dev/ttyUSB0` – 15 Volts Apr 19 '21 at 08:13

1 Answers1

0

Ok, so the problem was that arduino has the Serial.read() code. And the data I am sending fails unless I open the Arduino IDE's Serial Monitor. In Other words, if I want to create a hotpluggable device which my laptop searches for in a loop and when plugged in, writes data to it, it won't be possible.

Now to solve the issue, I needed to take a look at what the IDE's Serial Monitor does, it sets the baudrate, and opens the file descriptor 3 on the selected port.

So to solve the hotplugging issue, I first needed to set the baudrate and matching flags first. Then I need to open the file with fcntl file descriptor 3 just like the IDE's Serial Monitor.

Since I was writing the sending bytes in Ruby, I had the opportunity to write C Ruby. I set the baudrate with this code:

VALUE setBaudRate(volatile VALUE obj, volatile VALUE dev, volatile VALUE speed) {
    char *device = StringValuePtr(dev) ;
    unsigned int spd = NUM2UINT(speed) ;

    int serial_port = open(device, O_RDWR) ;
    struct termios tty ;

    char status = tcgetattr(serial_port, &tty) ;

    // IDE sets these flags
    // speed 57600 baud; line = 0;
    // min = 0; time = 0;
    // -brkint -icrnl -imaxbel
    // -opost
    // -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

    if(status != 0) return Qnil ;
    tty.c_cflag &= ~CSIZE ;
    tty.c_cflag |= CS8 ;
    tty.c_cflag |= CREAD | CLOCAL ;
    tty.c_oflag &= ~OPOST ;

    tty.c_lflag &= ~ECHO ;
    tty.c_lflag &= ~ECHOCTL ;
    tty.c_lflag &= ~ECHOK ;
    tty.c_lflag &= ~ECHOE ;
    tty.c_lflag &= ~ECHOKE ;
    tty.c_lflag &= ~ISIG ;
    tty.c_lflag &= ~ICRNL ;
    tty.c_lflag &= ~IEXTEN ;
    tty.c_lflag &= ~ICANON ;

    tty.c_cc[VTIME] = 0 ;
    tty.c_cc[VMIN] = 0 ;

    cfsetispeed(&tty, spd) ;
    cfsetospeed(&tty, spd) ;
    status = tcsetattr(serial_port, TCSANOW, &tty) ;

    if (status == 0) return Qtrue ;
    else return Qfalse ;
}

If you are writing C, you just replace the VALUE function name to whatever you want. And return whatever you like on failure, it can be 0 for success, 1 and 2 for failure.

Now, make sure the baudrate matches perfectly with the arduino. Here in my Ruby C Extension, I am actually getting the baudrate from Ruby volatile VALUE speed. It then sets the speed. A pure C implementation won't be much different.

In BASH however, you can just use stty:

sudo stty -F /dev/ttyUSB0 -cread -clocal -isig -iexten -echo -echoe -echok -echoctl -echoe -brkint -icrnl -imaxbel -opost -icanon min 0 time 0 57600

Anyways, to write to the file, you need to open the file in read write mode. Ruby provides that, I am attaching the code in case it helps someone:

require 'fcntl'
device = '/dev/ttyUSB0'

fd = IO.sysopen(device, Fcntl::O_RDWR | Fcntl::O_EXCL)
file = IO.open(fd)
file.syswrite("Hello world!")
file.close

In BASH, you can just use this command:

exec 3<> /dev/ttyUSB0

Now if you want not to hardcode the /dev/ttyUSB0, you need to send some bytes from the arduino, and read that after you set the correct baudrate matching with arduino.

In this way we can read and write the value to and from the arduino!

15 Volts
  • 1,946
  • 15
  • 37