Read+write on serial port mixes input and output

Hi,

I apologize for not posting a working example, but I haven’t figured out how to make it.

The problem is, I have an fpga hooked up to the usb on my Ubuntu-box. The ftdi-driver makes it look like an uart, a serial port, /dev/fpga. It works like this, I send it a short string to start it. I read output for a minute, then send a new short string to stop the output, change some parameters, and restart output, and read for another minute. And so on. Here’s the problem:

So it looks something like this:

io = open("/dev/fpga",  read=true, write=true)
for i in 1:N
  write(io,"p N=$i "); flush(io) # change parameter
  write(io,"r"); flush(io) # resume
  while <less than a minute passed>
     read!(io,buffer)
     process(buffer)  # mainly a push! on an array
  end
  write(io,"s"); flush(io) # stop
  <spawn some post-processing>
end

Now, the problem is that the fpga has quite some speed, and it manages to write several bytes between the final read! and the stop command at the end of the loop. This is not a problem, these bytes will be ignored by the processing routine.

However, when strace’ing reads and writes it transpires that the string “p N=$i” at the top of the loop has a few bytes prepended in the (system) write(), and it turns out to be bytes which the fpga has written to me and which I have not yet read (and which I will discard in the loop). These are then written back to the fpga. Occasionally, these bytes will be interpreted by the fpga as a command, and havoc ensues. The serial line is in raw-mode (i.e. with cfmakeraw()), verified by stty, no echo and such stuff.

It looks like the read() and write() shares a buffer. Things work fine when I replace open/read/write with ccall’s to open/read/write, i.e. when I bypass julia IO.

Is there a julia-workaround? I suppose I can use two opens, one for read and one for write, but that’s a bit awkward.

Huh, this looks scary and is probably worth opening a bug report for. Poking around in the source shows that ios_t has only one buffer. Data is read and written to that buffer depending on the state of the ios_t struct. I’m not sure how to make a test for this that doesn’t need hardware. In your code, how big is buffer? It looks like the read logic sometimes tries to read as much as possible and sometimes it just fills buffer depending on its size.

I have tried to use LibSerialPort.jl, but I had problems I couldn’t figure out with it appearing to drop certain received byte sequences.

There is also a wrapper for PySerial called SerialPorts.jl, I have not tested that.

In the end, the last time I had to do this, I used ccall and wrote the code just like I would in C, painful but effective.

It’s zeros(Int32,16).

Yikes. I see the same behavior with Julia 1.6.2 on an SBC with a serial peripheral talking to a USB-UART adapter connected to macOS.

@sgaure or @contradict Did either of you already raise a bug report for this?

I did not, I tried but failed to create a reproducible test case. If you have one that would be great!

1 Like

I have managed to reproduce it with this simple example.

I’m using a standard SBC (nVidia Jetson) where I have connected the UART TX/RX pins together, so it’s doing an external loopback. I also turned off TERMIOS level echo.

I agree, it’s a bit of an awkward MWE because it’s hard to differentiate if it’s an issue with read or write. But this example can be tested without any extra hardware.

Let me know if it’s similar to what you experienced. If so I’ll raise a bug.

ubuntu@unet:~$ stty -echo -F /dev/ttyTHS2

ubuntu@unet:~$ stty -a -F /dev/ttyTHS2
speed 115200 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 100; time = 2;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread clocal -crtscts
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl ixon -ixoff -iuclc
-ixany -imaxbel -iutf8
-opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl
echoke -flusho -extproc

ubuntu@unet:~$ julia
...

julia> io = open("/dev/ttyTHS2", "r+")
IOStream(<file /dev/ttyTHS2>)

julia> write(io, "hello"); flush(io)

julia> read(io, String)
"hello"

julia> write(io, "world"); flush(io)

julia>read(io, String)
"helloworld"

I get the same result under 1.6.2 and 1.7.0 using a looped-back USB-serial adapter.

1 Like

Looks like someone did open an issue. The first workaround suggested does not work for me (ERROR: SystemError: lseek: Illegal seek, but opening the port twice, once read only and once write only, does seem to fix the test here.

1 Like