Reply by Neil September 15, 20092009-09-15
Joe Chisolm wrote:
> On Thu, 10 Sep 2009 21:06:26 +0000, Fevric J. Glandules wrote: > >> nospam wrote: >> >>> PIC18s are 8 bit processors. >> So they are. I've spent enough time with the datasheet and looking at >> example assembler and my own disassembled C code that you'd think it >> would have percolated back to my consciousness by now. Mind, it was >> quite late when I wrote that. > > I have found there are several coding constructs that help the MPLAB > compiler create tighter code. Define a bit field for logical flags. > The compiler will emit BTFSS, BTFSC instructions. If you use a full > byte the compiler will emit a MOVF to W and test instructions. If > you have something like: > flag = 0; > .... > flag = 1; > .... > if (flag != 0) ... > > you can save a couple of instruction words by using flag += 1 instead of > the flag=1. This is because the compiler will generate a INCF instruction > instead of a MOVLW and MOVWF. > > Many times a switch statement will generate cleaner code than using a > if/elsif/else > > The compiler does a lot of work dealing with pointers and pushing > arguments on the stack. > > If I start running low on program memory I'll usually try different coding > constructs and look at the assembly listing to find the best generated > code. >
Or you could use bit vatiables.
Reply by Neil September 15, 20092009-09-15
Vladimir Vassilevsky wrote:
> > > Neil wrote: > >> Vladimir Vassilevsky wrote: >> >> >>> It is so much easier to use a basic time-slicing round robin >>> preemptive multitasker rather then developing the complicated >>> non-blocking state machines. Then, the tasks can be done as the >>> straightforward linear code, and you don't have to worry about blocking. >>> >> In a PIC18 ? > > What's the use of PICs or 8051x misery when AVRs, HC08s and other > processors with the flat memory and the traditional stack are available? > > > Vladimir Vassilevsky > DSP and Mixed Signal Design Consultant > http://www.abvolt.com >
If you can live with out an RTOS. Mature tools, lots of fourms, lots of code samples, A bigger selection of parts, dirt cheap. Sometimes a cheap lame CPU fits the bill.
Reply by Mikael Nordman September 14, 20092009-09-14
Fevric J. Glandules wrote:
> Hi all, > > I am new to this group but checking over recent postings seems > to indicate a fair amount of Clue kicking around [1]. > > A bit of background before y'all tell me to RTFM or JFGI. > > I've got quite a lot of experience in programming 8 bit microcontrollers > with assembler; I've also done quite a lot of 32 bit embedded stuff > using an RTOS with C. What's new to me is using a 16 bit mid-range > PIC18 device in C. > > So I'm trying to make sure that I come up with a fairly sensible > architecture. Googling isn't much help - most of the "tutorial" > stuff out there is aimed at a very low level, or else you find > discussion at the nuts and bolts level of a particular interrupt > handler. > > The application is probably fairly typical - inputs / commands > come in via the UART, peripheral devices need to be turned on > and off in response, other devices need to be queried via I2C > for data, etc. > > My current thinking is to have a main() loop that is subdivided > into separate sections that deal with each peripheral in turn. > > Each code block will be non-blocking, and will implement a > state machine for each peripheral. Incoming comms will run > off interrupts. In RTOS terms, a sort-of round-robin system. > > If, for example, incoming commands are ASCII sequences > like "L11" for "LED 1 on" followed by an end-of-message > checksum byte, in pseudo-code, something like this: > > uart_isr() { // push chars into circular buffer > push_char_to_uart_buf() > if (char == end_of_message) message_flag = 1; > } > > main_loop { > if (message_flag) { > move_chars_from_uart_buf_to_command_buf() > switch (command) { > case foo: foo_state = 1; > case bar: bar_state = 1; > } > } > // foo machine > if (foo_state) { > switch (foo_state) { > case 1: if (foo1()) foo_state = 2; > case 2: if (foo2()) foo_state = 0; > } > // bar machine > if (bar_state) { > switch (bar_state) { > case 1: if (bar1()) bar_state = 2; > case 2: if (bar2()) bar_state = 0; > } > } > > Does that make sense? Should the ISR do as little as possible or > is it a good idea to give it some "awareness"? > > The above pseudo-code is just meant to give an idea of where > I'm heading, so don't take it too seriously. I'm really looking > for general pointers on how to approach a mid-level architecture. > > Oh go on then, rip me to shreds. > > [1] I've also dug out the FAQ. >
Even if you want to use C, may I suggest FlashForth, a 16 bit Forth Operating System for the PIC18/PIC30/soonPIC24. It has an inbuilt command interpreter and compiler. It also has an round robin multitasker allowing you to have background tasks in addition to the operator task. Forth code for I2C is provided as an example. With the compiler you design new command langauges to your liking. These new commands can then interpreted by the interpreter. http://flashforth.sourceforge.net/ -- Mikael
Reply by Paul Carpenter September 13, 20092009-09-13
In article <1lzcgh2brzgjb.18skbxt6uqpxo$.dlg@40tude.net>, fb@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@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
Reply by ChrisQ September 13, 20092009-09-13
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
Reply by Paul Keinanen September 13, 20092009-09-13
On Sat, 12 Sep 2009 16:36:55 GMT, Tauno Voipio <news@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
Reply by Joe Chisolm September 13, 20092009-09-13
On Sat, 12 Sep 2009 22:26:57 +0000, Fevric J. Glandules wrote:

> Hans-Bernhard Br&ouml;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.
Reply by Frank Buss September 13, 20092009-09-13
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@frank-buss.de http://www.frank-buss.de, http://www.it4-systems.de
Reply by Fevric J. Glandules September 12, 20092009-09-12
Hans-Bernhard Br&#4294967295;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.
>> 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?
> 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!
Reply by Fevric J. Glandules September 12, 20092009-09-12
ChrisQ wrote:

> understand. Contrast that with the typical method of deeply indented > case statements implementation, which just becomes unreadable after a > couple of pages :-)...
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.