Main Site Documentation

What is the right way to do serial port I/O?


#1

I’m new to the .NETMF and Gadgeteer world but am incredibly impressed so far. I went from not even being aware this technology existed last Wednesday to a crude but complete prototype application for controlling an autonomous profiling oceanographic instrument platform today. So far, my priority has been getting something running in a hurry to see how the whole process goes. Now that I’ve got something working, I’d like to go back and clean up some of my shortcuts. My first issue is understanding how to write serial port I/O so it is “non-blocking” In my instrument platform there is a pressure transducer connected to a DGH current loop to RS232 converter. The pressure transducer is exposed to seawater pressure at whatever depth the instrument is at. This part of my application reads the pressure transducer, runs a PID control algorithm calculation and sets the speed of a motor (also a RS232 device) that pumps oil in and out of a bladder that change the platform’s buoyancy so it rises or sinks to the desired depth.

Here is what I did for the serial port I/O to get something working in a hurry.

    
    //DGH stuff
            int DGHReadCount = 0;
            int DGHBytesRead = 0;
            
            byte[] DGHTxBytes;
            byte[] DGHRxBytes = new byte[2];
            byte[] DGHRxByteArray = new byte[20];
            byte[] strippedDGHRxByteArray = new byte[15];

            char[] DGHRxCharArray = new char[20];

            string DGHRead = "$1RD\r";       //$1RD terminated with CR is the command to read data from the DGH
            string DGHRxString = "";
            
            double pressureDBar = 1000.0;
            double ma2bar = (15.0/1.45038) / 16.0;     //4-20 ma transducer; 15 psig full scale; 1 dBar = 1.45038 psi
            double maOffset = 4.0;

   //Setup the DGH RS232 port for the pressure transducer
            SerialPort DGHPort = new SerialPort("COM1", 38400, Parity.None, 8, StopBits.One);
            DGHPort.ReadTimeout = 0;
            DGHPort.Open();
            DGHPort.Flush();

//Send the DGH read command
                DGHPort.Write(DGHTxBytes, 0, DGHTxBytes.Length);

                Debug.Print("Waiting for DGH pressure value");
                // read the data, should terminated with a CR
                DGHBytesRead = 0;
                while (DGHBytesRead < 12)
                {
                    DGHReadCount = DGHPort.Read(DGHRxBytes, 0, 1);
                    if (DGHReadCount > 0)
                    {
                        DGHRxByteArray[DGHBytesRead] = DGHRxBytes[0];
                        DGHBytesRead = DGHBytesRead + 1;
                    }
                    else
                        Thread.Sleep(1);
                }

                //Get rid of non-characters in DGH byte array
                for (int i = 2; i < 11; i++)
                {
                    strippedDGHRxByteArray[i - 2] = DGHRxByteArray[i];
                }
                
                //Convert to bar (pressure)
                DGHRxCharArray = Encoding.UTF8.GetChars(strippedDGHRxByteArray);
                DGHRxString = new string(DGHRxCharArray);
                pressureDBar = Convert.ToDouble(DGHRxString);
                pressureDBar = (pressureDBar - maOffset) * ma2bar;

I’m pretty sure this meets the definition of blocking code. I’ve read a lot of the posts regarding non-blocking code but I’m still not sure the “right” way to go about it. Eventually, my depth control loop will be running continuously but I’ll also be reading another 3-5 instruments that are also RS232. They won’t be part of a control loop, I just need to send them a command, read the response and log the data somewhere in my code. I’m guessing that it will be a pretty bad idea to try and deal with 5-7 serial ports using the approach I’m currently using and would be overjoyed to get some advice.

Thanks - Gene


#2

Very interesting project you have there.

Couple of suggestions.

  1. Declare a buffer of a certain size (12 bytes for example) and try to read as many bytes as available from the serial port up to the size of that buffer.
  2. You can use Arra.Copy to copy from one array to another. You can specify starting index and length. This is for the portion of the code where you are removing first two bytes.

As far multiple sensors I would try to move that code into a separate thread.


#3

Andre - Thanks for the quick response, so far I’m just using plain .NETMF. Should I make the jump to Gadgeteers projects?

  1. Did you mean "when they are available [em]ADD[/em] this in a new thread? If so, can you point me at some documentation describing how to set up and use multiple threads?
  2. I think I understand that += sets up an event handler but, in my short time in the Gadgeteer world, I haven’t been able to find much documentation. Where can I find out more about “rs232.DataReceived” and “rs232.ErrorReceived”?
  3. Assuming the event handler is running in a separate thread, what do I do in the main thread without blocking while I’m waiting for the complete response from the DGH?

Thanks again


#4

Architect - Can you point me at a code example? When I search the code share I get way too many choices and the ones I’ve looked at haven’t been that helpful.


#5

OK, I’m beginning to get an inkling of how I might do this. I’d like to see if it makes sense and also ask a few more questions before I dive in and start coding. Here’s what I’m thinking:

  1. Define a new thread for each serial device and put a serial receive event handler there.
  2. Define some variables that the event handlers can write to and main() can read.
  3. Build a state machine in main() that cycles through all the stuff I have to do. When I get to the appropriate state where I want to request a reading from one of the serial devices I can write the device’s read string to the appropriate serial port and also execute the serial read command for that port.
  4. The event handler should catch the characters from the serial device. When the response is complete, the event handler will send the data to the appropriate variable in main() (defined in step 2 above).
  5. Repeat as and when necessary for each of the serial devices.

Questions
A) Does this make at least some sense?

B) I’ve seen an example that shows where to define variables so one thread can write to it and main() can read it. Can I define a serial port in the same way that an event handler in one thread can read and main() can write? I can put the serial write in the same thread as the read event handler, but this makes a little more sense to me.

C) According to what I found on the web ( http://blogs.msdn.com/b/netmfteam/archive/2011/01/17/threads-and-thread-priorities-in-netmf.aspx ) every thread gets a 20 msec time slice but “any thread that cant do anything (ie is blocked), relinquishes its time back to the scheduler which gives it to the next thread in line”. If the only thing that is in a thread is an event handler, will that thread relinquish its time slice automatically if there isn’t a character in the buffer and not use the entire 20 msec?

D) Does the “count” parameter in the serial port read command set the input buffer length for that read event for that serial port?

E) Am I correct in assuming that using event handlers in this way means .NETMF is setting up an interrupt driven serial port for me and all I have to do is type “+=” ?

F) Am I correct in assuming that a serial write is non-blocking ie if I execute a serialwrite command with a 10 element byte array (for example), the serialwrite command will execute, my program will immediately move on to the next command and .NETMF will handle all the buffering and whatnot to send out all the bytes iln the array in the background?

Thanks in advance


#6

One last question if you don’t mind. After thinking about this for a while and trying to understand what you’re suggesting, I’m not sure I see the need to put the serial read event handlers in separate threads. If I can build a non-blocking state machine (and that might be a big if), and I have event driven serial read routines, it shouldn’t matter what thread the event handlers sit in. They might as well sit in main() and save the thread switching overhead, right? Or am I missing something?

Thanks


#7

There might be something I don’t understand. If I use an event handler like

rs232.serialPort.DataReceived += new GT.Interfaces.Serial.DataReceivedEventHandler(serialPort_DataReceived);

or

rs232.serialPort.LineReceived += new GT.Interfaces.Serial.LineReceivedEventHandler(serialPort_LineReceived);

don’t they only get called when there is a character available and won’t that be non-blocking regardless of whether the event handler is in the same thread as main() or in its own thread?


#8

First of all thanks for all the help. I guess my question about why do I need threads boils down to

  1. the receive event handler is non blocking regardless of what thread it sits in.
  2. You told me earlier the serial write is non-blocking.
  3. If I can build a state machine that a) issues writes commands to the serial ports when I need to, b) the serial event handlers read each character as they arrive and when a complete message is received the event handler can pass the response back up to the state machine where c) the state machine deals with the response and d) as long as I make sure the rest of my state machine is non blocking, then my whole app should be non-blocking.

Does that sound right?


#9

Hi Gene,

About threads. Lets be clear, netmf already has threads behind the scenes. An Event hander is typically just something that looks for a condition and then generates an event that makes the core dispatcher then run your code. So in a serial datareceived event handler, the low level serial port driver code may be polling the serial port to see if here’s data ready, or it may be looking for a register bit to be set, or might be waiting for a hardware interrupt to fire, which then drives your handler. Trying to avoid threads doesn’t avoid threads, it just means that netmf is dealing with the intricacies of locks etc on relevant data, rather than you needing to do some of it.

There’s no real reason why you couldn’t write your own “MyDataReceived” handler, like the Gadgeteer add-on LineReceived handler does (that you can check out any time you want - not necessarily saying you should model anything you do on that but it’s available to look at)

Now one of the specific things you will need to think about is your item 3B in the above text. When the DataReceived event handler fires, your code in there needs to grab the bytes from the serial port, stuff them somewhere and then figure out whether it meets your condition to trigger the state machine, and appropriately flag that back to main() for processing


#10

OK enough talk; time to start coding.

Thanks for all the help, I’m sure it has saved me an incredible amount of time and frustration.

Cheers - Gene


#11

@ Gene - Greetings!

Hey Gene. I’m a mechanical engineering senior and for my senior design project I’m making something that needs to sense depth. I was curious what hardware you used to accomplish this.

Thank you for your time.


#12

First, can you change your user ID to something that has your name? It makes your profile a little more human :slight_smile:

To sense depth is very easy using a pressure sensor. There is a simple calculation to convert the pressure into a depth reading but note that your need to know the density of the water to get an more accurate reading. Sea water is more dense than fresh water.

Just get yourself a pressure sensor and feed the output into the ADC of the processor and then apply the calculation. You’ll find loads of information on this calculation via a Google search.

You can use 4-20mA output sensors (in my own opinion, the easiest to use as only 2 wire and can be easily scaled to the ADC input with only 1 resistor) or a voltage output type. These are often 0-5V, 0-10V etc. Depending on the ADC input, you may need external voltage scaling to suit the ADC.


#13

@ Dave McLaughlin - Name changed!

Thank you for responding!

In your response, you say “… and feed the output to the ADC of the processor…” I know that ADC means analog to digital converter however I have very little experience with .NET gadgeteering. Is the ADC of the processor a module one purchases or is it something intrinsic to all mainboards on available for purchase?

I was thinking of using the Cerberus.

Also, for the pressure sensor, the maximum depth it needs to sense (in sea water) is 150ft. I’m currently scouring google to find one that’s suitable but I was curious if you’ve ever used one (and could therefor simply my search.)

Thank you much.


#14

Keith

We’re using a GHI spider for the controller because of the massive amount of RAM and Flash it has but the Cerberus should be fine for a simple application.

We use a variety of simple (inexpensive) to sophisticated (really expensive) pressure transducers. I’m guessing you’re looking for something on the cheap side. Omega Engineering ( www.omega.com ) has a ton of pressure transducers and just about everything else you might want. I’ve used their PX429 series for medium accuracy applications. They also have plenty of less expensive/less accurate pressure transducers on their website and they have good technical support if you want some help selecting something appropriate for your needs.

If you aren’t familiar with interfacing computers to sensors then you need to educate yourself a little on this subject. There are plenty of books and websites dedicated to this subject. Although it takes some effort, if you find this stuff interesting having these skills on your resume makes you much more attractive to potential employers. You might be able to find some EE classes at your college that will give you much better introduction but here is a wildly oversimplified overview.

Dave is exactly right, you need an ADC and usually some signal conditioning to make an accurate measurement for most pressure transducers. You can buy pressure transducers with a 4-20 milliamp (mA) output. This is a standard sensor output and you should be able to find some tutorials on the web. Basically all you do is wire the + side of a DC voltage source (usually 10-20 volts) to the positive terminal of your sensor, the negative side of the sensor to 1 side of your “shunt” resistor and the other side of the shunt to the negative side of the voltage source. You use your ADC to measure the voltage drop across the shunt resistor and voila, you’ve got a voltage proportional to pressure. The shunt resistor value sets the scale factor.

You can also get pressure transducers with a voltage output. You need to power the sensor with a DC source and the sensor outputs a voltage proportional to pressure, usually 0-5VDC represents 0 psi to whatever full scale pressure the sensor supports.

There is an ADC built into the Spider CPU and some code examples on the forum. It is only a 10 bit ADC (if I understand correctly) which doesn’t give enough resolution or accuracy for our needs but might work for your needs. You can buy pressure transducers with a RS232 output which makes life a lot easier for you but they’re kind of pricey and there aren’t a lot of choices out there. You can interface a much more precise external ADC and there are lots of good choices with SPI outputs that work quite nicely with the Gadgeteer hardware. Or you can buy an analog to RS232 signal conditioner that plugs right into your pressure transducer (either 4-20 mA or voltage output) and outputs a RS232 message with the voltage reading. Take a look at the Omega D1000 series of modules.

Good luck - Gene


#15

Hi Keith (that’s better ay?)

Gene hinted on the fact that the sensor may be expensive. The issue you have is that the depth if 150ft, and although no issue in doing this with a pressure sensor, the cable is likely to be the biggest cost here.

Most, if not all, submersible sensors require a special cable with a small vent tube installed in it. This vent tube is used to apply atmospheric pressure to the back of the sensor. The water exerts pressure on the front. You then directly read the pressure and calculate this to depth. Without this vent tube you would need to have some form of sensor at the surface which records the atmospheric pressure and then subtract this from the water pressure to get the correct depth otherwise atmospheric changes will cause your depth to change.

This will certainly be the case with gauge or absolute sensors that don’t have this vent tube but in general, a submersible depth sensor is designed with this vent tube but just check with the supplier.


#16

The project I’m working on surfaces a free diver after a timer expires by inflating a life jacket.

The absolute maximum depth that anyone goes to who isn’t trying to set a world record is 150 feet. I think the maximum a free diver holds their breath for a dive is about a minute (quoting my free diver teammate.)

Anything but a self contained apparatus would be unsatisfactory to my professors. (No ties to the surface.)

I found this Dwyer pressure transducer, http://www.dwyer-inst.com/Product/Pressure/SinglePressure/Transmitters/Series628CR/Ordering model 628CR-10-GH-P1-E1-S1. It outputs 4 to 20 mA. Could I make this thing work? I chose this one because I have a $500 budget. My teammates and I are going to foot the remainder of the bill.

I’m going to stop by the electronics lab and the under water robotics team and ask them about how to power a transducer, how to hook it up to an ADC. Actually I just reread what Gene said about how a transducer works and it makes perfect sense. I think a better question to ask them would be how to set up a constant power supply to the transducer so that the voltage drop read by the ADC stays accurate. Do cells typically output the same voltage over their life?

As for the accuracy of the ADC in the Spider… I’m going to have to pull out my Mechanical Measurements book to see if that provides an acceptable resolution.

Let’s say I have to buy an ADC: what module or adapter would I need to interface the ADC with a .net gadgeteer item?


#17

OK. That helps a lot.

Those sensors should work fine but you’ll need to aim for the 100 psi sensor if your max depth is 150 feet. This handy little calculator should help.

http://www.calctool.org/CALC/other/games/depth_press

This includes the ATM pressure in the calculation as this is what your sensor will see anyway. You can subtract this at the surface before the diver enters the water through some sort of ZEROING button etc.

Don’t worry about any current regulation for the sensor. Just make sure you have the voltage at 9V or higher. It will self regulate. That’s the beauty of current loop sensors! :slight_smile:

Good luck with the project. It sounds like a nice challenge to work on.


#18

Too much tequila? :smiley:


#19

I mean - Why are you so upset with @ mhectorgato? He is not even in this thread.


#20

I was about to make the same comment about Andre.m being on Tequila etc!

:slight_smile:

It is hard to comment sometimes on an old thread and to keep it on topic, especially when you are actually trying to get an answer from someone who posted in there before and you are interested in one of the topics discussed within, as has happened here.

Sometimes it is better to start a new thread and ask a direct question of all. It often gets a better response anyway.