EmbeddedRelated.com
Forums

Software architecture using C for mid-range PIC.

Started by Fevric J. Glandules September 9, 2009
On Fri, 11 Sep 2009 10:10:02 +0000, Fevric J. Glandules wrote:

> Joe Chisolm wrote: > >> Many times a switch statement will generate cleaner code than using a >> if/elsif/else > > I've written some code to parse incoming strings; 'AB1' triggers foo(), > 'AB2' triggers bar(), and so on. Is the following sensible? > > // Coded with nested switches in the belief that this will // be far > more code and RAM efficient than a strcmp... > switch (*ptrUartRxBuf++) { > case 'A': > switch (*ptrUartRxBuf++) { > case 'B': > switch (*ptrUartRxBuf++) { > case '1': > foo(); > break; > case '2': > bar(); > break; > } > break; > } > break; > }
Depends on the communications flow. If the commands occur anywhere in the buffer and you have an ISR filling the UartRXBuf then things get complicated. If the flow is a simple command/response and the commands will always be at the beginning of the buffer, then simplify life. UartRxBuf will be static, hopefully in access memory. If this can be the case then use something like: switch(UartRxBuf[0]) case 'A': switch(UartRxBuf[1]) ... Of course, part of this decision is if you are speed or memory size constrained. If you are not hitting up against a program memory size limit or not hitting a execution speed limit, then program in a style you like. If you have to start playing tricks, please be nice to the next person who has to look at the code and comment, comment comment - and not crappy comments like // load character from buffer -- Joe Chisolm Marble Falls, Tx.
Bob wrote:

> I'm always amazed at how hung-up programmers get about parsing. If you > have the authority to define the command set, you can make it easy on > yourself by having fixed length commands; Even a single character (A - > Z, e.g.) might suffice.
That's probably what I would have done; OTOH at least they are all exactly three characters long.
> A query has a '?' in buf[1] (or whatever your > command identifier length is), and a list of comma separated numeric > arguments is trivial to collect. Since a small project often has no > floating point, the numbers are all integers.
And numbers - whatever they are - are also all exactly three characters long, which helps. E.g. 12.3 volts is "123".
Fevric J. Glandules wrote:

> I've written some code to parse incoming strings; 'AB1' triggers foo(), > 'AB2' triggers bar(), and so on. Is the following sensible?
It can be. It may not be. It all depends on the circumstances. How many commands are there? Do you get any chance to change the command names? Is the command set fixed for the foreseeable future or likely to change at a whim? For large-scale applications of such lexical recognition, there are general-purpose tools like regexp, tool generators like lex/yacc, and perfect hash generators.
> switch (*ptrUartRxBuf++) { > case 'A': > switch (*ptrUartRxBuf++) {
I'll second the suggestion to leave the pointer alone. Make those ptrUartRxBuf[0] and ptrUartRxBuf[1]. *ptr++ is a useful idiom sometimes, but not really in this case.
Joe Chisolm wrote:

> Depends on the communications flow. If the commands occur anywhere > in the buffer and you have an ISR filling the UartRXBuf then things get > complicated. If the flow is a simple command/response and the commands > will always be at the beginning of the buffer, then simplify life.
Still up in the air.
> UartRxBuf will be static, hopefully in access memory. If this can be > the case then use something like: > switch(UartRxBuf[0]) > case 'A': > switch(UartRxBuf[1])
I was going on something I'd remembered from this: http://www.microchipc.com/conference/PIC18Fxxx_comprehensive_tutorial_containing_7Mb_of_info.pdf but now you've made me go back and check my facts it seems that array indexes should only be avoided (and pointers used instead) for "large" (>256 byte) arrays. Or perhaps the last line on page 64 really does mean that pointers are /always/ more code efficient than array indexing. Either way, I really shouldn't be thinking about these issues at this stage at all! Premature optimisation and all that...
> style you like. If you have to start playing tricks, please be nice > to the next person who has to look at the code and comment, comment > comment - and not crappy comments like // load character from buffer
+1 to that.
Hans-Bernhard Br�ker wrote:

> Fevric J. Glandules wrote: > >> I've written some code to parse incoming strings; 'AB1' triggers foo(), >> 'AB2' triggers bar(), and so on. Is the following sensible? > > It can be. It may not be. It all depends on the circumstances. How > many commands are there? Do you get any chance to change the command > names? Is the command set fixed for the foreseeable future or likely to > change at a whim?
The command set is fixed, and consists of about 30 ASCII triplets, and with any luck won't change too much. What I'm doing at this stage is, I'm afraid, asking rather open-ended and general questions with the aim of getting into the right mindset for this level of device / code. I've done embedded stuff with bytes of RAM and with megabytes of RAM; just not with kilobytes. In addition, all that was a few years ago - I've mostly been doing PHP, mysql, and Linux server admin in recent years.
> For large-scale applications of such lexical recognition, there are > general-purpose tools like regexp, tool generators like lex/yacc, and > perfect hash generators.
Meanwhile I've got a little less than 4K of RAM to play with <grin>.
>> switch (*ptrUartRxBuf++) { >> case 'A': >> switch (*ptrUartRxBuf++) { > > I'll second the suggestion to leave the pointer alone. Make those > ptrUartRxBuf[0] and ptrUartRxBuf[1]. *ptr++ is a useful idiom > sometimes, but not really in this case.
As per the other post, I was going on some Microchip advice to use pointers not indexes - advice I probably misunderstood the first time I skimmed through the hundreds of slides in that PDF. Thanks to all, again, for the responses, questions and suggestions. And if anybody here is struggling with the DMA engine on a PPC403, I may have some tips for you <grin>.
Fevric J. Glandules wrote:
> Hans-Bernhard Br&#4294967295;ker wrote: >> Fevric J. Glandules wrote:
> The command set is fixed, and consists of about 30 ASCII triplets, and > with any luck won't change too much.
Encoding a mere 30 commands as three-letter tokens is excessively redundant. I would strongly suggest to abbreviate further, to one or two letters. In the process you would both reduce your communication load and speed up the interpretation.
> Meanwhile I've got a little less than 4K of RAM to play with <grin>.
You could still use a perfect hash function. That would basically amount to letting a machine condense your command set to single letters, in whichever way it liked. Or you could combine all three letters into a single number, then switch() directly on that: switch((u16)buf[0]<<16 + (u16)buf[1]<<8 + (u16)buf[2]<<0) and let the compiler decide how to implement that most efficiently.
In comp.arch.embedded,
Hans-Bernhard Br&#4294967295;ker <HBBroeker@t-online.de> wrote:
> Fevric J. Glandules wrote: >> Hans-Bernhard Br&#4294967295;ker wrote: >>> Fevric J. Glandules wrote: > >> The command set is fixed, and consists of about 30 ASCII triplets, and >> with any luck won't change too much. > > Encoding a mere 30 commands as three-letter tokens is excessively > redundant. I would strongly suggest to abbreviate further, to one or > two letters. In the process you would both reduce your communication > load and speed up the interpretation. > >> Meanwhile I've got a little less than 4K of RAM to play with <grin>. > > You could still use a perfect hash function. That would basically > amount to letting a machine condense your command set to single letters, > in whichever way it liked. > > Or you could combine all three letters into a single number, then > switch() directly on that: > > switch((u16)buf[0]<<16 + (u16)buf[1]<<8 + (u16)buf[2]<<0) > > and let the compiler decide how to implement that most efficiently.
That's how I implemented a little command interface for setting parameters a while ago. All commands in the form xy=i. Precheck the length and extract 'i' and then assign it based on 'xy' in a switch. if((len > 3) && (buf[2] == '=')) { p = (tolower(buf[0]) << 8) | tolower(buf[1]); i = (int) strtol(&buf[3], NULL, 10); switch(p) { case ('k' << 8) | 'p': set_kp(i); break; case ('k' << 8) | 'i': set_ki(i); break; -- Stef (remove caps, dashes and .invalid from e-mail address to reply by mail) NOWPRINT. NOWPRINT. Clemclone, back to the shadows again. - The Firesign Theater
Hans-Bernhard Br&#4294967295;ker wrote:

> Fevric J. Glandules wrote: >> Hans-Bernhard Br&#4294967295;ker wrote: >>> Fevric J. Glandules wrote: > >> The command set is fixed, and consists of about 30 ASCII triplets, and >> with any luck won't change too much. > > Encoding a mere 30 commands as three-letter tokens is excessively > redundant. I would strongly suggest to abbreviate further, to one or > two letters.
So would I <grin> but the spec says (e.g.) "0x01 0x44 0x45 0x41 0x03" [0] i.e. "start-byte ascii ascii ascii end-byte" and the chip that's talking to me says the same too. I'm not designing [1], I'm implementing. If the spec says "jump" I ask "how high?". Only if the spec says "jump off a cliff" do I raise objections.
>> Meanwhile I've got a little less than 4K of RAM to play with <grin>. > > You could still use a perfect hash function. That would basically > amount to letting a machine condense your command set to single letters, > in whichever way it liked. > > Or you could combine all three letters into a single number, then > switch() directly on that: > > switch((u16)buf[0]<<16 + (u16)buf[1]<<8 + (u16)buf[2]<<0) > > and let the compiler decide how to implement that most efficiently.
Let me guess - you live in a 32 bit world, right? <grin> This is an 8 bit chip, and from what I've seen of its output, the C18 compiler isn't all that brilliant [2]. I think this is very much a case of using C as a "high level assembler" - assume that the compiler will treat everything literally and if it does manage to optimise every now and then, it's a bonus. Footnotes: [0] somebody somewhere got SOH (0x01) confused with STX (0x02). [1] at that level, at any rate. I have a (relatively) free hand with what happens on "my" chip. [2] I'm far from fluent in PIC assembler, in fact I am trying to avoid spending time on it, but I can read it and get the general idea, based on past experience with other assembly languages.
On Fri, 11 Sep 2009 21:26:43 +0000 (UTC), "Fevric J. Glandules"
<fjg@invalid.invalid> wrote:

>The command set is fixed, and consists of about 30 ASCII triplets, and >with any luck won't change too much.
The command table would consume 30x3 bytes and a jump table based on the command index 30x2 bytes (assuming 16 bit addresses), so the total overhead would be 30x5=150 bytes and perhaps 10-20 bytes for scanning the command table and then performing an indexed jump based on the result. Or is that processor too braindead to perform an indexed jump ? With such a large number of commands, it is more memory space effective (on any regular processor at least) to scan a command table and jump/call an action routine based on the command index than build a case/switch structure on a character by character basis. Anyway, if the commands are generated by a human user, 3 unambigous characters is quite sufficient to create a large command set, in which the user can type in the whole command 3 to INFINITY characters long (which is great for documentation purposes). A command table is also useful if support for commands in different laguages is needed. A switch/case structure would be a mess to maintain for each language :-). Paul
In article <nv9ma5h2ae4h545niqs0kscqkle223n76a@4ax.com>, keinanen@sci.fi 
says...
> On Fri, 11 Sep 2009 21:26:43 +0000 (UTC), "Fevric J. Glandules" > <fjg@invalid.invalid> wrote: > > >The command set is fixed, and consists of about 30 ASCII triplets, and > >with any luck won't change too much. > > The command table would consume 30x3 bytes and a jump table based on > the command index 30x2 bytes (assuming 16 bit addresses), so the total > overhead would be 30x5=150 bytes and perhaps 10-20 bytes for scanning > the command table and then performing an indexed jump based on the > result. > > Or is that processor too braindead to perform an indexed jump ?
I agree with that and have implemented such schemes several times, first time being in 1983 on an 8 bit processor with limited memory.
> With such a large number of commands, it is more memory space > effective (on any regular processor at least) to scan a command table > and jump/call an action routine based on the command index than build > a case/switch structure on a character by character basis.
So much easier to add/remove commands to the set.
> Anyway, if the commands are generated by a human user, 3 unambigous > characters is quite sufficient to create a large command set, in which > the user can type in the whole command 3 to INFINITY characters long > (which is great for documentation purposes).
Extending this to a command line structure becomes a lot easier where you match the first part (upto first delimiter often a SPACE), and get each command function to call common routines to check the additional parameters and convert them.
> A command table is also useful if support for commands in different > laguages is needed. A switch/case structure would be a mess to > maintain for each language :-).
Easier to reuse the code later in another project. -- Paul Carpenter | paul@pcserviceselectronics.co.uk <http://www.pcserviceselectronics.co.uk/> PC Services <http://www.pcserviceselectronics.co.uk/fonts/> Timing Diagram Font <http://www.gnuh8.org.uk/> GNU H8 - compiler & Renesas H8/H8S/H8 Tiny <http://www.badweb.org.uk/> For those web sites you hate