From: Frank Buss on
Fevric J. Glandules wrote:

> Yes, I think my switch / case thing is headed for /dev/null.
>
> Thanks to all for the pointers. I'll be looking into some sort
> of command table / tree in due course.

If your protocol is simple, e.g. "command arg1 arg2 ..." newline "command
arg1 arg2 ...", I would just start with something like Rocky described:

void hello(void)
{
// you can parse command arguments here
printf("hello ");
}

void world(void)
{
printf("world!");
}

typedef void(*CommandFunction)(void);

struct Command {
char* name;
CommandFunction function;
};

struct Command commands[] = {
{ "hello", hello },
{ "world", world }
};

void evaluate(char* commandName)
{
int i;
for (i = 0; i < sizeof(commands) / sizeof(struct Command); i++) {
struct Command* command = &commands[i];
if (strcmp(command->name, commandName) == 0) {
command->function();
return;
}
}
printf("command not found: %s\n", commandName);
}

int main(int argc, char** argv)
{
evaluate("hello");
evaluate("world");
evaluate("foo");
return 0;
}

If this is too slow, sort the commands alphabetically and use a binary
search.

For most projects C18 produces code which is fast enough. Don't worry about
speed until it is too slow :-) E.g. if you have a 100 ms measure cycle, it
doesn't matter if a function needs 100 us or 1 ms and writing code which is
easy to read and to maintain is more important.

--
Frank Buss, fb(a)frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Joe Chisolm on
On Sat, 12 Sep 2009 22:26:57 +0000, Fevric J. Glandules wrote:

> Hans-Bernhard Bröker wrote:
>
>> Fevric J. Glandules wrote:
>>
>>> Let me guess - you live in a 32 bit world, right? <grin>
>>
>> Erm ... no.
>
> Apologies. I based my assumption on:
> switch((u16)buf[0]<<16 + (u16)buf[1]<<8 + (u16)buf[2]<<0)
>
> Is this 8-bit PIC friendly? As I said, I'm trying to get myself (back)
> into the 8 bit world. So I really don't mind (much) having my ignorance
> pointed out.
>

The C18 compiler will compile your construct but will generate a lot
of instructions to handle it. The PIC architecture does not have a
bunch of general registers for C18 to use for register allocation, it
only has an accumulator (the W reg) and a few other registers you can
use.

>>> This is an 8 bit chip, and from what I've seen of its output, the C18
>>> compiler isn't all that brilliant [2].
>>
>> Well, I won't be held liable for you having bad tools.
>
> Well, I'm stuck with it. Unless the C18 is *so* bad that I can make a
> case for the client spending more money. Any opinions here on how the
> C18 measures up?
>

The free C18 compiler does not do sub-expression elimination and some
other common optimization techniques but does generate reasonably tight
code. It wont beat carefully crafted assembler but I know of only
3 or 4 compilers I've ever run into that could beat hand generated
assembler for code density. You will generally find that the 2K SRAM
is enough. I've had some pretty big projects using serial, USB,
driving LEDs and relays along with a bunch of other stuff and not
used more than 1000 to 1200 bytes of SRAM.
The IDE has some tools to help you watch memory usage.

>> If you really can't trust your C compiler to generate efficient code
>> for a sparse switch on a non-minimal integer type, and since you appear
>> to be allergic to any and all code generators, go ahead and code it
>
> I am in no way allergic to anything that will save coding time - but I
> do worry about how much code space, RAM and CPU cycles general purpose
> tools will consume.
>
>> yourself: binary search in a table of {command_string,action_function}
>> structs, sorted by command_string. Points will be deducted for using
>> any standard library function like bsearch(), of course. ;-)
>
> Happy to lose points if I gain time!

Lookup tables, constants, etc can be forced into flash locations to
reduce runtime SRAM requirements. But you suffer a hit having to do
a flash read. Since you are new to PIC, carefully study the manual
regarding the various run time libraries, pay close attention to
the string functions, memory functions and printf. There are
different flavors depending on if the source (or destination)
is in flash.

I have also found that the default IDE libraries are compiled for 24 bit
pointers and you suffer a hit with this. They work but you have to
make sure your code memory model matches the library's LARGE model.
Anytime I load the IDE I recompile the libraries with the SMALL memory
model which gives me 16 bit pointers. This works for all the low end
PICs that have less than 64K of flash.


--
Joe Chisolm
Marble Falls, Tx.
From: Paul Keinanen on
On Sat, 12 Sep 2009 16:36:55 GMT, Tauno Voipio <news(a)sem.pp.fi> wrote:

>
>DO NOT use MODBUS. The protocol has tight timing requirements
>for the characters in a packet and for the intervals between
>packets. It is practically impossible to create a MODBUS program
>with proper timing running in a PC (Windows or whatever).
>
>Yes, there are MODBUS programs for PC's, but I have not seen
>yet one with the timing according to the bus specifications.

In practice, accurate timing is only required in multidrop slaves.
Multiple PCs are seldom used as slaves on the same RS-485 bus.

When the PC acts as a Modbus master or a point-to-point slave, the
connection works well with conservative timing, although there might
be a few performance in throughput.

Paul

From: Paul Carpenter on
In article <1lzcgh2brzgjb.18skbxt6uqpxo$.dlg(a)40tude.net>, fb(a)frank-
buss.de says...
> Fevric J. Glandules wrote:
>
> > Yes, I think my switch / case thing is headed for /dev/null.
> >
> > Thanks to all for the pointers. I'll be looking into some sort
> > of command table / tree in due course.
>
> If your protocol is simple, e.g. "command arg1 arg2 ..." newline "command
> arg1 arg2 ...", I would just start with something like Rocky described:
>
> void hello(void)
> {
> // you can parse command arguments here
> printf("hello ");
> }

I tend to use a command function that returns an integer/byte (depending
on architecure). Reason being if you return an error code you can do some
common error reporting for the parsing such as using return values for

0 Success
1 first argument missing
...
'n' max argument number missing
0x81 first argument invalid
0x8n max argument invalid
0xFF hardware failure

I normally make the first command tried a 'help' command to list in short
form valid commands accepted, and two other commands to return strings of
product name and software version. These of course normally return no
errors. This way you prove the structure of the code before proving the
more complicated commands.

.....
> void evaluate(char* commandName)
> {
> int i;
> for (i = 0; i < sizeof(commands) / sizeof(struct Command); i++) {
> struct Command* command = &commands[i];
> if (strcmp(command->name, commandName) == 0) {
> command->function();

or error = command->function();
// insert common argument function error handling here

> return;
> }
> }
> printf("command not found: %s\n", commandName);
> }

--
Paul Carpenter | paul(a)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
From: ChrisQ on
Frank Buss wrote:
> Fevric J. Glandules wrote:
>
>> Yes, I think my switch / case thing is headed for /dev/null.
>>
>> Thanks to all for the pointers. I'll be looking into some sort
>> of command table / tree in due course.
>
> If your protocol is simple, e.g. "command arg1 arg2 ..." newline "command
> arg1 arg2 ...", I would just start with something like Rocky described:
>
> void hello(void)
> {
> // you can parse command arguments here
> printf("hello ");
> }
>
> void world(void)
> {
> printf("world!");
> }
>
> typedef void(*CommandFunction)(void);
>
> struct Command {
> char* name;
> CommandFunction function;
> };
>
> struct Command commands[] = {
> { "hello", hello },
> { "world", world }
> };
>
> void evaluate(char* commandName)
> {
> int i;
> for (i = 0; i < sizeof(commands) / sizeof(struct Command); i++) {
> struct Command* command = &commands[i];
> if (strcmp(command->name, commandName) == 0) {
> command->function();
> return;
> }
> }
> printf("command not found: %s\n", commandName);
> }
>
> int main(int argc, char** argv)
> {
> evaluate("hello");
> evaluate("world");
> evaluate("foo");
> return 0;
> }
>
>

Sorry, but must disagree :-). The problem with the above is that you
still have inband data embedded in the code. The only way to make the
code completely reusable is to separate form and function. In this case,
all the parser needs to know about is the *structure* of the data, not
it's content.

I would rewrite the code to allow this:

eStatus = eParseExec (&sRootCommandNode);

Where sRootCommandNode is the command parser table root and the command
functions are called via the table contents. It might seem a trivial
change, but can have a major impact w/regard to future maintenance and
spec change.

The command parser is completely encapsulated and can be replaced in the
same application if the command format changes. Being encapsulated, with
no global data, it's easy to make it into a library module. After all,
who has the time to rewrite the same stuff over and over again ?.

Taking the time to design good data structures is worthwhile, imo and is
the key to a robust design...

Regards,

Chris