elixir-circuits / circuits_uart Goto Github PK
View Code? Open in Web Editor NEWDiscover and use UARTs and serial ports in Elixir
License: Apache License 2.0
Discover and use UARTs and serial ports in Elixir
License: Apache License 2.0
No significant latency
Latency up to a second with modems set to 20ms window to minimise buffering
I have a pair of radio modems at close to point blank range set to 57600 baud sending a short < 100 byte message. The modems have a firmware maximum window size config of 20ms which is the setting normally used to minimise latency. I minimised the number of other messages (they support sending hundreds of messages of similar size with no throughput issues), and the same software setup running over WiFi/UDP has no noticable delay. However when I use the modems with one of them via circuits UART I get a latency delay of about a second. I have set the uart connection to active.
I know there are lots of variables in play here, but I was wondering if there are any configuration settings you would use to minimise latency of short messages when using this library?
Thanks,
Robin
Hi. When reading serial data through messages (active: true), the data received is in the form ‘{:nerves_uart, port_name, data}’. Because og this, I need to maintain a map of name to pids, as pids are what I can use to send messages back to the receiving serial port. I would have expected and preferred the messages to be in the format: ‘{:nerves_uart, pid, data}’. This could be implemented in a backeard compatible way by having an argument to ‘open’ such as: ‘open(pid, name, active: :format2)
Hi Everyone, I am unsure what I'm doing wrong here to compile the circuits_uart project
I have included both mingw32-make and mingw-w64 in the PATH as well as created an environment variable for the windows driver SDK INCLUDE paths.
I don't feel like copying and throwing the dependent include files into the ei_copy is the best solution, but tell me if I should try.
I run mix compile and it gives the following compilation error
==> circuits_uart
" CC uart_enum.o"
" CC circuits_uart.o"
" CC debug_tests.o"
" CC uart_enum_win.o"
src/uart_enum_win.c:24:22: fatal error: ntddmodm.h: No such file or directory
#include <ntddmodm.h>
^
compilation terminated.
src/Makefile:106: recipe for target ......
Not having success on OSX
iex(1)> Nerves.UART.enumerate
** (ArgumentError) argument error
:erlang.port_close(#Port<0.182363>)
lib/uart/enumerator.ex:17: Nerves.UART.Enumerator.enumerate/0
Interactive Elixir (1.2.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, pid} = Nerves.UART.start_link
{:ok, #PID<0.13578.0>}
Going to terminate: {:function_clause, [{Nerves.UART, :handle_info, [{#Port<0.20777>, {:exit_status, 1}}, %{controlling_process: nil, name: nil, port: #Port<0.20777>}], [file: 'lib/nerves_uart.ex', line: 286]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 615]}, {:gen_server, :handle_msg, 5, [file: 'gen_server.erl', line: 681]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 240]}]}
** (EXIT from #PID<0.385.0>) an exception was raised:
** (ArgumentError) argument error
:erlang.port_close(#Port<0.20777>)
(stdlib) gen_server.erl:643: :gen_server.try_terminate/3
(stdlib) gen_server.erl:809: :gen_server.terminate/7
(stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Interactive Elixir (1.2.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(2)>
20:35:03.103 [error] GenServer #PID<0.13578.0> terminating
** (ArgumentError) argument error
:erlang.port_close(#Port<0.20777>)
(stdlib) gen_server.erl:643: :gen_server.try_terminate/3
(stdlib) gen_server.erl:809: :gen_server.terminate/7
(stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {#Port<0.20777>, {:exit_status, 1}}
State: %{controlling_process: nil, name: nil, port: #Port<0.20777>}
seems the port opened here is in a closed state before it is closed here
But I cannot figure out how or why ....
Operating System: x86_64-apple-darwin15.0.0
ERTS: Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:8] [async-threads:0] [hipe] [kernel-poll:false] [dtrace]
Elixir 1.2.4
I worked through getting nerves_uart
building on x86_64 Alpine Linux today and wanted to share my setup.
This is part of a multi-part build in Docker so I'll just share my Dockerfile
Dockerfile.txt
The relevant parts are the apk packages that are installed.
RUN apk add --update git erlang-dev build-base make gcc abuild binutils cmake linux-headers
Hopefully this can help other people looking to do the same.
Hi team.
I'm evaluating Circuits.UART for my next project.
We need to push a lot of usually small or sometimes bigger messages (50-2000 bytes) through serial line.
Performance is critical aspect here - we want to send as many messages as possible. They could be glued together on serial line (separated by <<0x7E>> byte)
After couple of days spent on testing Circuits.UART on different platforms I could summarize my observations in a list below.
Issues 1-4 could be somehow mitigated by calling UART.drain but delay introduced with such approach (27ms on RasPI3) is not acceptable.
Could you evaluate how problematic will be to solve those issues? (if you agree on such classification)
This is related to issue #26
Version: 1.3.1
OS: Linux
Platform:
PC:
Ubuntu on Intel Xeon E3-1535M, Dell 7510
Erlang 21.3.2, Elixir 1.8.1 (from erlang-solutions.com repos)
Real UART 16550A on docking station - ttyS0.
Raspberry PI, Raspberry PI3:
Latest Raspian
Erlang 20.1.5, Elixir 1.7.4 (from erlang-solutions.com repos)
ttyS0 on RasPI3, ttyAMA0 on RasPI
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
13:00:56.033 [error] GenServer #PID<0.159.0> terminating
** (ArgumentError) argument error
...
...
defmodule Bridge do
alias Circuits.UART
def test_start() do
uart_name = "ttyS0"
uart_speed = 115_200
{:ok, uart_pid} = Circuits.UART.start_link()
UART.open(uart_pid, uart_name,
speed: uart_speed,
active: false
)
uart_pid
end
def test_send(pid, len \\ 100, rep \\ 1, wait \\ false) do
data = :binary.copy("1", len) <> <<0x7E>>
Enum.each(1..rep, fn _ ->
UART.write(pid, data) |> IO.inspect
if wait do
UART.drain(pid)
end
end)
end
end
Issue 1:
iex(1)> p = Bridge.test_start
#PID<0.159.0>
iex(2)> Bridge.test_send p, 17000
Issue 2:
iex(1)> p = Bridge.test_start
#PID<0.159.0>
iex(2)> Bridge.test_send p, 4200
Issue 4:
iex(1)> p = Bridge.test_start
#PID<0.159.0>
iex(2)> Bridge.test_send p, 1500, 3
Issue 5:
iex(1)> p = Bridge.test_start
#PID<0.159.0>
iex(2)> Bridge.test_send p, 1500, 3, true
At present the library links when started with start_link (which is expected). However, consider that we start the library under a supervisor, and then some other genserver calls controlling_process
and open
, etc. If this process exits it seems desirable (to me) to close the serial port?
Now I can trap exits in the genserver process, but that leads to a rabbit hole of how tricky that is to get right, and the main alternative would be for the genserver to link to the circuits_uart process. However, I can't quite decide if that's a good idea or not? It has implications good and bad about what happens if the genserver process crashes during a message transmission over the serial port? However, the counter point is that closing the serial port also has some similar implications (although I guess we can choose to guard these by draining sending buffers on termination, etc?)
I'm genuinely not sure what is the right answer, but I notice that we are modelling this after gen_tcp and the like, and those will monitor (and link) to the process passed in controlling_process
. Should the same logic be used here? (Possibly then with some concerns about whether to drain transmit buffers on close?)
When enumerating serial ports on Linux, the built-in ones should return some information - even if it is just that they're built-in.
The built-in serial ports return empty maps.
Enumerate serial ports on a Raspberry Pi, BBB, or any platform with built-in serial ports.
When a timeout happens upon calling UART.read, it returns {:ok, <<>>}
.
See code:
circuits_uart/lib/circuits_uart.ex
Lines 417 to 418 in 6ffd439
This is not intuitive to me. I expect to get something like {:ok, :timeout}
or {:error, :timeout}
.
I suggest implementing one of three options below to improve this:
{:ok, <<>>}
on a timeout, return {:ok, :timeout}
or {:error, :timeout}
. This is a breaking change.read(pid, timeout \\ 5000, result_on_timeout \\ <<>>)
. I don't think this is considered a breaking change.I'm willing to submit a PR if we can come to a conclusion on what to do.
The warning looks like this:
src/uart_enum_osx.c:49:47: warning: 'kIOMasterPortDefault' is deprecated: first deprecated in macOS 12.0 [-Wdeprecated-declarations]
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices);
^~~~~~~~~~~~~~~~~~~~
kIOMainPortDefault
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/IOKit.framework/Headers/IOKitLib.h:123:19: note: 'kIOMasterPortDefault' has been explicitly marked deprecated here
const mach_port_t kIOMasterPortDefault
^
1 warning generated.
We're running into an issue where on OSX, our USB devices show up in Circuits.UART.enumerate()
with the actual device manufacturers, with serial numbers. We would expect the same behavior in Windows.
In Windows, the manufacturer key is coming up as (we think) the manufacturer of the device driver. For example, for one type of device, in Windows the manufacturer is "Microsoft," whereas the other device appears as "FTDi". When we look at the COM ports in usbview.exe
, we see the correct manufacturer values listed under the iManufacturer
key, with the serial number listed as iSerialNumber
.
It looks as if the values provided by the <setupapi.h>
header may be specific to the device driver, rather than the usb device information. In <usbspec.h> we see definitions for the values provided by USB.
The source code for usbview.exe shows an example for how to retrieve this information from USB.
Since UART is not just USB-specific, we weren't sure if making the change to get this info from the USB bus would be something that should or should not be added to this library. We spent a bit of time trying to get this data using more recent (Vista+) functions available from setupapi, but haven't yet found a way to do so. If you know how to do so and are ok with this change, we're happy to submit a pull request.
Thanks!
We have one device (out of a series of supposedly identical ones) that receives {:error, :einval}
when trying to call Circuits.UART.open/3
. The :einval
error is documented as happening when trying to read
from an active connection, but is not documented on open
. We tried looking where it is being generated in the C code of circuits_uart
and found a few occurrences, but can't find anything wrong.
Confusingly, the same device works fine on Mac and Linux, and all other devices from the same product line work fine even under the exact same Windows setup - it really seems to be just that one device only under Windows.
open/3
should work as expected
We get the following stack trace:
** (RuntimeError) expected to receive :ok from UART open/3, received "{:error, :einval}"
(vernal_falls) lib/vernal_falls/splate/firmware.ex:276: VernalFalls.Splate.Firmware.open/1
(vernal_falls) lib/vernal_falls/splate/firmware.ex:26: VernalFalls.Splate.Firmware.handle_cast/2
(stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :open}
As a way to prevent the process from crashing, we are pattern matching on the return of open/3
to ensure that we have a successful read and open—this explains the specific RuntimeError
in the stacktrace.
We forked circuits_uart
and enabled the debugging tool to see what we would get.
These appear to be the relevant log outputs from three different debug files:
164787334: Starting...
164787334: uart_add_wfmo_handles
164787334: adding read handle (active mode)
164787334: Calling WFMO count=2
164787334: WFMO returned 0
164787336: uart_add_wfmo_handles
164787336: Calling WFMO count=1
164787337: ReadFile on real_stdin failed (port closed)! 109
164787337: WFMO returned 0
We looked up the error 109 for ReadFile
and it seems to us that it means "The pipe ended". We have no idea why this particular device "ends pipes" and all other don't, and on Windows only.
There is a comment in erlcmd.c
that references weirdness about pipes on Windows, so maybe it's related to that?
When we call Circuits.UART.enumerate()
, the device shows up fine. Here are the broken and another, working, one plugged in at the same time:
iex(vernal_falls@LAPTOP)6> Circuits.UART.enumerate()
%{
"COM7" => %{
description: "USB Serial Device",
manufacturer: "Microsoft",
product_id: 22336,
vendor_id: 1155
},
"COM8" => %{
description: "USB Serial Device",
manufacturer: "Microsoft",
product_id: 22336,
vendor_id: 1155
}
}
Joking aside, since it only seems to happen to this specific device on Windows, we're not sure how you'd be able to reproduce it. Please let us know if there is any information we can provide from/about the device that would be useful for debugging this.
Nerves.UART.read
returns {:ok, ""}
upon timeout. When testing some code I noticed it is possible to use Nervers.UART.write
with an empty string (""
) and it is possible to read it: {:ok, ""}
will also be returned. So it is not possible to distinguish between empty binaries being written and timeout.
I do not need to write empty binaries on the serial but noticed this while using propcheck and tty0tty.
Would it make sense to distinguish timeout and empty binaries? (by returning :timeout
for example)
When running this code on a Windows box (both 10 and 8.1):
{:ok, pid} = Nerves.UART.start_link
:ok = Nerves.UART.open(pid, port_name, speed: 9600, active: false)
Nerves.UART.write(pid, "test\n")
:timer.sleep(500)
Nerves.UART.read(pid)
I do not get anything back. The Arduino is set up to echo the data back:
void setup() {
Serial.begin(9600);
}
void loop() {
if(Serial.available() > 0)
{
String data = Serial.readStringUntil('\n');
Serial.print(data);
Serial.print("\n");
}
}
The code is working using the same board on OSX as expected, but no response on Windows (tried on both 10 and 8.1).
Tried on two different Arduino boards: Adafruit Feather M0 (SAMD) and Adafruit Feather 32u4
Versions:
Elixir 1.3
Erlang: 18.2
Nerves_UART: 0.0.7
Also tried using active-mode, but no response back
circuits_uart.open
command has no way to configure a timeout
, the default 4000ms always applies, and the error below might be avoidable if we could configure a longer or flexible timeout say.
** (stop) :port_timed_out
(circuits_uart 1.4.3) lib/circuits_uart.ex:563: Circuits.UART.call_port/4
(circuits_uart 1.4.3) lib/circuits_uart.ex:359: Circuits.UART.handle_call/3
(stdlib 3.16.1) gen_server.erl:721: :gen_server.try_handle_call/4
(stdlib 3.16.1) gen_server.erl:750: :gen_server.handle_msg/6
(stdlib 3.16.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
circuits_uart/lib/circuits_uart.ex
Line 552 in 7181819
Provision to supply a timeout to call_port
is not given from open
.
Was that intentional?
Nerves.UART.enumerate
should return a map of available ports with info on each.
The receive block in Nerves.UART.Enumerator
times out and gets a argument error on the call to Port.close()
(line 21).
Nerves.UART.enumerate
, after 5 seconds it should error outI have a section of code that is trying to handle incoming data till a marker is set and we can start the framing portion. The UART is a USB gadget that is always present, so open always succeeds. Sometimes a Circuits.UART.read(pid, 1000) results in an exit due to an odd issue with timeout or other. The documentation for read/2 is that it returns {:ok, buffer} or {:error, reason}, but does not cover the exit condition. Either this exit should be conditional, or documented.
Here is a link to the problematic line that calls exit.
circuits_uart/lib/circuits_uart.ex
Line 554 in 5ef46d9
I get the spurious EV_RXCHAR
event under windows, after which the Uart interface stops recieving any mode data.
i fixed it by calling start_async_reads(port)
again to continue polling.
seems to work, but I'm not a windows expert ;-)
Windows seems to occasionaly set this flag...
Starting async read
WaitCommEvent returned asynchronously
uart_add_wfmo_handles
adding write handle
adding read handle (active mode)
Calling WFMO count=3
WFMO returned 1
uart_process_handle: write event
Back from write 1, 997
uart_add_wfmo_handles
adding read handle (active mode)
Calling WFMO count=2
WFMO returned 1
uart_process_handle: event event
Got an events event: 1 0 4!!
spurious EV_RXCHAR
uart_add_wfmo_handles
adding read handle (active mode)
Calling WFMO count=2
WFMO returned 0
Going to write 37 bytes
WriteFile asynchronous completion
uart_add_wfmo_handles
adding write handle
adding read handle (active mode)
Calling WFMO count=3
WFMO returned 1
uart_process_handle: write event
Back from write 1, 997
uart_add_wfmo_handles
adding read handle (active mode)
Calling WFMO count=2
We have observed failure in our unit tests when the serial cannot be opened, as if it has not been closed properly during the termination of a previous test process.
At first I thought I would have to explicitly terminate it by trapping exit in another process but the GenServer documentation states:
Therefore it is not guaranteed that terminate/2 is called when a GenServer exits. For such reasons, we usually recommend important clean-up rules to happen in separated processes either by use of monitoring or by links themselves. For example if the GenServer controls a port (e.g. :gen_tcp.socket) or File.io_device/0, they will be closed on receiving a GenServer’s exit signal and do not need to be closed in terminate/2.
So I am assuming this is the case for Nerves also, so there is no need to explicitly close it.
Looking at the implementation of close/1
, it is synchronous (GenServer.call
is used) but internally it uses call_port
which use message passing with the port.
Is it possible that this internal message passing is asynchronous and one of the test in the suite open the serial while the message to close it from the previous invocation has not been processed?
Adding a small delay ( :timer.sleep(100)
) in the ExUnit setup function solves the problem. However I am not big fan of the solution, as solving problems with timers have a tendency to not be reliable and cause later problem when it is not expected.
If my hypothesis is true (asynchronous call is used internally), what approach would you suggest to solve the problem?
Instead of using a cable, some tests could also run using a null modem simulation using socat
. Perhaps even this should run by default, and could run in Travis. I tried this quickly and mosts tests run with this quick setup:
In one shell:
$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2018/03/28 14:12:14 socat[97930] N PTY is /dev/ttys002
2018/03/28 14:12:14 socat[97930] N PTY is /dev/ttys003
2018/03/28 14:12:14 socat[97930] N starting data transfer loop with FDs [5,5] and [7,7]
In another shell:
$ export NERVES_UART_PORT2=ttys003
$ export NERVES_UART_PORT1=ttys002
$ mix test
C:\workspace\elixpos>mix deps.get
Running dependency resolution
* Getting nerves_uart (Hex package)
Checking package (https://repo.hex.pm/tarballs/nerves_uart-0.1.1.tar)
Fetched package
* Getting elixir_make (Hex package)
Checking package (https://repo.hex.pm/tarballs/elixir_make-0.3.0.tar)
Fetched package
C:\workspace\elixpos>mix compile
==> elixir_make
Compiling 1 file (.ex)
Generated elixir_make app
==> nerves_uart
Unchecked dependencies for environment prod:
* elixir_make (Hex package)
the dependency is not locked (run "mix deps.get" to generate "mix.lock" file)
could not compile dependency :nerves_uart, "mix compile" failed. You can recompile this dependency with "mix deps.compile nerves_uart", update it with "mix deps.update nerves_uart" or clean it with "mix deps.clean nerves_uart"
==> elixpos
** (Mix) Can't continue due to errors on dependencies
C:\workspace\elixpos>mix deps.get
Running dependency resolution
All dependencies up to date
C:\workspace\elixpos>mix deps.compile nerves_uart
==> nerves_uart
Unchecked dependencies for environment prod:
* elixir_make (Hex package)
the dependency is not locked (run "mix deps.get" to generate "mix.lock" file)
could not compile dependency :nerves_uart, "mix compile" failed. You can recompile this dependency with "mix deps.compile nerves_uart", update it with "mix deps.update nerves_uart" or clean it with "mix deps.clean nerves_uart"
==> elixpos
** (Mix) Can't continue due to errors on dependencies
C:\workspace\elixpos>dir mix.lock
Volume in drive C is OS
Volume Serial Number is 7EF8-261C
Directory of C:\workspace\elixpos
11/10/2016 18:46 557 mix.lock
1 File(s) 557 bytes
0 Dir(s) 35,903,332,352 bytes free
Key | Value |
---|---|
OS: | Windows 10, sight. |
Elixir: | 1.3.2 |
Erlang: | 19.1 |
Mix env: | dev |
Dependencies in mix.env | {:nerves_uart, "~> 0.1.1"} |
I can compile and build other elixir projects without a problem.
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?
'Development Tools' are installed.
iex -v
Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
IEx 1.14.3 (compiled with Erlang/OTP 25)
CC circuits_uart.o
CC debug_tests.o
CC erlcmd.o
CC uart_comm.o
CC uart_comm_unix.o
CC uart_comm_win.o
CC uart_enum.o
CC uart_enum_linux.o
CC uart_enum_osx.o
CC uart_enum_win.o
CC util.o
LD circuits_uart
/usr/bin/ld: /usr/libexec/gcc/x86_64-redhat-linux/13/liblto_plugin.so: error loading plugin: /usr/libexec/gcc/x86_64-redhat-linux/13/liblto_plugin.so: verkeerde ELF-klasse: ELFCLASS64
collect2: fout: ld gaf exit-status 1 terug
make: *** [src/Makefile:111: /home/antonie/Projects/TrackTimer_V2.0/02-TrackTimer_Main_Module/02-tracktimer_server/elixir/tracktimer_2.0.x/_build/dev/lib/circuits_uart/priv/circuits_uart] Fout 1
{:circuits_uart, "~> 1.3"}
A function to read a specified number of bytes, where the read terminates once the number of bytes has been read or a timeout occurs. The number of bytes to be read may differ between calls.
Is there a way to do this with Circuits.UART? This is a common use case, so I may be missing something.
Thanks!
Talk to a Fona 808 device using Circuits UART on a Raspberry Pi Zero W.
Cannot talk to Fona 808 when using a Raspberry Pi Zero W, the responses from the device are either empty or "Unknown Command" or a bunch of "\b\b\b\b\b\b\b\b\b\b\b" characters.
I have a Fona 808 device which I am able to talk to via UART using a USB to TTL adapter and Circuits UART on my laptop with IEX but cannot do the same when using Circuits UART on a Raspberry Pi Zero W and Nerves.
SAMPLE OUTPUT:
iex(5)> Circuits.UART.read(pid, 1000)
{:ok, ""}
iex(5)> Circuits.UART.read(pid, 1000)
{:ok,
"d\r --> > Unknown command\rUnknown command\r --> n command\rUnknown command\r"}
iex(5)> Circuits.UART.read(pid, 1000)
{:ok,
{:partial,
" \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b \b "}}
I am transmitting a file over a serial line. I read the file like this with an Elixir task:
alias NervesUartEvaluation.Serial
def read(file) do
serial = Serial.setup_serial(:readDeviceName)
read_loop(serial, file)
end
def read_loop(serial, file) do
case Nerves.UART.read(serial, 5) do
{:ok, ""} -> read_loop(serial, file)
{:ok, content} ->
IO.puts(byte_size(content))
IO.binwrite(file, content)
read_loop(serial, file)
fail -> IO.puts fail
end
end
and read the file with another task:
@buff_size 4098
alias NervesUartEvaluation.Serial
alias Nerves.UART
def write(file) do
serial = Serial.setup_serial(:writeDeviceName)
write_loop(serial, file)
end
def write_loop(serial, file) do
case IO.binread(file, @buff_size) do
:eof -> UART.flush(serial); IO.puts "done"
{:error, reason} -> IO.puts("Error" <> reason)
content ->
IO.puts(byte_size(content))
UART.write(serial, content)
UART.drain(serial)
write_loop(serial, file)
end
end
I have created the file with dd
: dd if=/dev/urandom of=./input.bin bs=1024 count=683
.
When setting buff_size
to 4098 there are errors when transmitting the file:
cmp input.bin output.bin
input.bin output.bin differ: byte 4096, line 19
whereas a value of 4000 or 2048, or 1000 works. The same byte is always the incorrectly transmitted.
Is there a bug somewhere in the library or am I doing something wrong?
Please can we connect to 2 ore more Serial Ports at the same time?
Please can you give a small example if this is indeed possible.
Thanks!
When a serial port is visible as COM5
on Windows, it is accessible as /dev/ttyS5
in the WSL. After ensuring access rights are OK, it is possible to open the connection with nerves_uart
:
iex> {:ok, uart} = Nerves.UART.start_link
{:ok, #PID<...>}
iex> Nerves.UART.open(uart, "/dev/ttyS5")
:ok
However, it is not enumerated properly.
Nerves.UART.enumerate/0
should return a list of available serial ports.
Nerves.UART.enumerate/0
always returns %{}
, even if a device is connected.
Setup a project with nerves_uart
in WSL.
Connect a device and get its COM port in the device manager on Windows.
In the WSL shell, do:
$ sudo chmod 666 /dev/ttySx
where x
is the COM port number in COMx
.
Assert the communication works properly.
Try to enumerate the ports with nerves_uart
.
nerves_system_rpi
Application eventually recovers, but the error did trigger our production error logger.
This is what our production error reporter spotted:
Unfortunately, I am not sure. Hopefully, the stack trace helps. Happy to provide more information if needed.
It seems Linux is more picky about attempting RS485 options on a port that may not actually support it. In CI or most host Linux boxes, attempting to run Circuits.UART.open(uart, "/dev/some_device", rs485_rx_during_tx: false)
will result in a :enotty
error.
It seems we may need to be a little more picky about when we actually attempt the RS485 options or not.
(This was discovered attempting to use :jeff
with :tty0tty
in CI)
Hello.
Hello
I created a small application to test Circuits.UART. But I cannot enumerate the ports. I use iex -S mix and Circuits.UART.enumerate.
The result is always %{}
Need help.
Thanks.
circuits_uart 1.4.1
OS is Manjaro Linux, Kernel 5.6.6
I7 Laptop
Communicating with a USB serial cable to a DSP amplifier (Mac Audio Reference 2.1 DSP)
The software I'm writing is here: https://github.com/tomboland/mac_audio_reference_2.1_dsp
This function is the first thing I'm trying to get working: https://github.com/tomboland/mac_audio_reference_2.1_dsp/blob/a10b572811bb51540c745f51f78bba553963fd00/lib/dsp_uart.ex#L56
I'm invoking it in this test here: https://github.com/tomboland/mac_audio_reference_2.1_dsp/blob/a10b572811bb51540c745f51f78bba553963fd00/test/dsp_uart_test.exs#L68
This is my first time using Elixir, and I've found it a pleasure to use, but you may find some stuff unidiomatic! I'm up for trying to submit a PR, I'm not bad with C, but I'll probably find it difficult to dive in and get this sorted. I'm hoping someone may have an instinct on it from the behaviour I'm describing! Thanks for any help, and please let me know if I can give you any more information, or be of any help at all (with guidance)! :)
I think I should be able to set a custom BAUD rate of 192000 or 200000. These speeds both work in an example python script I'm pasting below, which uses pyserial. I've tried with various other more conventional speeds, like 115200, 230400 and others, and I fail to communicate with my DSP using those speeds in python, but 192000 and 200000 work OK. The original software uses 192000. I think the problem is with the ioctl being used, but I must admit, it's not something I'm confident to make assertions about. The expected behaviour can be seen in the strace output from the python script that I'll put below. I'm also pasting strace output from my elixir program.
I get an ENOTTY error and cannot use the serial port at all when I configure a speed of 192000 or 200000.
I don't think you'd need my hardware to reproduce this, and actually, if I use tty0tty and try and connect to /dev/tnt0, I also get the ENOTTY error. Please see the strace output below, which is talking to the real hardware. You can see that the python script (where I've basically copypasta'd bits of the original code I'm rewriting) is getting the response I expect from the serialised message. Basically, if I send this binary: <<85, 2, 3, 7, 10>>, I should get back <<0xff>>
from serial import Serial, SerialException
import ctypes
import array
def send_message(ser, message):
if len(message) == 0:
return
ln = len(message) // 2 + 1
buf = array.array('B', [85, ln, 3])
sum = 3
for i in range(len(message) // 2):
m = int(message[i * 2:i * 2 + 2], 16)
sum = sum + m
buf.append(m)
buf.append(ctypes.c_ubyte(sum).value)
print("DEBUG:", buf, buf.tostring())
ser.write(buf.tostring())
if __name__ == "__main__":
ser = Serial()
ser.baudrate = 192000
ser.timeout = 2
ser.port = '/dev/ttyUSB0'
try: ser.open()
except SerialException: pass
send_message(ser, b'07')
response = ser.read()
print(response)
send_message(ser, b'08')
response = ser.read()
ioctl(3, TCGETS, 0x7fffedf6bff0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6bff0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b0a0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a7e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a7e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c010) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c010) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c020) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c020) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c020) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b0d0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(0, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xbf, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(0, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xbf, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(1, TCGETS, 0x7fffedf6cf00) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(1, TCGETS, 0x7fffedf6d1c0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(2, TCGETS, 0x7fffedf6cf00) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(2, TCGETS, 0x7fffedf6d1c0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c100) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b1b0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a260) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a260) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a260) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf69310) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6bca0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6bbe0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(4, TCGETS, 0x7fffedf6a810) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6cea0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6d8d0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c450) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a5b0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a5b0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6a5b0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c450) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6c450) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fffedf6b500) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xcbd, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xcbd, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3, SNDCTL_TMR_START or TCSETS, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=0, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x00\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3, TCGETS2, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=0, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x00\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3, TCSETS2, {c_iflags=0, c_oflags=0, c_cflags=0x1cb0, c_lflags=0, c_line=0, c_cc[VMIN]=0, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x00\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3, TIOCMBIS, [TIOCM_DTR]) = 0
ioctl(3, TIOCMBIS, [TIOCM_RTS]) = 0
ioctl(3, TCFLSH, TCIFLUSH) = 0
ioctl(8, TCGETS, 0x7fffedf6b880) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf6a930) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf699e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf699e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf6a930) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf699e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf67b40) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf66bf0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf68a90) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf699e0) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(8, TCGETS, 0x7fffedf6bc70) = -1 ENOTTY (Inappropriate ioctl for device)
pyserialtest.py:29: DeprecationWarning: tostring() is deprecated. Use tobytes() instead.
send_message(ser, b'07')
pyserialtest.py:32: DeprecationWarning: tostring() is deprecated. Use tobytes() instead.
send_message(ser, b'08')
DEBUG: array('B', [85, 2, 3, 7, 10]) b'U\x02\x03\x07\n'
b'\xff'
DEBUG: array('B', [85, 2, 3, 8, 11]) b'U\x02\x03\x08\x0b'
b'\x11'
+++ exited with 0 +++
mix test
ioctl(-1, TIOCGPGRP, 0x7ffd2e7a4d04) = -1 EBADF (Bad file descriptor)
ioctl(2, TIOCGPGRP, 0x7ffd2e7a4bc4) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(2, TIOCGPGRP, 0x7ffd2e7a4ba4) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7ffd2e7a4c90) = -1 ENOTTY (Inappropriate ioctl for device)
strace: Process 833241 attached
strace: Process 833242 attached
[pid 833242] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833242, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833243 attached
[pid 833243] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833243, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833244 attached
[pid 833244] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833244, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833245 attached
[pid 833245] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833245, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833246 attached
[pid 833246] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833246, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833247 attached
[pid 833247] +++ exited with 0 +++
[pid 833241] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833247, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[pid 833241] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833241, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833248 attached
[pid 833248] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833248, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
ioctl(1, TCGETS, 0x7ffd2e7a4710) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(2, TCGETS, 0x7ffd2e7a4700) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(-1, TIOCGPGRP, 0x7fff3ffeeb44) = -1 EBADF (Bad file descriptor)
ioctl(2, TIOCGPGRP, 0x7fff3ffeea04) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(2, TIOCGPGRP, 0x7fff3ffee9e4) = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(3, TCGETS, 0x7fff3ffeead0) = -1 ENOTTY (Inappropriate ioctl for device)
strace: Process 833249 attached
strace: Process 833250 attached
strace: Process 833251 attached
[pid 833250] ioctl(2, TIOCGPGRP, 0x7fff3ffee034) = -1 ENOTTY (Inappropriate ioctl for device)
[pid 833250] +++ exited with 0 +++
[pid 833251] +++ exited with 0 +++
[pid 833249] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833250, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[pid 833249] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833249, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 833252 attached
[pid 833240] ioctl(0, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xbf, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
[pid 833240] ioctl(0, TCGETS, {c_iflags=0x500, c_oflags=0x5, c_cflags=0xbf, c_lflags=0x8a3b, c_line=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
strace: Process 833253 attached
strace: Process 833254 attached
strace: Process 833255 attached
strace: Process 833256 attached
strace: Process 833257 attached
strace: Process 833258 attached
strace: Process 833259 attached
strace: Process 833260 attached
strace: Process 833261 attached
strace: Process 833262 attached
strace: Process 833263 attached
strace: Process 833264 attached
strace: Process 833265 attached
strace: Process 833266 attached
strace: Process 833267 attached
strace: Process 833268 attached
strace: Process 833269 attached
strace: Process 833270 attached
strace: Process 833271 attached
strace: Process 833272 attached
strace: Process 833273 attached
strace: Process 833274 attached
strace: Process 833275 attached
strace: Process 833276 attached
strace: Process 833277 attached
strace: Process 833278 attached
strace: Process 833279 attached
strace: Process 833280 attached
strace: Process 833281 attached
strace: Process 833282 attached
strace: Process 833283 attached
strace: Process 833284 attached
[pid 833284] ioctl(0, TCGETS, 0x7ffee4b25320) = -1 ENOTTY (Inappropriate ioctl for device)
[pid 833284] ioctl(-1, TIOCGPGRP, 0x7ffee4b25524) = -1 EBADF (Bad file descriptor)
[pid 833284] ioctl(2, TIOCGPGRP, 0x7ffee4b253e4) = -1 ENOTTY (Inappropriate ioctl for device)
[pid 833284] ioctl(2, TIOCGPGRP, 0x7ffee4b253c4) = -1 ENOTTY (Inappropriate ioctl for device)
strace: Process 833285 attached
[pid 833285] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=833284, si_uid=1000} ---
[pid 833285] +++ killed by SIGUSR1 +++
[pid 833284] +++ exited with 0 +++
[pid 833255] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833284, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[pid 833263] ioctl(1, TCGETS, 0x7f07fb6b5ad0) = -1 ENOTTY (Inappropriate ioctl for device)
Compiling 1 file (.ex)
[pid 833256] ioctl(1, TIOCGWINSZ, 0x7f080027eb60) = -1 ENOTTY (Inappropriate ioctl for device)
[pid 833256] ioctl(0, TIOCGWINSZ, {ws_row=27, ws_col=106, ws_xpixel=0, ws_ypixel=0}) = 0
DspEqTest
* test eq coeffs runs (14.3ms)
* doctest DspEq.sample_rate/0 (1) (0.00ms)
DspUartTest
* test Serial connection can be madestrace: Process 833286 attached
[pid 833286] ioctl(3, TCGETS, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
[pid 833286] ioctl(3, SNDCTL_TMR_START or TCSETS, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
[pid 833286] ioctl(3, TCGETS, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
[pid 833286] ioctl(3, SNDCTL_TMR_START or TCSETS, {c_iflags=0, c_oflags=0, c_cflags=0xcbf, c_lflags=0, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=0, c_cc="\x03\x1c\x7f\x15\x04\x00\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
[pid 833286] ioctl(3, TIOCGSERIAL, 0x7ffeda0bbaa0) = -1 ENOTTY (Inappropriate ioctl for device)
* test Serial connection can be made (15.3ms)
[pid 833286] +++ exited with 0 +++
[pid 833255] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=833286, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
1) test Serial connection can be made (DspUartTest)
test/dsp_uart_test.exs:68
** (MatchError) no match of right hand side value: {:error, :enotty}
code: pid = DspUart.get_serial_connection("/dev/ttyUSB0")
stacktrace:
(mac_audio_dsp 0.1.0) lib/dsp_uart.ex:52: DspUart.get_serial_connection/1
test/dsp_uart_test.exs:69: (test)
* test Test serialised messages with byte length 8 (1.5ms)
* test Test message checksum examples (0.00ms)
* test Test serialised messages with byte length 2 (1.0ms)
* test Test serialised messages with byte length 1 (0.9ms)
* test Test serialised messages with byte length 4 (0.6ms)
Finished in 0.2 seconds
1 doctest, 7 tests, 1 failure
Randomized with seed 940922
[pid 833254] +++ exited with 0 +++
[pid 833283] +++ exited with 1 +++
[pid 833281] +++ exited with 1 +++
[pid 833282] +++ exited with 1 +++
[pid 833280] +++ exited with 1 +++
[pid 833278] +++ exited with 1 +++
[pid 833277] +++ exited with 1 +++
[pid 833276] +++ exited with 1 +++
[pid 833275] +++ exited with 1 +++
[pid 833274] +++ exited with 1 +++
[pid 833273] +++ exited with 1 +++
[pid 833272] +++ exited with 1 +++
[pid 833271] +++ exited with 1 +++
[pid 833270] +++ exited with 1 +++
[pid 833269] +++ exited with 1 +++
[pid 833268] +++ exited with 1 +++
[pid 833267] +++ exited with 1 +++
[pid 833266] +++ exited with 1 +++
[pid 833265] +++ exited with 1 +++
[pid 833257] +++ exited with 1 +++
[pid 833264] +++ exited with 1 +++
[pid 833263] +++ exited with 1 +++
[pid 833262] +++ exited with 1 +++
[pid 833261] +++ exited with 1 +++
[pid 833260] +++ exited with 1 +++
[pid 833259] +++ exited with 1 +++
[pid 833258] +++ exited with 1 +++
[pid 833256] +++ exited with 1 +++
[pid 833253] +++ exited with 1 +++
[pid 833252] +++ exited with 1 +++
[pid 833279] +++ exited with 1 +++
[pid 833240] +++ exited with 1 +++
+++ exited with 0 +++
When running the library as an escript, an error is raised:
** (EXIT from #PID<0.77.0>) an exception was raised:
** (ArgumentError) argument error
:erlang.++({:error, :bad_name}, '/nerves_uart')
(nerves_uart) lib/nerves_uart.ex:284: Nerves.UART.init/1
(stdlib) gen_server.erl:365: :gen_server.init_it/2
(stdlib) gen_server.erl:333: :gen_server.init_it/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
I am sending 1,1MB of data with this code:
@buff_size 4000
alias NervesUartEvaluation.Serial
alias Nerves.UART
def write(file) do
serial = Serial.setup_serial(:writeDeviceName)
write_loop(serial, file)
end
def write_loop(serial, file) do
case IO.binread(file, @buff_size) do
:eof -> UART.flush(serial); IO.puts "done"
{:error, reason} -> IO.puts("Error" <> reason)
content ->
IO.puts(byte_size(content))
UART.write(serial, content)
UART.drain(serial)
write_loop(serial, file)
end
end
and reading with cat
. I observe a transmission rate of about 77 KiB/s (77 kilobyte/sec).
When doing the same experiment with PySerial, the transmission rate is of 88.7 KiB/s.
What can explain the performance drop? I would expect same speed as the test is IO bound.
It should be possible to both send and receive messages through the serial port after the connection is opened.
Calling Circuits.UART.write
has no effect, despite it not rendering an error and Circuits.UART.read
always results in a timeout
I'm using Circuits UART on a personal project.
The ESP32 board is recognized properly by the enumerate
function, which I use in order to decide which port to use. After opening the serial connection (which BTW returns :ok
as expected), I try to call either write
or read
and have no success whatsoever while trying to communicate with and ESP32 Dev Kit running at 921600 bps baud rate.
The serial communication works properly if I run my project under Pop_OS! instead of Windows 10. Prior to using and ESP32, I was using an Arduino Uno board; which worked flawlessly on both Linux and Windows.
I've already tried to recompile the depencies by running mix clean
, mix compile
and mix deps.compile
but the result remains the same.
Any idea on what I could try next?
With all the new elixir-circuits
work going on for GPIO, I2c, and SPI, would it make sense to move nerves_uart
to that umbrella instead as circuits_uart
?
It seems to be a common misconception that one would need nerves to use this and moving might help separate the distinction from nerves.
But on the flip side, it might not be technically a “circuit” since UART is not dependent on pin headers and is focused on being cross platform library.
Hi,
it would be great to put in the FAQ how the program is interfaced with C (port / NIFS / C node) and the security guarantees with regards to the supervisor if the C code crashes. If I understand correctly ports are used.
circuits_uart/lib/circuits_uart.ex
Line 511 in 4832c87
This line will cause an exception if UART.open/3
was not called, or if it was called, but returned an error
Hi there,
I'm trying to cross-compile nerves_uart
on my mac for my raspberry pi (original). When make
is ran on the nerves_uart
dependency, I get the following error:
/Users/john/.nerves/toolchains/nerves-armv6-rpi-linux-gnueabi-darwin-x86_64-v0.6.0/bin/armv6-rpi-linux-gnueabi-gcc src/debug_tests.o src/erlcmd.o src/nerves_uart.o src/uart_comm.o src/uart_comm_unix.o src/uart_comm_win.o src/uart_enum.o src/uart_enum_linux.o src/uart_enum_osx.o src/uart_enum_win.o src/util.o -L/Users/john/.nerves/systems/nerves/rpi-0.4.0/staging/usr/lib/erlang/erts-7.2.1/lib -L/Users/john/.nerves/systems/nerves/rpi-0.4.0/staging/usr/lib/erlang/lib/erl_interface-3.8.1/lib -lerts -lerl_interface -lei -framework CoreFoundation -framework IOKit -o priv/nerves_uart
armv6-rpi-linux-gnueabi-gcc: error: CoreFoundation: No such file or directory
armv6-rpi-linux-gnueabi-gcc: error: IOKit: No such file or directory
armv6-rpi-linux-gnueabi-gcc: error: unrecognized command line option '-framework'
armv6-rpi-linux-gnueabi-gcc: error: unrecognized command line option '-framework'
make: *** [priv/nerves_uart] Error 1
could not compile dependency :nerves_uart, "mix compile" failed. You can recompile this dependency with "mix deps.compile nerves_uart", update it with "mix deps.update nerves_uart" or clean it with "mix deps.clean nerves_uart"
I am looking forward to compiling circuits UART targeting a custom device. We have built a system based on BeagleBone (https://github.com/nerves-project/nerves_system_bbb) but compilation fails.
I am facing an issue compiling circuits_uart:
src/uart_comm_unix.c: In function 'uart_get_rs485_config':
src/uart_comm_unix.c:383:50: error: 'SER_RS485_TERMINATE_BUS' undeclared (first use in this function)
383 | config->rs485_terminate_bus = (rs485.flags & SER_RS485_TERMINATE_BUS) != 0;
| ^~~~~~~~~~~~~~~~~~~~~~~
src/uart_comm_unix.c:383:50: note: each undeclared identifier is reported only once for each function it appears in
src/uart_comm_unix.c: In function 'uart_config_rs485':
src/uart_comm_unix.c:434:32: error: 'SER_RS485_TERMINATE_BUS' undeclared (first use in this function)
434 | update_flags(&rs485.flags, SER_RS485_TERMINATE_BUS, config->rs485_terminate_bus);
| ^~~~~~~~~~~~~~~~~~~~~~~
make: *** [src/Makefile:107: /<***>/lib/circuits_uart/obj/uart_comm_unix.o] Error 1
Compile circuits_uart with a BeagleBone-based system as a target should trigger the above error.
After some investigation, I saw that there were kernel headers missing in our target system, the whole uapi-related headers that include the required SER_RS485_TERMINATE_BUS
constant.
If we set the option for framing with \n
, framing: {Nerves.UART.Framing.Line, separator: "\n"}
, but send no new line we get this error:
** (exit) exited in: GenServer.call(#PID<0.166.0>, {:read, 3000}, 3100)
** (EXIT) time out
(elixir) lib/gen_server.ex:834: GenServer.call/3
(hello_uart) lib/hello_uart.ex:24: HelloUart.uart_loop/1
The example can be found in: https://github.com/DisruptiveAngels/uart-example/blob/d990251ce6fd4992f91c3fee490253e0f1a8d06d/lib/hello_uart.ex
The code works with parity set to :ignore
but according to the documentation and code comments I should be able to set parity to :mark
.
{:ok, pid} = Circuits.UART.start_link()
:ok = Circuits.UART.open(pid, "/dev/tty.SLAB_USBtoUART", speed: 9600, parity: :mark, data_bits: 8, stop_bits: 1, active: true)
open returns {:error, :einval}
.
Above two lines of code.
Successful compilation
Compilation fails at link time:
c:/programdata/chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: c:/dev/bas/bas_server/_build/dev/lib/circuits_uart/obj/util.o:util.c:(.text+0x40): undefined reference to `clock_gettime'
collect2.exe: error: ld returned 1 exit status
mingw32-make: *** [src/Makefile:111: c:/dev/bas/bas_server/_build/dev/lib/circuits_uart/priv/circuits_uart.exe] Error 1
could not compile dependency :circuits_uart, "mix compile" failed. You can recompile this dependency with "mix deps.compile circuits_uart", update it with "mix deps.update circuits_uart" or clean it with "mix deps.clean circuits_uart"
** (Mix) Could not compile with "mingw32-make" (exit status: 2).
You may need to install mingw-w64 and make sure that it is in your PATH. Test this by
running `gcc --version` on the command line.
Build on Windows
When reading a port in a read loop with nerves uart and testing the program from iex, nerves uart crashes when typing twice <tab>
:
λ iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [kernel-poll:false]
Interactive Elixir (1.6.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> NervesUartBug.bug()
%Task{
owner: #PID<0.152.0>,
pid: #PID<0.155.0>,
ref: #Reference<0.3372432089.4237557761.212204>
}
### HERE type <tab> <tab> quickly
iex(2)>
15:37:03.575 [error] GenServer #PID<0.154.0> terminating
** (stop) :port_timed_out
(nerves_uart) lib/nerves_uart.ex:501: Nerves.UART.call_port/4
(nerves_uart) lib/nerves_uart.ex:356: Nerves.UART.handle_call/3
(stdlib) gen_server.erl:636: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:665: :gen_server.handle_msg/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.155.0>): {:read, 50}
State: %Nerves.UART.State{controlling_process: #PID<0.152.0>, framing: Nerves.UART.Framing.None, framing_state: nil, is_active: false, name: "tnt0", port: #Port<0.4984>, queued_messages: [], rx_framing_timeout: 0, rx_framing_tref: nil}
Client #PID<0.155.0> is alive
(stdlib) gen.erl:169: :gen.do_call/4
(elixir) lib/gen_server.ex:828: GenServer.call/3
(nerves_uart_bug) lib/nerves_uart_bug.ex:22: NervesUartBug.read_serial_line/1
(elixir) lib/task/supervised.ex:88: Task.Supervised.do_apply/2
(elixir) lib/task/supervised.ex:38: Task.Supervised.reply/5
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
I have a demo code showing the problem: https://github.com/pallix/nerves_uart_bug
In my project, data is sent over serial in the following format:
node-id;child-sensor-id;message-type;ack;sub-type;payload\n
In it's current implementation, Nerves.UART makes this a little hard to work with, so I was looking for some method of reading things line by line.
The implementation I've worked out (in active mode, anyways) was to pass a string buffer around in state (initially empty), and on each serial callback I check if the new data contains a \n
, and if it does, I take the existing buffer, append the new data, split it in 2 parts at the \n
character, stick the remainder back in the buffer, and GenServer.call my own {:readline} event.
It would make it easier for people to adopt the library if we could set a delimiter to split packets in the buffer by, and even if we could define a fixed packet size, and buffer until we get that many bytes, and then send a message when data is ready.
{:circuits_uart, "~> 1.4"}
I install circuits_uart on my Nerves project and after burning on it. I connect to my nerves and run this function: Circuits.UART.enumerate
iex(5)> Circuits.UART.enumerate
%{"ttyAMA0" => %{}, "ttyS0" => %{}}
iex(6)> Circuits.UART.enumerate
%{"ttyAMA0" => %{}, "ttyS0" => %{}}
iex(7)> {:ok, pid} = Circuits.UART.start_link
{:ok, #PID<0.1130.0>}
iex(8)> Circuits.UART.enumerate
%{"ttyAMA0" => %{}, "ttyS0" => %{}}
but it can not find anything, although I connected 3 different USB to my Raspberry Pi 4:
iex(52)> Circuits.UART.open(uart, "ttyS0", speed: 9600,active: true)
:ok
iex(53)> Circuits.UART.write(uart, "Hello there\r\n")
:ok
iex(54)> Circuits.UART.read(uart, 60000)
{:error, :einval}
Thank you
I am running into a Dialyzer error when using Nerves.UART.read/2
from v0.1.2, as the @spec
is not correct.
Searching through the repo, I see it has been fixed more than six months ago in #20, but it is not yet released. Is there a plan to do a bugfix release anytime soon? It is sad to get a cascade of errors in Atom ide-elixir for this.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.