03-17-2020 10:08 AM
Hi,
For a project I want to couple an Arduino Uno to the LabVIEW environment.
I want to send sensor data for more than 20 sensors. Some of these sensors have values of more than 8 bits. Therefore, I have seperated them into 4 bytes which can be put back together into a numeric single (32 bit number). This seems to work occasionally (if the array is ordered like: 0 0 49 40). However, often the array is ordered different (e.g. 40 0 0 49), resulting in a wrong 32-bit value. I know this is caused by how the VISA read block works (fifo until it collected 4 bytes). However, I do not manage to collect the bytes continously in the right order. I did some research on using a termination character, but did seems not the way to go.
Hopefully someone can help me.
Thanks in advance,
Bryan
Arduino code:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
unsigned long myNumber = 10289.5;
byte buf[4];
buf[0] = myNumber & 255;
buf[1] = (myNumber >> 😎 & 255;
buf[2] = (myNumber >> 16) & 255;
buf[3] = (myNumber >> 24) & 255;
Serial.write(buf, sizeof(buf));
}
Solved! Go to Solution.
03-17-2020 10:49 AM
This is not an issue with VISA, but an issue with your protocol. How is a receiver to know which byte is supposed to be the first one? How does it know it got all of the data? You have a couple of options here:
1. Send your value as an ASCII string. I think you can do this with just a simple Serial.PrintLn(myNumber). I am not an Arduino expert at all, so be sure to research that. But I do know this command will append a Line Feed to the message. With that, you can then use the Termination Character feature of VISA to read the entire message very simply.
2. Add a byte or two to the front of your message and a checksum of some sort to the end. With binary/hex/raw command formats, I typically see a command begin with 0x02 (Start Text) followed by a byte to state either how much data is in the message or a message ID (which the receiver can derive the number of bytes are left). The checksum at the end can be a CRC or a simple addition of all of the bytes in the message. On the receive side, you just read a single byte until you read a 0x02, then read an additional byte to get the number of bytes and/or ID, then read the rest of the message (including the checksum). You then do whatever algorithm you had for the checksum and verify it is correct. If it is correct, you process the data. If wrong, just ignore what you had. Then you go back to reading a single byte looking for the start character.
03-17-2020 11:16 AM
First, why are you trying to put a floating point value in an unsigned long variable?
Anyway, crossrulz is right. Your Arduino code is sending four numbers repeatedly (say ABCD). From NI VISA's point of view, it sees ABCDABCDABCDABCDABCD.....
Depending on when you start reading (and assuming you read 4 bytes each time), you could get CDAB CDAB CDAB CDAB...
You need a way to signal "start of transmission" or "end of transmission" somehow. If you're sending truly arbitrary data, like literally every single value, you will need to signal the start of the transmission with a special character then read ahead by a few values. If you are sending a limited subset of values, say from 0-1000, you can use values outside of the range of 0-1000 as your termination character. (You can't do this if you're sending the full range of values, as "all values" includes the termination character!)
I would recommend doing what crossrulz said, but here are some thoughts on implementation.
First, in your Arduino code, decide on a "start value" (0x02 is fine), then your "identifier value" to let your receiving code know how long the packet is. If you only ever send one data value, you don't need this, but it's good to have. I would STRONGLY recommend an identifier rather than a packet length. With an identifier, you can create a table of message-lengths. For example:
Message ID Description Length
0x00 New 8-bit sensor value 1 bytes
0x01 New 32-bit sensor value 4 bytes
0x02 Sensor error 0 bytes
0x03 Startup acknowledge 0 bytes
With this, you can always add new messages, and your receiver knows how to interpret them. For 0x02 and 0x03, those would be "status reporting" messages that your device can send if it detects an internal error (just an example).
Your packet would then look like this:
0x02 [transmission begins]
0x01 [this message is new sensor data, and you know it's always 4 bytes]
0x00 [data byte 0]
0x00 [data byte 1]
0x49 [data byte 2]
0x40 [data byte 3]
0x8C [checksum- just add all previous bytes and drop anything bigger than 0xFF)
On the LabVIEW side, have your producer constantly sample data and add each sample to the end of an array (or queue or whatever) while your consumer pulls data from the other side. When the consumer sees a 0x02, it knows a new message started, so it pulls the next byte and looks up how long the message is. If it's a 0x02 or 0x03, the total length is 1 (it's just a checksum). If it's 0x00, it's 2 (one data byte and one checksum). If it's 0x01, it's 5 more bytes (4 data bytes plus a checksum) so pull the next 5 bytes out of the array/queue/whatever. If the checksum read from the serial stream matches the checksum generated on the LabVIEW side, it's a valid packet, so extract the data bits and reassemble them.
I know this sounds complicated but you HAVE to have a way to let the system know where the data starts and where it ends.
And last, if I haven't put you to sleep, and your sample rate is super low.... you can just use timeouts. Set your Arduino to delay 1 second between each sample and set NI VISA to read 4 bytes with a 200 ms timeout.
Serial.write(buf, sizeof(buf));
delay(1000);
Call the VISA Read function in LabVIEW repeatedly and clear the Timeout error after each call. If there was a timeout error, it got either 0 bytes or it did a partial read. If there was no error, it got a full 4 bytes.
03-17-2020 01:26 PM - edited 03-17-2020 01:29 PM
@BertMcMahan wrote:On the LabVIEW side, have your producer constantly sample data and add each sample to the end of an array (or queue or whatever) while your consumer pulls data from the other side. When the consumer sees a 0x02, it knows a new message started, so it pulls the next byte and looks up how long the message is. If it's a 0x02 or 0x03, the total length is 1 (it's just a checksum). If it's 0x00, it's 2 (one data byte and one checksum). If it's 0x01, it's 5 more bytes (4 data bytes plus a checksum) so pull the next 5 bytes out of the array/queue/whatever. If the checksum read from the serial stream matches the checksum generated on the LabVIEW side, it's a valid packet, so extract the data bits and reassemble them.
You were doing good until here. The actual reading of the port and decoding the data does not need to be a Producer/Consumer. This is a place I actually recommend cascading case structures. It just helps the flow in my head. Here is an example I have been putting together for my NI Week discussion (haven't figure out if I can make the new date or not). Where I do recommend using a queue is where I am writing to the chart. You can replace the chart with an Enqueue Element and then have a consumer loop do something with the data. I have also used User Events so many different loops can do something with the data (one loop logs, another loop runs some calculations on the data as it comes in, another just for GUI, etc.).
NOTE: I just noticed I missed wiring a FALSE to the Enable Termination Character. Corrected it in my copy, but I'm too lazy to update the snippet here.
03-17-2020 02:08 PM
That works but you will lose two messages if you drop a byte in a message, as your 9-byte Read will "eat" the next Start of Message character. Typically my applications are "get all values transmitted" rather than "get the latest value" so I have to sample everything, then scan the data and extract frames that way.
I tend towards grabbing a buffer of data and look for a Start of Command character. I throw away bytes until I see one, then check for a Message ID byte and finally a checksum. If that's valid, I take the whole thing out of the buffer and send that as a "message" upstream. If the checksum is NOT valid, I throw away only the first byte and begin checking for Start of Command characters.
03-18-2020 04:45 AM
First of all, I want to thank both of you for your quick answers.
I just made a (rough) start with the implementation of your suggestions (see attached file).
I'am totaly unexperienced in working with LabVIEW, so if I am doing something completely wrong please correct me.
It seems that the start byte 0x02 is not recognized, is this because a string is used in LabVIEW? Should I use something else? Furthermore, the case studies forces me to also add a second case, for the False case blocks I just connected the start and the end of the block, is this correct?
Updated Arduino code
buf[0] = myNumber & 255;
buf[1] = (myNumber >> 😎 & 255;
buf[2] = (myNumber >> 16) & 255;
buf[3] = (myNumber >> 24) & 255;
Serial.write("0x02"); // Start byte
Serial.write("0x01"); // Identifier byte
Serial.write(buf, sizeof(buf)); // Data (4bytes)
03-18-2020 07:12 AM
What do you mean by "It seems that the start byte 0x02 is not recognized"?
In your Arduino code, you are writing "0x02" which is a literal string consisting of an zero x zero two. You need to send a single byte that has a value of 2. In BASIC that would be a string built using chr$(2). I don't know what that looks like in Arduino language.
03-18-2020 08:33 AM
Thanks for your response. You are totally right on that (stupid mistake of me). I have changed it, and now this part works!
03-18-2020 10:59 AM
@BertMcMahan wrote:
I tend towards grabbing a buffer of data and look for a Start of Command character. I throw away bytes until I see one, then check for a Message ID byte and finally a checksum. If that's valid, I take the whole thing out of the buffer and send that as a "message" upstream. If the checksum is NOT valid, I throw away only the first byte and begin checking for Start of Command characters.
I used to do that, but the string manipulations made the "buffer" slow and caused a lot of issues. But now you are giving me ideas to look at. Hopefully I can find some time to reexamine this since I expect to be "between projects" fairly soon.
03-18-2020 11:42 AM
First of all, I want to thank everyone for all the help so far.
I finally can read out my Arduino sensor data.
I have one remaining question:
Besides the Arduino data I also have an NI DAQ card, which I read using the DAQ assistant in LabVIEW.
From research I thought that it was possible to run both data acquistion methods parallel, in two different while loops. However i tried 3 possible methods that did not workout.3 possible methods to read Arduino and NI DAQ
Using the first and the second method, the data acquisition from the Arduino works as expected. However, the DAQ Assistant does not return any value (until the while loop is stopped, than it will return a value once).
Using the third method, I get data from both the Arduino and the DAQ Assistant. However, the data from the Arduino is way slower.
Normally I would think the second method was the correct one, but something goes wrong?
Hopefully someone can help me.