How do i get index 6+7 and 8+9 from a stream of 24 chars that come from the serial input into the Arduino? The connected serial device is in idle mode until i send a command to it. When i send “ZD”, it will send a timecode in the format ZD2b102c080c090e05ba0549, where ZD stands for the type of message it returns. 2c represents the minutes and 08 the hours of the the current time (in HEX format). The last 4 chars are a checksum. There is no CR or NL at the end. I would like to get the minutes and hours as interger values. Any help would be appreciated. Thanks!
State machine?
Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking (has example of something similar)
Send the ZD command and then read the 24 chars into an array which will make it easy to pick off 6+7 and 8+9. You must also start a timeout so that if it takes longer than, say, one second and you still haven't received 24 characters from the ZD command, you ignore whatever you've just read, send the ZD command again and restart the timeout.
If you know how to compute the checksum it would be best to do that too and if it doesn't match you again ignore what you've just read and try again.
Pete
krl:
How do i get index 6+7 and 8+9 from a stream of 24 chars that come from the serial input into the Arduino? The connected serial device is in idle mode until i send a command to it. When i send “ZD”, it will send a timecode in the format ZD2b102c080c090e05ba0549, where ZD stands for the type of message it returns. 2c represents the minutes and 08 the hours of the the current time (in HEX format). The last 4 chars are a checksum. There is no CR or NL at the end. I would like to get the minutes and hours as interger values. Any help would be appreciated. Thanks!
this is a down and dirty method that while it will work for you, well the problem is you have two 'c' leading chars in your data stream...
int Hour;
int Minute;
void setup()
{
Serial.begin(9600);
}
//
void loop()
{
if(Serial.available())
{
char myChar = Serial.read();
if (myChar == 'c')
{
Hour = Serial.parseInt();
Serial.println(Hour);
Minute = Serial.parseInt();
Serial.println(Minute);
}
}
}
my feeling is that it will prove unreliable unless there is considerable time between transmissions...
You can also take a look at how GPS libs concantinate serial character into sentences. Say $GPRMC... Limor's Adafruit GPS is a good one IMO. Translation back to standard C is straightforward.
Do not get hung-up on delimiters, you can always count characters!
Here is a snippet from my GPS for PSoC:
/*
Project: Nokia5110GLCD_PSoC4.cydwr (using bit-bang SPI)
Build date: 20140909 All code by Ray is public domain - OTHER? see Credits-Licenses.txt
*/
#include <main.h>
#include <stdlib.h>
// Global variables
uint8_t hour, minute, seconds; //, year, month, day;
uint8_t day, month, year;
// .... Stuff ... Deleted for this snippet....
char nmea[120];
c = UART_UartGetChar(); // Get received character or null
if (c)
{
if(c == '
Ray
Full code http://www.hackster.io/rayburne/gps-clock-an-about-time-project-for-psoc-4200) { // $ start of NMEA sentences
for(k=0; k<5; k++) // need 5 characters for sentence type
{
LED_Write( ~LED_Read() ); // flicker LED for activity
do {
c = UART_UartGetChar();
}
while (! (c));
nmea[k] = c; // G + P + R + M + C
}
// DEBUGGING to GLCD
// glcd_tiny_draw_string(0, 3, buffer);
// glcd_write() ; // display
LED_Write( LOW ); // LED off
if (strstr(nmea, "GPRMC"))
{
do {
do {
c = UART_UartGetChar();
LED_Write( ~LED_Read() ); // flicker LED
} while (!(c));
nmea[k] = c;
++k;
} while ( !( c == '*' ) && k < 120) ; // marker
LED_Write( LOW ); // LED off
// Inspiration: Limor Fried's Arduino GPS lib
char *p = nmea;
p = strchr(p, ',') + 1; // position after 1st comma
// float timef = atof(p);
// uint32_t time = timef;
uint32_t time = atoi(p);
hour = time / 10000;
minute = (time % 10000) / 100;
seconds = (time % 100);
// output to GLCD
sprintf(buffer, " %s",""); // clearbuffer
// this corrects time to the West but not the date!!!
if( hour > GMToffset) {
hour = hour - GMToffset;
} else {
hour = (hour + 24) - GMToffset; }
// correct midnight to follow standard format
if (hour == 24) hour = 0;
if (hour < 10) {
if (DayLightSavings) {
sprintf(buffer, "EDT: %d", hour);
} else {
sprintf(buffer, "EST: %d", hour);
}
.......... Etc, etc, etc
Ray
Full code http://www.hackster.io/rayburne/gps-clock-an-about-time-project-for-psoc-4200
Thanks for all the hints, but somehow i'm still stuck.
[quote author=Nick Gammon link=topic=266669.msg1880441#msg1880441 date=1410642649]
Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking (has example of something similar)[/quote]
I understand that case '\n' waits for a new line and then starts the processing. But how would i first check for "ZD", then count for my maximum 24 characters and then start the processing?
[quote author=Nick Gammon link=topic=266669.msg1880441#msg1880441 date=1410642649]
http://www.gammon.com.au/statemachine[/quote]
This is a bit over the top for me at the moment. It would be fine to check, if there is a "Z", then collect the rest of the 23 characters in an array / buffer, read the buffer and get 6th + 7th and 8th + 9the character, an then convert both from Hex to Decimal.
el_supremo:
Send the ZD command and then read the 24 chars into an array which will make it easy to pick off 6+7 and 8+9...
This is good advice, but how do i do it? Which commands should i use?
mrburnette:
..Do not get hung-up on delimiters, you can always count characters!
I wanted to try this, but unfortunately your code does not compile: c = UART_UartGetChar(); > expected constructor, destructor or type conversion before "=" token.
try this yet?
int Hour;
int Minute;
void setup()
{
Serial.begin(9600);
}
//
void loop()
{
if(Serial.available())
{
char myChar = Serial.read();
if (myChar == 'c')
{
Hour = Serial.parseInt();
Serial.println(Hour);
Minute = Serial.parseInt();
Serial.println(Minute);
}
}
}
This is a bit over the top for me at the moment.
Why? Read the serial data. If you read a 'Z', you are in the "GotAZ" state, where you do something different.
In the GotAZ state, read the first character available. If you get a D, you are in the GotAZandAD state. If not, you return to the LookingForZ state.
In the GotAZAndAD state, read when there is data, until you've read 22 more characters.
Engage your brain!
BulldogLowell:
this is a down and dirty method that while it will work for you, well the problem is you have two 'c' leading chars in your data stream... my feeling is that it will prove unreliable unless there is considerable time between transmissions...
I adapted your code, but nothing is being shown in the serial monitor.
int Hour;
int Minute;
void setup()
{
Serial.begin(9600);
Serial1.begin(4800, SERIAL_8N2);
}
//
void loop()
{
Serial1.println("ZD");
if(Serial1.available())
{
char myChar = Serial1.read();
if (myChar == 'c')
{
Hour = Serial1.parseInt();
Serial.println(Hour);
Minute = Serial1.parseInt();
Serial.println(Minute);
}
}
delay(1000);
}
if(Serial1.available())
{
char myChar = Serial1.read();
if (myChar == 'c')
{
Hour = Serial.parseInt();
Serial.println(Hour);
Minute = Serial.parseInt();
Serial.println(Minute);
}
}
You have only read 1 character from the serial1 port! You have to read beyond (or to the end) of the line before attempting to parse out the Hour/Minute.
} while ( !( c == '*' ) && k < 120) ; // marker
In my earlier GPS example (C code for another processor), I read until I find a '*' or until I fill the array completely. In your case, you can read and stop after receiving 24 characters.
Ray
PS: Just a note... this is a fundamental concept in any C uC program and we are attempting to give you hints... there are many ways this can be coded, so everyone is stating their favorite methodology. This is life with Arduino on the forum. We could give you the solution, but that is simply unfair to you. If you get frustrated; back-off for a while and return. Once the concept is understood, you will be happy we did not spoon-feed you.
Another Note:
When I mess around with parsing, I usually start with one of two crutches:
- Set a string variable to representative data instead of reading the serial port,
- Set the console in loopback mode so I can type the input string instead of using the device
This allows me to work on the parsing methodology before having to mess with reading from the (real) serial device.
You may wish to look at: Inkling
Also, to my knowledge ParseInt() works only on numeric strings, 0-9... which is to say, I do not think it can manipulate HEX. I may be wrong! Take a look at function; Analyzing Strings with sscanf
Late Addition:
If you would like to play around with serial parsing concepts in a working program, you may want to run my sketch here:
http://forum.arduino.cc/index.php?topic=147550.0
At least, it may take the edge off that HEX stuff for a while ![]()
well, each time your myChar != 'c' then you pause for a second and retransmit "ZD"
is that what you want to to?
If you want t retransmit every second, try to put it on a millis() timer and wait until the Serial Buffer is empty to RX
something like this (untested)
int Hour;
int Minute;
unsigned long lastTransmit;
void setup()
{
Serial.begin(9600);
Serial1.begin(4800, SERIAL_8N2);
}
//
void loop()
{
if(Serial1.available())
{
char myChar = Serial1.read();
if (myChar == 'c')
{
Hour = Serial.parseInt();
Serial.println(Hour);
Minute = Serial.parseInt();
Serial.println(Minute);
}
}
if (millis() - lastTransmit >=1000UL && Serial.available() == 0)
{
Serial1.println("ZD");
lastTransmit = millis();
}
}
If you're sure the format doesn't change, this might work. The hex conversion routines were pulled from the web, but I'm not sure about the rest of it.
void setup() {
Serial.begin(115200);
}
char message[25];
char temp[10];
int charsRead;
int hours, minutes;
void loop()
{
if(Serial.available() > 0) {
charsRead = Serial.readBytes(message, 24);
message[charsRead] = '\0'; // Make it a string
if (strncmp(message, "ZD", 2) == 0){ // Right message type?
minutes = htoi(strncpy(temp, &message[6], 2));
hours = htoi(strncpy(temp, &message[8], 2));
Serial.println(minutes, DEC);
Serial.println(hours,DEC);
delay(1000);
}
}
}
// Convert hex chars to int
int chartoint(int c)
{
char hex[] = "aAbBcCdDeEfF";
int i;
int result = 0;
for(i = 0; result == 0 && hex[i] != '\0'; i++) {
if(hex[i] == c) {
result = 10 + (i / 2);
}
}
return result;
}
// Convert string of hex digits to int
unsigned int htoi(const char s[])
{
unsigned int result = 0;
int i = 0;
int proper = 1;
int temp;
//To take care of 0x and 0X added before the hex no.
if(s[i] == '0') {
++i;
if(s[i] == 'x' || s[i] == 'X') {
++i;
}
}
while(proper && s[i] != '\0') {
result = result * 16;
if(s[i] >= '0' && s[i] <= '9') {
result = result + (s[i] - '0');
} else {
temp = chartoint(s[i]);
if(temp == 0) {
proper = 0;
} else {
result = result + temp;
}
}
++i;
}
//If any character is not a proper hex no. , return 0
if(!proper) {
result = 0;
}
return result;
}
Ray, "Everyone needs a working example..."
Caveat: There are many, many ways to solve this puzzle. The code shown is neither intended to be the best example or the most efficient example - it is just an example that I thought could be followed simply.
Ray
The following code will produce this output in the console 9600 BAUD:
Beginning Example Run...
Original String: ZD2b102c080c090e05ba0549Substrings: 0x08 : 0x2c
Hours: 8 Minutes: 44
/*
Example by mrburnette (ray) on nano 20140914
Binary sketch size: 5,844 bytes (of a 30,720 byte maximum)
Ray says, "Public domain example"
http://forum.arduino.cc/index.php?topic=266669.msg1881289#msg1881289
How do i get index 6+7 and 8+9 from a stream of 24 chars that come from the serial input into the Arduino?
The connected serial device is in idle mode until i send a command to it. When i send “ZD”,
it will send a timecode in the format ZD2b102c080c090e05ba0549, where ZD stands for the type of message it returns.
2c represents the minutes and 08 the hours of the the current time (in HEX format). The last 4 chars are a checksum.
There is no CR or NL at the end. I would like to get the minutes and hours as interger values.
References: http://forum.arduino.cc/index.php?topic=87797.0
The problem, extract MM and HH (hex format) as integers
0123456789
"ZD2b102c080c090e05ba0549\0"
mmhh
*/
#include <Streaming.h> // get used to this library - it makes serial output fun
char sInput[] = {"ZD2b102c080c090e05ba0549\0"} ;
String sMM ; // 0xHH\0 = 5 characters
String sHH ;
char Mbuf[]= { '\0','\0','\0','\0','\0'} ;
char Hbuf[]= { '\0','\0','\0','\0','\0'} ;
int hours, minutes ;
void setup(void)
{
Serial.begin(9600);
delay(1000);
Serial << "Beginning Example Run...\r\n" ;
Serial << "Original String: " << sInput << "\r\n\r\n" ;
// alternate http://arduino.cc/en/Reference/StringSubstring
// Ref: http://arduino.cc/en/Reference/StringToCharArray
// Ref: http://stackoverflow.com/questions/23576827/arduino-convert-a-sting-hex-ffffff-into-3-int
sMM += "0x" ;
sMM += sInput[6];
sMM += sInput[7];
sMM += '\0' ;
sHH += "0x" ;
sHH += sInput[8];
sHH += sInput[9];
sHH += '\0' ;
Serial << "Substrings: " << sHH << ":" << sMM << "\r\n\r\n" ;
sMM.toCharArray( Mbuf, 5 ) ;
sHH.toCharArray( Hbuf, 5 ) ;
hours = (int)strtol(Hbuf, NULL, 16);//cast to (int) because strtol returns long
minutes= (int)strtol(Mbuf, NULL, 16);
Serial << "Hours: " << hours << " Minutes: " << minutes << "\r \n";
Serial << "\r\n *****End of run*****\r\n" ;
Serial.end();
}
void loop( void ) {}
krl:
Thanks for all the hints, but somehow i'm still stuck.[quote author=Nick Gammon link=topic=266669.msg1880441#msg1880441 date=1410642649]
Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking (has example of something similar)
I understand that case '\n' waits for a new line and then starts the processing. But how would i first check for "ZD", then count for my maximum 24 characters and then start the processing?
[/quote]
Scroll down a bit to reply #1 in that thread.
That handled something very similar to yours. You start with looking for "Z" (as PaulS says) and that triggers the process. Then you hopefully get a "D". THen you get some hex characters. It looks to me that after every two you could convert them (from ASCII to a number) and store then in an array. Then after the appropriate number has arrived you do something with it.
Thanks a lot everyone!
econjack, your code works perfectly fine.
mrburnette, your example works as well.
Nick Gammon: You are right. I really tried to get my head around this with all the examples, but there were too many obstacles for me, that i have to learn first before i can accomplish a task like this. I will check the working code step by step to understand it. Unfortunately i have to get this going soon, so it really helps to have a working code for now.
Seems like i still have to do some homework, because the time is represented in GMT and the information about daylight savings time seems to be in some other byte. I have to check this tomorrow.
Does anyone have recommendations for books or tutorials about parsing serial data with an Arduino?
I really tried to get my head around this with all the examples, but there were too many obstacles for me, that i have to learn first before i can accomplish a task like this. I will check the working code step by step to understand it. Unfortunately i have to get this going soon, so it really helps to have a working code for now.
Please do take the time to work through the examples, use forum search and Google to gain a firm understanding of what the instructions are doing.
You got a "get out of jail" pass today, but the forum was kind to you. In the future, I expect more effort on your part.
Ray