Code Monkey home page Code Monkey logo

serialapp's Introduction

Using embedded GPIO UART serial port with .NET Core 2.0 on a Raspberry PI 3 running Linux

There is now an official support for System.IO.Ports in the main .NET Core repository. The nudget is still in preview, so you have to add preview for the System.IO.Ports nuget and the install it. This will allow you to enjoy a much better package than this one. If you are still using old version of .NET Core, then this one may help you. The new package is working for version higher than 2.2 including 3.

Note: A Nuget Package has been release to include support of Serial Port in Linux and MacOS. You can find it searching for NetCoreSerial. You still can use the source code in your own code.

I have an arduino connected to a Rapsberry PI 3 running Linux thru the embedded UART. It's the one you access thru the GPIO on physical pins 8 and 10 and logical GPIO14 and GPIO15. I used to access this Arduino for analogic data. I rencently wanted to test .NET Copre 2.0 and see how it can work on a RPI running a Linux ARM architecture. Support for Linux ARM with .NET Core 2.0 is still in preview and quite new.

I realized as well that there is so far, no native support for System.IO.Ports in .NET Core 2.0 for Linux ARM configs. The Windows ARM version is available, still all in preview. So let see what are the steps needed to make anyway all working. I've split the article in couple of steps which are necessary to have the full project working.

Step 1: get the Raspberry PI 3 ready for using the embedded serial port

This is a part which I had to spend quite a lot of time because there were some changes between RPI 1/2 and RPI 3 in the embedded serial port. And in the early firmware of the RPI 3 I had, was buggy and it did not allow to work correctly. I finally found answers on GitHub in the distribution issue list here. In short, here are the steps you need to follow to activate the serial port.

Update the firmware

Open a command line and type:

sudo rpi-update

You'll have to wait for all to be installed, it takes couple of minutes. Then reboot. Once rebooted, you will have to activate in the boot option the serial port.

Enabling the UART

Easy way

Just run sudo raspi-config then Interfacing Options then Serial then select Noto console login then select Yesto embedded serial port. Exit the program and then reboot with sudo shutdown -r now.

Longer way Not recommended and that won't disable the console port

For this, you'll have to edit /boot/cmdline.txt and /boot/config.txt Open a console, then cd /boot I usually then use leafpad to edit any config file. I prefer the graphical interface. For this, you'll need admin right to overright config files, so just type sudo leafpad. Open the /boot/cmdline.txt file and remove console=ttyS0,115200 or equivalent. Keep the console=tty1. Save the file.

Then, you'll need to activate the UART per say in the /boot/config.txt file. Open it with the same leafpad administrator session, and add at the end of the file enable_uart=1. Save the file. Time to reboot. This will now enable the UART and allow you to access it.

Important note 1: this procedure is valid only for Raspberry PI 3. It is different on Raspberry PI 1/2. And it is well documented on the web.

Important note 2: the embedded UART name is ttyS0 in RPI3 while it was ttyAMA0 on RPI1/2. ttyAMA0 is used for Bluetooth on RPI3.

Checking all is correct

You can run the following command:

ls –l /dev | grep serial

Serial list

If all is activated, you'll get the 2 serials showing like in the picture above. If it's not what you have then something went wrong. Try again, and check that you've remove the right things from the cmdline.txt file and added the line in the config.txt file.

Important note 1: On a RPI1/2, you'll only get 1 serial port mapped on ttyAMA0

Important note 2: if you plugged other serial devices on USB port, they'll appear as well in the list

Step 2: installing .NET Core 2.0 on the Rapsberry

This step is not needed anymore if you are packaging the application as explained in Step 4. You still may need to install the dependencies.

Important note: Officially the .NET Core 2.0 is not released while I am writing those lines. And Linux ARM support is still in beta.

In fact, there is nothing special to instal, just dependencies which will be used by the dotnet engine to run the netcore 2.0 application.

Installing the missing dependencies

I used as a source this file from GitHub to setup all the .NET Core on the RPI3 running Linux. Please refere to this file as I'm sure it will be updated. In short, you'll have first to add all the needed dependencies. Run the following command from a console:

sudo apt-get install libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev unzip

Now you should be all good to and all dependencies should be installed for you to run a netcode 2.0 appplication.

Step 3: installing the .NET Core 2 SDK on a supported platform

My choice goes for Windows. But you can choose any platform. Instruction for Windows are here. Make sure you install the .NE TCore 2.0. You can as well install daily builds and you can find them here. You'll find from the same URL the other plateformes like Linux and Mac OS. I've tested MacOS and is does work perfectly as well. Be carreful and follow all instructions as you may have dependency issues.

Step 4: creating a simple console app

Create a simple hello world console app

From a command line (Powershell on Windows for example), go to any directory and create a new project with the following command line:

dotnet new console –o serialapp

Serial list

It will create for you a simple console project. This will allow to test if the all setup is working. Now, let's package this simple hello world app for the Raspberry. Compile and create the package Go to the created directory and type dotnet restore then dotnet publish -r linux-arm

Serial list

This will create a directory with all the necessary files to be able to run on the Raspberry.

Serial list

Deploy the file to the RPI3

Select all the directory and copy it to the RPI. You can use WinCSP, create a share on the RPI, use a stick, anything to copy all the directory to the RPI. In my case, I'm using SMB share.

Run the app

Now open a console on the RPI, go to the directory you just copy. and type ./serialapp If all goes righ, you should have a "Hello world!"

Serial list

Step 5: Serial port support in netcore 2.0

This is where things starts to be quite difficult. So far, there is not support for System.IO.Ports in .NET Core for the Linux ARM platforms. It's only available in preview for the Windows ARM platforms. You can check the long thread discussion on this topic on GitHub here.

If you want to use the Windows plateform, even for ARM, you can Nuget. But it will not work for Linux ARM. So what are the solutions?

Solution 1: the simple wrapper one

This technic is quite simple, I've already used it in the Rapsberry PI .NET Microframework class compatibility. Idea is to use the DLLImport feature of .NET. It's already working for .Net Core of course. I found great inspiration and code on this repo. I've simplified the code and made is very straight forward, code available on my GitHub here. Import all the functions you need from the native libc. For example the open function.

[DllImport("libc")]
public static extern int open(string pathname, OpenFlags flags);

The way then you can call the imported functions is like any other one:

// open serial port
int fd = Libc.open(portName, Libc.OpenFlags.O_RDWR | Libc.OpenFlags.O_NONBLOCK);

Note that the GetPortNames functions is checking the /dev/tty* existance for serial ports. On Windows serial ports are all COMx where x is a number, on Linux, they are /dev/tty* where the * can be very different. As in section 1, we know that the embedded serial port for the RPI3 is /dev/ttyS0. So we should find it in the list.

Open a port from the main code is as well quite simple:

SerialDevice mySer = new SerialDevice("/dev/ttyS0", BaudRate.B1152000);

To make this example working, just clone the repo and just use same as previous, dotnet restore then dotnet publish -r linux-arm and then copy to the final destination. The code is straight forward, it does raise an event every time a caracter is available. There is no advance ReadLine or equivalent. You can build it on top if you need.

using System;
namespace serialapp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Serial port!");
            var ports = SerialDevice.GetPortNames();
            bool isTTY = false;
            foreach (var prt in ports)
            {
                Console.WriteLine($"Serial name: {prt}");
                if (prt.Contains("ttyS0"))
                {
                    isTTY = true;
                }
            }
            if (!isTTY)
            {
                Console.WriteLine("No ttyS0 serial port!");
                return;
            }
            Console.WriteLine("Yes, we have the embedded serial port available, opening it");
            SerialDevice mySer = new SerialDevice("/dev/ttyS0", BaudRate.B115200);
            mySer.DataReceived += MySer_DataReceived;
            mySer.Open();
            while (!Console.KeyAvailable)
                ;
            mySer.Close();
        }
        private static void MySer_DataReceived(object arg1, byte[] arg2)
        {
            Console.WriteLine($"Received: {System.Text.Encoding.UTF8.GetString(arg2)}");
        }
    }
}

Running the sample will give you this:

Serial list

and then, you'll get the serial inputs everytime caracters are coming. Note that it's a very basic sample, it will work correctly with a text serial communicaiton as it is using System.Text.Encoding.UTF8.GetString to convert the byte array into a string.

Solution 2: the advance solution with more compatibility with original System.IO.Ports

I wanted to see how advanced I can go with closer implementation to System.IO.Ports. I found a library that is quite widely used called SerialPortStream and does support Mono as well as .NET Standard. This library is available as a nuget that you can add to your solution. You can use the same created solution as before with the console or create a new one. In this case, I've used the Visua Studio 2017 15.3 Preview 3. I've create a .NET Core 2.0 console app, searched for the nuget and add it:

Serial list

Name space is using RJCP.IO.Ports; And the main serial port object is SerialPortStream. It has almost the same properties as in system.IO.Ports. And it's the reason why I've decided to use this one as a temporary solution before an official support for the Linux ARM architecture. Main difference I found is in the SerialPortStream creation, parity and databits are inverted compare to the traditional SerialPort. For the rest, usage is very simple like for the System.IO.Ports.

using System;
using System.Collections.Generic;
using RJCP.IO.Ports;
namespace serialapp
{
    class Program
    {
    static void Main(string[] args)
    {
        SerialPortStream myPort = null;
        Console.WriteLine("Hello Serial!");
        Console.WriteLine(Environment.Version.ToString());
        string[] ports = GetPortNames();
        foreach (var port in ports)
            if (port == "/dev/ttyS0")
            {
                myPort = new SerialPortStream("/dev/ttyS0", 115200, 8, Parity.None, StopBits.One);
                myPort.Open();
                if (!myPort.IsOpen)
                {
                    Console.WriteLine("Error opening serial port");
                    return;
                }
                Console.WriteLine("Port open");
            }
        if (myPort == null)
        {
            Console.WriteLine("No serial port /dev/ttyS0");
            return;
        }
        myPort.Handshake = Handshake.None;
        myPort.ReadTimeout = 10000;
        myPort.NewLine = "\r\n";

        while (!Console.KeyAvailable)
        {
            try
            {
                string readed = myPort.ReadLine();
                Console.Write(readed);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }

        }
    }
    public static string[] GetPortNames()
    {
        int p = (int)Environment.OSVersion.Platform;
        List serial_ports = new List();

        // Are we on Unix?
        if (p == 4 || p == 128 || p == 6)
        {
            string[] ttys = System.IO.Directory.GetFiles("/dev/", "tty\*");
            foreach (string dev in ttys)
            {
                if (dev.StartsWith("/dev/ttyS") || dev.StartsWith("/dev/ttyUSB") || dev.StartsWith("/dev/ttyACM") || dev.StartsWith("/dev/ttyAMA"))
                {
                    serial_ports.Add(dev);
                    Console.WriteLine("Serial list: {0}", dev);
                }
            }
        }
        return serial_ports.ToArray();
    }
}
}

Same as for the privious code, I'm just opening the port. And I'm using the ReadLine function to read up to a carriage return and new line. The GetPortNames function is the same as in the previous example. Now, you can compile the app with the usal "dotnet restore" then "dotnet publish -r linux-arm" and then deploy it to the device. And then run it. You'll get the following error:

Serial list

It's normal that you get this error. The nuget package do not carry the necessary Linux serial library. You'll basically will need to compile them. It is documented on the GitHub page. In short:

git clone https://github.com/jcurl/serialportstream.git
cd serialportstream/
cd dll/serialunix/
./build.sh

If you get an error, you main need to install couple of dependencies like cmake. In this case use sudo apt-get install cmake or anything similar to add wha tis missing. Then copy the 3 generated libnserial.so libraries (the .1 and 1.1 as well) which are located in serialportstream/dll/serialunix/bin/usr/local/include/lib to the same directory as the main code from your .NET Core 2 application. Run it and this time it will perfectly work.

Serial list

If nothing is plugged to the serial or nothing is sent, then you'll get as well error messages like here the timeout one.

Conclusion

.NET Core est really a great set of technology and very interesting as it gets more and more support. As always when it comes to using native capabilities of some plateformes, there is the need to write specific code to support each plateformes. Serial ports, USB portas and in general anything related to hardware is specific. But there are always simple ways to integrate things, those are usually simple and efficient for specific projects. Once you want more features and become more compatible, having more performances, you'll need more work.

I'm quite impatient to see how the support for System.IO.Ports will be done in .NET Core 2.0. In the mean time, both solutions are working for me and I'll keep them for some time.

serialapp's People

Contributors

ellerbach avatar enghch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

serialapp's Issues

SerialDevice.GetPortNames Not returning Ports

I'm trying to use in Mac Os Mojave 10.14.5 and it not working.

The function GetPortNames always return null, and it appear not working correctly.

var ports = SerialDevice.GetPortNames();

Remote peer could not receive the data send from RaspberryPi(based on Debian stretch) via SerialPortStream

Hi, thanks for the great work.
I'm using a USB to TTL with CP2102 chip.
this is via lsusb:
Bus 001 Device 009: ID 10c4:ea60 Cygnal Integrated Products, Inc. CP210x UART Bridge / myAVR mySmartUSB light

I've build your lib in my Pi, and then added the output file into my .NET Core 2.1 application, the application finally can run, and it even can listed the available serial port via SerialPortStream.GetPortNames(), what I get is /dev/ttyUSB0.
I want do sth more:

public void Test()
{
    SerialPortStream comPort = new SerialPortStream("/dev/ttyUSB0", 9600, 8, Parity.Odd, StopBits.One)
            {
                ReadTimeout = 5000,
                WriteTimeout = 5000
            };
    comPort.DataReceived += Port_DataReceived;
    comPort.Open();
    //pesudo code, send 0x5020FA every 1 second.
    timer.Fire(()=>{
        comPort.Write(new byte[]{0x50, 0x20,0xFA});
        logger.Debug("Msg send");
    }, 1000);
}
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    var buffer = new List<byte>();
    while (this.comPort.BytesToRead > 0)
    {
        var readByte = this.comPort.ReadByte();
        logger.Debug("RAW read: 0x" + readByte.ToString("X").PadLeft(2, '0'));                    
    }
}

by running this app, I can see the UsbToTtl's led for TX is blinking as expected.
for the remote peer side(a PC), I've opened a RS232 port, and connected PC Rx UsbToTtl's Tx, I noticed either I always received the E5, B7, and if I connected the PC Tx to UsbToTtl's Rx, the UsbToTtl's rx led will keep on, and will hang the Pi to death though I never send anything from PC, any idea?

Bug with Threading

Hello @Ellerbach ,

thanks for your good work with this serial port implementation!

I found a possible error source in your implementation. In your SerialDevice.cs in the open() method you wrote

Task.Run((Action)StartReading, CancellationToken);
this.fd = fd;

while it should be

this.fd = fd;
Task.Run((Action)StartReading, CancellationToken);

because of threading issues. It can happen that the newly started thread checks

if (!fd.HasValue)
{
    throw new Exception();
}

and ends with exception because "this.fd = fd;" has not been set at the time. This indeed happend to me. Switching the instructions solved the problem.

Could you please fix this?

Best Regards

Receiving works, but not sending

Hello @Ellerbach !

Thank you for your work!

I have been testing it to send data from the Raspberry Pi3 GPIO and I can receive data, but the sent data does not arrive to my other testing PC.
This is the simple code I use for the test:

Dim mySer As IO.Ports.SerialDevice = New IO.Ports.SerialDevice("/dev/ttyS0", IO.Ports.BaudRate.B115200)
AddHandler mySer.DataReceived, AddressOf DataRec
mySer.Open()
While (Not Console.KeyAvailable)
    Dim MySerialText As String = Date.Now.ToString("dd/MM/yyyy HH:mm:ss")
    Dim buf As Byte() = System.Text.Encoding.ASCII.GetBytes(MySerialText)
    Console.WriteLine("Sending: " & MySerialText)
    mySer.Write(buf)
    Threading.Thread.Sleep(500)
End While
Console.WriteLine("Closing port")
mySer.Close()

As I said, it works receiving the data, but not sending.
I tested a loopback joining the Raspberry TX and RX pins and nothing is received, but the curious thing is that if I do this loopback test from python it works, and after it, it also works with my code, so I am wondering if I am missing a "wake up call" to the serial sending port or something like that.
Could you please help me?

This is the python code that "wakes" the serial port and after it my code also works:

#!/usr/bin/env python
import time
import serial
ser = serial.Serial(
               port='/dev/ttyS0',
               baudrate = 115200,
               parity=serial.PARITY_NONE,
               stopbits=serial.STOPBITS_ONE,
               bytesize=serial.EIGHTBITS,
               timeout=1
           )
counter=0
while 1:
               ser.write('Write counter: %d \n'%(counter))
               time.sleep(1)
               counter += 1

Thanks!

Not being able to read from buffer on sending a command request

Hello,
I am connecting to one of our device using serial port and requesting some data from it and I am using the first approach that is given in Readme. I am using a serial port sniffer to check the buffers and I can see the device sending some data into the buffer however it is not being read/ read event not being fired ever. I have just added the code to write to the serial port in Program.cs as given below. Any idea if I am missing something?

... Same code as is....
Console.WriteLine("Yes, we have the embedded serial port available, opening it");
SerialDevice mySer = new SerialDevice("/dev/ttyS0", BaudRate.B1152000);
mySer.DataReceived += MySer_DataReceived;
mySer.Open();
var command = string.Concat((char)1, "i20100");
Console.WriteLine($"Writing following command: {command}");
mySer.Write(System.Text.Encoding.UTF8.GetBytes(command));

while (!Console.KeyAvailable)
;
mySer.Close();

.... rest of the code.

Garble or high CPU

I have tried both methods, using bluetooth. Using a Pi 3b+ I bind the remote bluetooth module (ESP32) with the rfcomm tool, then refer to /dev/rfcomm0 in the c# code instead of ttyS0, etc. It mostly works (see below), but I'm running into the following:

  • First method works good with receiving on the Pi, no strange stuff, and is decent on CPU usage (about 5%). However something (the so library?) on the pi sends random bytes (it seems) to the esp32 module, even when I'm not sending anything explicitly in code.

  • The second method works flawlessly in receiving and sending (no random garble being sent to the esp32), however it consumes 50-60% cpu time on the pi, which is too much to make it useful. It even consumes this when I just open de port and literally do nothing else in the code (no sending or receiving).

Any pointers would be great, since these are the only two methods I came across to make it work in .Net core. The pi and esp32 work fine in Nodejs for example, but the threading model is too limited for my purposes that way.
Thanks

Serial port names - ttyPS*

This is very nice library but there is another one serial port naming policy in linux. The serial port name starts with ttyPS... sometimes. This particular case has happened on Zynq Linux from Xilinx.

[19:03][root@buildroot][~] $ setserial /dev/ttyPS0
/dev/ttyPS0, UART: undefined, Port: 0x0000, IRQ: 26
[19:03][root@buildroot][~] $ setserial /dev/ttyPS1
/dev/ttyPS1, UART: undefined, Port: 0x0000, IRQ: 27
[19:04][root@buildroot][~] $

Thank you for your effort

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.