|
|
View previous topic :: View next topic |
Author |
Message |
Markdem
Joined: 24 Jun 2005 Posts: 206
|
Serial ISR and function |
Posted: Sun May 06, 2012 8:23 pm |
|
|
Hi All,
I need to receive a heap of data from a PC to the PIC. The incoming data looks like this "$,CMD1,CMD2,CMD3,CMD4,#"
I know most people like to use a ring buffer to get the data in, but then I would have to split the data up later. Could I get the pic RDA_INT to look for a $, flag that it has a start char and disable the interrupts. Then in the main code I would be checking the flag and calling a function to get the rest of the data? I am writing the PC app too so I can write it to send a "$" wait form a ack from the pic that it is ready for the next CMD and the loop until I receive a "#"? eg, I would not be sending a string of data like above but 6 small chunks that the PIC would ack each time. Once I have the "#" char, I would enable the interrupts again.
I would like to do this is to avoid having to split the incoming data
later. I guess at the same time I would have acknowledgments for each chunk of data too.
Is this OK, or is it just rubbish programing?
Thanks.
Mark |
|
|
bkamen
Joined: 07 Jan 2004 Posts: 1611 Location: Central Illinois, USA
|
|
Posted: Mon May 07, 2012 12:27 am |
|
|
GPS applications all have a similar issue.
You want to collect a string... then then parse the string to
1: see if it's one you care about
2: if it is, collect the data you need.
there's no single solution...
but the important part is to keep the ISR collecting while the ISR can (or the main can) decide what to keep and what to toss away.
I'd stick with keeping the ISR active at all times as to not miss a char.
-Ben _________________ Dazed and confused? I don't think so. Just "plain lost" will do. :D |
|
|
Markdem
Joined: 24 Jun 2005 Posts: 206
|
|
Posted: Mon May 07, 2012 2:44 am |
|
|
Hi bkamen,
Thanks for the reply. This is not a GPS application. I just thought a $ is a good start marker.
Just to show you all what i am thinking, here is what I have.
Code: |
#int_rda
void serial_isr()
{
RS232_buffer[0] = getc();
if(RS232_buffer[0] == 36) //buffer contains $
{
disable_interrupts(int_rda); //disable the interrupt so we can deal with the rest of the data
RS232_haveRequest = 1; //flag that PC wants to send data
}
}
void RS232_Command()
{
int i = 0;
char buff[10];
while(i < RS232_buffer_size)
{
printf("!\r\n"); //send "ready for next data" signal
get_string(buff, 5);
if(buff[0] != 35) //check to see if we have got a #, meaning that it is the last data in
{
RS232_Buffer[i] = atoi(buff);
i++;
}
else
{
break;
}
}
//do somthing with the command
printf("Command OK"); //tell the PC we have finnished doing whatever the command wanted.
RS232_haveRequest = 0;
enable_interrupts(int_rda);
}
void main()
{
RS232_haveRequest = 0;
output_high(DIAG_LED);
enable_interrupts(GLOBAL);
enable_interrupts(int_rda);
while(1)
{
if(RS232_haveRequest == 1)
{
RS232_Command();
}
}
}
|
and the code for get_string()
Code: |
void get_string(char* s, int max)
{
unsigned int8 len;
int16 timeout = 0;
char c;
len=0;
do
{
// timeout++;
if(kbhit())
{
c=getc();
if (len<=max)
{
s[len++]=c;
}
}
}
while(c!=13 && timeout < RS232_timeout);
s[len]=0;
}
|
This needs some more work to test for a few more "error conditions", but more or less works. Looking at it again I guess all i have done is made some form of a software flow control.
Do you all think this is OK to do it this way?
Thanks
Mark |
|
|
languer
Joined: 09 Jan 2004 Posts: 144 Location: USA
|
|
Posted: Mon May 07, 2012 1:37 pm |
|
|
You can do it (as was suggested) either on the ISR or the Main. You already set a flag; so if you processed on the ISR you would be looking for "$" when the flag is not set; and for "#" if the flag is set.
You truly only need the ring buffer if you need the ability to send and receive data at undetermined times. If you can control when you're receiving data; or if the particular application allows you to collect the data, process it, and send whatever you need out before the next reception of data occurs, then you do not need the ring buffer. For example, if you had serial communications and had hardware handshaking, you may not need the ring buffer (you could have some use for it - but probably not require it). |
|
|
ezflyr
Joined: 25 Oct 2010 Posts: 1019 Location: Tewksbury, MA
|
|
Posted: Mon May 07, 2012 2:10 pm |
|
|
Hi,
It seems to me that you are totally missing the point of the serial interrupt, namely that it fires automatically, and can be used to retrieve a single character from the hardware UART without you even having to think about it.
I really don't understand your concern about "but then I would have to split the data up later"? What's that supposed to mean? If all the commands arrive in the same command packet, you're going to have to do some splitting anyway. Besides, that is a trivial exercise.....
I would implement a 'linear buffer' for this project. Basically, as soon as the ISR receives the '$' character, stuff it into buffer location 0 (zero). Then increment the buffer index, and store the character in the buffer for each new character until the '#' is received. At this point, you've got the complete command string, so you can set a global variable called 'CMD_Ready' to True to signal the main program that you've got a new command string to process. Now, turn off the INT_RDA interrupt, process the command string, and then re-enable the interrupt.
I've been using this simple, bomb-proof method for years with my PICs.
John |
|
|
Markdem
Joined: 24 Jun 2005 Posts: 206
|
|
Posted: Mon May 07, 2012 5:39 pm |
|
|
Thanks for the reply guys.
All I wanted to confirm is if there was am "issues" in not using a ISR buffer. From the sounds of it, I think I should be OK.
When I say "splitting the data" I need each CMD to be in a array. So if i receive it as a string with "," separating the data I would need to then fill the array. I agree it is easy i was just thinking I could skip that step if I just do a get_string which delimits with a CR.
Thanks
Mark |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Tue May 08, 2012 4:01 am |
|
|
Most successful input schemes look for something that *ends* the input, i.e. delimits it, rather than starts it. The best have distinct delimiters at the start AND end.
The basic "get_string()" type of routine is looking for a CR or LF, or both, to mark the end of the string. So you end up doing something after the data has been received in its entirety. So, with your data the conventional way would be to look for the '#' and then raise a flag to say data was present in the buffer. Better still would be to look for the $, then save data until the ISR sees the #, then raise the flag to say there's data.
But you don't want to do that: you want to look for the start of data, then get some other entirely separate code read the contents of the message. That make the only function of the ISR to be checking for $, which is most likely to be pointless. Much simpler to check for $ in the main loop and then get and process the data if you've seen it.
Either way the main loop is likely to be able to do nothing else while is receives the message. A long time ago, in the late 50's, this was seen to be a waste of processor power. The cost driver for interrupts may have gone - the PIC, even as limited as it is, is far more powerful than computers of the 50's, and far, far cheaper - but the utility of this approach remains. Buffering data is generally a good thing and is actually simpler once you get your head around it, certainly much more so than any scheme involving turning interrupts on and off, and inputting data in different parts of the code.
Just from a code architechture point of view, its better to do all interaction with a particular piece of hardware in one place. In other words, the only code that reads from the UART should be, well, the ISR. This is the basis for drivers and hardware abstraction. It make it much simpler and understandable for anyone maintaining your code. So much so that if I was a maintainer and was given code that did the sort of thing you, and so many others propose, I'd change it to a straightforward fully buffered scheme, just to make it more maintainable.
It doesn't matter much what sort of buffering: circular/riing, linear or double buffer/"ping-pong". I'd generqally choose something that allowed my code to receive a new message/packet/command/line/whatever while processing the last, so I'd favour double buffer, or my personal favourite, circular buffer of at least twice the size of the longest possible packet.
There are other aspects to this. You should be considering what might happen if something goes wrong. What if the sending code sends too many commands, or doesn't snd a #, or gets its commas in a twist (do you really mean to send leading and trailing commas as in your example?). How will your code deal with errors? "Real" code should always deal with errors gracefully. No crashes, no lock-ups, no having to send some strange character sequence to unblock it, no "going off into the weeds". That may sound like overkill, but it can't be considered even "satisfactory" code unless it has this level of robustness. To be "good" in my opinion it would need to be elegant and efficient as well as robust. In other words from a maintainer's point of view, there's nothing to do. It works, and works well and never needs changing.
Reading the original post again, it seems the task is:
'$' signals start of data.
commands are delimted by ','. Individual commands should be acknowledged by '$'.
'#' ends the data.
Is this in fact what you want the code to do? If so, is the acknowledgement for the string, or for the command? In other words does it say "Yes, I heard that command" or "Yes, I heard, and understood that command" or even, "Yes, I heard, understood and acted on that command correctly."
What happens with syntactically corrent but otherwise meaningless commands? Do you have some sort of "Received, but NOT understood" response? Thiis can, and on many systems does go much further, with all sorts of distinct acknowledgements/negative acknowledgements. That complexity was realised long ago and communications protocol were devised that allowed the separation of the sending and receiving of data from the processing and acting on the data. In other words comms protocols don't care what the data is, it's job is to get it to the right place, intact and in time. In the PIC CAN is like that, its a comms protocol, and transmits a lot more than just your message. You can send what you like, its the CAN's job to get it there.
Personally I'd always do this sort of thing with buffering and use and ISR to get all data, Maybe it might do the very basic framing, i.e. looking for $ and # and only buffering if they've been received. I would probably define my serial protocol more robustly (for example what happens in you scheme if a command contains #, or even $?). My preferred construct would be a state machine to decode packets. That may be in the main code, but I've done it in the ISR for well organised protocols, which I hope mine are. WIth an ISR state machine packet decoder, I only flag a packet for processing that's correctly formatted and with good CRC (I generally use CRCs as standard to ensure good data integrity).
"Is this OK, or is it just rubbish programing?" Err, sorry but from the above you may be able to tell that from my perspective it's just rubbish programming, or to be fair, inexperienced programming.
RF Developer. |
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Tue May 08, 2012 8:59 am |
|
|
With Rs232 a char can and will be received at anytime unless it is prevented by some sort of synchronization EX hardware handshaking. Without synchronization the code must be able to receive a char even if in the middle of executing a line of code in main. In this case unless main is really very very short so it can loop back fast enough to get any inbound char an ISR is necessary. Strings of chars that burst in via rs232 will need buffering.
Since the buffer can't be ridiculously large some management is needed.
The ISR feeds the buffer and the main routine consumers the buffer. A ring buffer is a popular way of managing this. As long as the main routine on average can consume the buffer as fast as it fills the tail wont catch up with the dog. Now what gets in the buffer maybe important. A GPS squawks several sentences but maybe only one is of interest. In that case an identifier for the wanted sentence is needed both at the beginning and at the end. The end can be generic ( all sentences could use it) but the beginning needs to be unique often several chars like $GPxxx where xx is the one wanted. This is often implemented in a strike out type code within the ISR. The ISR is called everytime a char arrives so upon seeing $ a flag is incremented from zero if the next call of the isr sees the flag as 1 and the char is G another increment occurs and so forth with Pxxx if at anytime the flag can't be incremented it resets back to hunting for the next $ otherwise it is the sentence wanted and it is gated into a buffer by the ISR until the end of sentence is reached and the main routine is told by a flag a sentence is ready. In short the ISR has a very very small FSM routine inside it. The buffer could be a ring that can contain at least two sentences the one coming in and the one being consumed or just simply two strings the one being written and the one being read with a switch to say which one can be read and which one can be overwritten. If performance permits and RAM space is an issue the buffer can be read almost as soon as a char has been inserted in which case the ring buffer allows the dog to almost catch its tail and buffer can then be small enough just to prevent any possibility of overflow. |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Tue May 08, 2012 12:58 pm |
|
|
Cant you just use the ring buffer normally and then search for the string your looking for?
Code: | STRSTR(Buffer, String); |
Something like:
1) clear buffer.
2) poll buffer index. (or set a flag on $ and another on #)
3) did it change? "no" go back to 2 else go to 4
4) search substring (see function stated above)
5) if found set flag...
6) ... you get my point...
.
.
n) repeat.
.... you have start and end characters and comma separators...
you could count the commas, as well.
also to use string functions on your ring buffer you could make it like this:
Code: | Char Buffer[101];
Buffer [100]='\n'; |
and make it loop back on index 99
so its always Null terminated...
G. _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
Markdem
Joined: 24 Jun 2005 Posts: 206
|
|
Posted: Tue May 08, 2012 5:17 pm |
|
|
Thanks for everybody reply's.
Some excellent points have been raised and I most certainly have taken them on board. From what i have read I will just use a normal buffing method and then deal with the data when received.
However, one question still lingers in my mind;
If the PIC is doing nothing until a command is received, and there is no chance that another command will come in while the PIC is working on the last command, would it be OK to use the sort of thing I have below?
I only ask to clear up my understanding (or lack of).
Thanks |
|
|
Gabriel
Joined: 03 Aug 2009 Posts: 1067 Location: Panama
|
|
Posted: Thu May 10, 2012 12:41 pm |
|
|
You can have a buffer thats large enough to take on more than one command.
Or you can make a temporary copy of the buffer and work off of that and then compare it to the original...and see if there are any changes and make a new temporary copy and so on...
The ring buffer will contain only the latest elements received... when it gets full it will start to over write the oldest content.
Since its interrupt base, yes you can very well receive new information while processing a command.
If you don't want to receive anything while working, you could use flow control.
Or set up you GPS(or device) to only give information when you request it.
G _________________ CCS PCM 5.078 & CCS PCH 5.093 |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|