elixir-circuits / circuits_uart

Discover and use UARTs and serial ports in Elixir
Apache License 2.0
189 stars 48 forks source link

250k Baud communication #15

Closed salzig closed 7 years ago

salzig commented 7 years ago

Hej there, i try to communicate with a Chinese Arduino Mega 2560 (250000 Baud).

As a precheck i used the arduino IDE serial monitor and sending "G28\r\n", which worked.

After that i tried to play around with nerves_uart in a iex session.

iex(1)> Nerves.UART.enumerate
%{"/dev/cu.Bluetooth-Incoming-Port" => %{},
  "/dev/cu.JabraREVOa400-SPPDev" => %{},
  "/dev/cu.SLAB_USBtoUART" => %{manufacturer: "Silicon Labs", product_id: 60000,
    serial_number: "0001", vendor_id: 4292}}
iex(2)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.140.0>}
iex(3)> Nerves.UART.open(pid, "/dev/cu.SLAB_USBtoUART", speed: 250000, active: true)
:ok
iex(4)> Nerves.UART.write(pid, "G28\r\n")
:ok

Which doesn't result in what i was expecting. Did i miss something?

Something i observed: starting the arduino serial monitor will reset the mega2560. But it should work none the less right?

Edit: On first glimpse it looks like the provided nerves uart c-code handles custom baud rates. But i couldn't find any example using it. Maybe it's not working?

salzig commented 7 years ago

i did some more investigation.

To reset the arduino i need to toggle Nerves.UART.set_dtr(pid, boolean) twice (1. false, 2. true).

iex(1)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.139.0>}
iex(2)> Nerves.UART.enumerate
%{"/dev/cu.Bluetooth-Incoming-Port" => %{},
  "/dev/cu.JabraREVOa400-SPPDev" => %{},
  "/dev/cu.SLAB_USBtoUART" => %{manufacturer: "Silicon Labs", product_id: 60000,
    serial_number: "0001", vendor_id: 4292}}
iex(3)> Nerves.UART.open(pid, "/dev/cu.SLAB_USBtoUART", active: false, speed: 250000)
:ok
iex(4)> Nerves.UART.set_dtr(pid, false) 
:ok
iex(5)> Nerves.UART.set_dtr(pid, true)
:ok
iex(6)> { :ok, message } = Nerves.UART.read(pid) 
{:ok,
 <<87, 161, 171, 15, 206, 233, 146, 126, 105, 57, 141, 172, 233, 207, 104, 10,
   243, 96, 107, 199, 173, 144, 164, 175, 165, 41, 133, 40, 61, 179, 157, 185,
   33, 172, 21, 161, 80, 253, 237>>}
iex(7)> String.codepoints(message)
["W", <<161>>, <<171>>, <<15>>, <<206>>, <<233>>, <<146>>, "~", "i", "9",
 <<141>>, <<172>>, <<233>>, <<207>>, "h", "\n", <<243>>, "`", "k", "ǭ",
 <<144>>, <<164>>, <<175>>, <<165>>, ")", <<133>>, "(", "=", <<179>>, <<157>>,
 <<185>>, "!", <<172>>, <<21>>, <<161>>, "P", <<253>>, <<237>>]

now i can see all message written by arduino on boot, but it seems like the used baud rate isn't right. I checked again with the arduino IDE, everything works fine with 250k baud there.

edit: I expect to get around 906chars, but read returns only 36. 906/36 = 25.16 so i get 1/25 number of letters as expected. Which could mean that the serialdevice is used with around 10k baud (250k/25 = 10k), maybe 9600baud.

fhunleth commented 7 years ago

I suspect that the custom baud rate code isn't quite right:

https://github.com/nerves-project/nerves_uart/blob/master/src/uart_comm_unix.c#L185

I hardly ever use custom baud rates and even less so on OSX. If you're not afraid to dive into the C code, I might try hardcoding IOSSIOSPEED ioctl after calls to tcsetattr. Maybe tcsetattr is overriding the custom baud rate.

salzig commented 7 years ago

Thanks for the hint. I just checked if the call to set_custom_speed did was i was expecting. Which is does. Right now i'm not really sure if it's maybe a driver problem, after also having a look into the Code of https://developer.apple.com/library/content/samplecode/SerialPortSample/Introduction/Intro.html Which mentions: "The driver for the underlying serial hardware ultimately determines which baud rates can be used.".

In the end, it works with Arduino on this machine. I'll try to fix the c code, if i can.

salzig commented 7 years ago

Ok, i found two variants that work out.

First Variant: set the speed in options to the configured custom baudrate.

@@ -250,9 +250,12 @@ static int uart_config_line(int fd, const struct uart_config *config)
         // Linux lets you set custom baudrates for the B38400 option
         cfsetispeed(&options, B38400);
         cfsetospeed(&options, B38400);
+#elif defined(__APPLE__)
+        cfsetspeed(&options, config->speed);
 #endif

Second Variant: set speed via ioctl after tcsetattr

@@ -305,7 +306,10 @@ static int uart_config_line(int fd, const struct uart_config *config)
     options.c_cc[VTIME] = 0;

     // Set everything
-    return tcsetattr(fd, TCSANOW, &options);
+    int result = tcsetattr(fd, TCSANOW, &options);
+    speed_t sio_speed = config->speed;
+    ioctl(fd, IOSSIOSPEED, &sio_speed);
+    return result;

Which version would you prefer @fhunleth? Or do you have any feedback? Would love to create a (pull|merge)-request afterwards.

salzig commented 7 years ago

as a side note, in both variants passive mode seems not to work anymore, independent of configuration passed to UART.open it will always end in active mode.

fhunleth commented 7 years ago

Awesome! (Well except for the passive mode issue - that's weird)

Code-wise I like the cfsetspeed option (first one), but I was surprised that it worked since the parameter is an enumerated type. On Linux, the enumerated types don't match the baud rates at all, but I saw that on OSX they do. I wonder if you change the to_baudrate_constant() function on __APPLE__ to just return speed;. Maybe that would fix everything since your baud rate would no longer be custom?

My concern is that this is serial driver-specific and maybe both cfsetspeed and the ioctl(fd, IOSSIOSPEED) are needed. However, I'm good for merging a PR if your setup is fixed and then if someone else runs into the issue, we can experiment with their setup.

salzig commented 7 years ago

I still need to find a bug.

iex(1)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.139.0>}
iex(2)> Nerves.UART.open(pid, "/dev/cu.SLAB_USBtoUART", speed: 250000, active: true, framing: {Nerves.UART.Framing.Line, separator: "\n"}) 
{:error, :einval}
iex(3)> Nerves.UART.set_dtr(pid, false)
:ok
iex(4)> Nerves.UART.set_dtr(pid, true)
:ok
iex(5)> :timer.sleep(1000);
:ok
iex(6)> Nerves.UART.write(pid, "G28")
:ok
iex(7)> Nerves.UART.write(pid, "G1 Z0 F10000")
:ok
iex(8)> Nerves.UART.write(pid, "G28")
:ok
iex(9)> :timer.sleep(5000);
:ok
iex(10)> flush
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "start"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Marlin1.0.0"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo: Last Updated: Feb 28 2017 20:26:44 | Author: (Leafy)"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "Compiled: Feb 28 2017"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo: Free Memory: 3634  PlannerBufferBytes: 1232"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Hardcoded Default Settings Loaded"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Steps per unit:"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M92 X80.00 Y80.00 Z80.00 E96.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Maximum feedrates (mm/s):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M203 X200.00 Y200.00 Z200.00 E200.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Maximum Acceleration (mm/s2):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M201 X3000 Y3000 Z3000 E3000"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Acceleration: S=acceleration, T=retract acceleration"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M204 S3000.00 T3000.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Advanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M205 S0.00 T0.00 B20000 X20.00 Z20.00 E20.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Home offset (mm):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M206 X0.00 Y0.00 Z0.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Endstop adjustement (mm):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M666 X0.00 Y0.00 Z0.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:PID settings:"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:   M301 P22.20 I1.08 D114.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "ok"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:SD card ok"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "ok"}
:ok

as you can see, i get an {:error, :einval} for UART.open, but it works nonetheless. O.o

fhunleth commented 7 years ago

When this is figured out, I'll make a new nerves_uart release. I might have some time this weekend to try 250KBaud out on my Mac, but I'm really hoping that you figure it out first! ;-)

salzig commented 7 years ago

So far i didn't manage to figure out how to solve einval, sadly.

My C-FU isn't that good, as it seems.

fhunleth commented 7 years ago

Hey - I programmed an Arduino Uno to output at 250000 baud and connected it to my Mac. I think that I have something that may work for you. Could you try out my custom-speed branch? Both active and passive mode work for me. I didn't try sending data to the Uno, so I can't say that it's fully tested. The caveat is that changing baudrate, flow control, etc. after the call to open/3 will probably fail since the SIOSSIOSPEED ioctl seems to totally mess up tcsetattr. I moved the ioctl to the end of function that opening the port succeeds. Not many people change the port settings after open, so hopefully this fix satisfies your use and the majority of others using custom speeds on OSX.

salzig commented 7 years ago

Yeah, works and without the einval. Awesome!

active:

iex(1)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.139.0>}
iex(2)> Nerves.UART.open(pid, "/dev/cu.SLAB_USBtoUART", speed: 250000, active: true, framing: {Nerves.UART.Framing.Line, separator: "\n"}) 
:ok
iex(3)> Nerves.UART.set_dtr(pid, false)
:ok
iex(4)> Nerves.UART.set_dtr(pid, true)
:ok
iex(5)> :timer.sleep(1000);
:ok
iex(6)> Nerves.UART.write(pid, "G28")
:ok
iex(7)> Nerves.UART.write(pid, "G1 Z0 F10000")
:ok
iex(8)> Nerves.UART.write(pid, "G28")
:ok
iex(9)> :timer.sleep(5000);
:ok
iex(10)> flush
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "start"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Marlin1.0.0"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo: Last Updated: Feb 28 2017 20:26:44 | Author: (Leafy)"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "Compiled: Feb 28 2017"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo: Free Memory: 3634  PlannerBufferBytes: 1232"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Hardcoded Default Settings Loaded"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Steps per unit:"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M92 X80.00 Y80.00 Z80.00 E96.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Maximum feedrates (mm/s):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M203 X200.00 Y200.00 Z200.00 E200.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Maximum Acceleration (mm/s2):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M201 X3000 Y3000 Z3000 E3000"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Acceleration: S=acceleration, T=retract acceleration"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M204 S3000.00 T3000.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:Advanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART",
 "echo:  M205 S0.00 T0.00 B20000 X20.00 Z20.00 E20.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Home offset (mm):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M206 X0.00 Y0.00 Z0.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:Endstop adjustement (mm):"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:  M666 X0.00 Y0.00 Z0.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:PID settings:"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:   M301 P22.20 I1.08 D114.00"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "ok"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "echo:SD card ok"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "ok"}
{:nerves_uart, "/dev/cu.SLAB_USBtoUART", "ok"}
:ok

passive:

iex(2)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.140.0>}
iex(3)> Nerves.UART.open(pid, "/dev/cu.SLAB_USBtoUART", speed: 250000, active: false) 
:ok
iex(4)> Nerves.UART.set_dtr(pid, false)
:ok
iex(5)> Nerves.UART.set_dtr(pid, true)
:ok
iex(6)> :timer.sleep(3000);
:ok
iex(7)> Nerves.UART.read(pid)
{:ok,
 "start\necho:Marlin1.0.0\necho: Last Updated: Feb 28 2017 20:26:44 | Author: (Leafy)\nCompiled: Feb 28 2017\necho: Free Memory: 3634  PlannerBufferBytes: 1232\necho:Hardcoded Default Settings Loaded\necho:Steps per unit:\necho:  M92 X80.00 Y80.00 Z80.00 E96.00\necho:Maximum feedrates (mm/s):\necho:  M203 X200.00 Y200.00 Z200.00 E200.00\necho:Maximum Acceleration (mm/s2):\necho:  M201 X3000 Y3000 Z3000 E3000\necho:Acceleration: S=acceleration, T=retract acceleration\necho:  M204 S3000.00 T3000.00\necho:Advanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)\necho:  M205 S0.00 T0.00 B20000 X20.00 Z20.00 E20.00\necho:Home offset (mm):\necho:  M206 X0.00 Y0.00 Z0.00\necho:Endstop adjustement (mm):\necho:  M666 X0.00 Y0.00 Z0.00\necho:PID settings:\necho:   M301 P22.20 I1.08 D114.00\necho:SD card ok\n"}
iex(8)> Nerves.UART.write(pid, "G28\n")
:ok
iex(9)> Nerves.UART.read(pid)
{:ok, "ok\n"}
iex(10)> Nerves.UART.write(pid, "G1 Z0 F10000\n")
:ok
iex(11)> Nerves.UART.read(pid)
{:ok, "ok\n"}
iex(12)> Nerves.UART.write(pid, "G28\n")
:ok
iex(13)> Nerves.UART.read(pid)
{:ok, ""}
fhunleth commented 7 years ago

Thanks for verifying! I'm going to merge and push out an updated release.