This is a multi-part message in MIME format. ------=_NextPart_000_02EA_01CA4CA7.66E42E80 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit Vitaliy wrote: >> Huh? I really can't imagine why either chunk processing or byte >> stream processing is inherently more "straight forward" than the >> other. Byte stream processing lends itself particularly well to >> multi-tasking. The task >> runs in a infinite loops getting the next opcode byte, dispatching >> to the routine for that opcode, which may then branch further >> depending on command >> options, etc. > [snip] > > Olin, how is this "reusable"? Your "opcode dispatch" logic is > superglued to your UART code. Are you sure you're looking at the same file? Just to be sure I have attached at copy. To be honest, it's been a while since I've been in here, and I didn't remember the exact mechanisms. Also keep in mind this was developed for PIC 16, with some conditional assembly added for PIC 18 later. Since the call stack is readable and writeable on a PIC 18, this can be done more elegantly there, and I have, but since I mentioned this file let's stick with this version and view it in light of the PIC 16 architecture. I should probably start with a overview of the module because it does something pretty obscure (although clever and useful) for a PIC 16, which is to allow a section of code to run as a infinite loop in its own "thread". Actually I call this construct a pseudo-thread because it has restrictions like it can't leave anything on the call or data stack when allowing other code to run. The thread starts execution at CMD_START on line 228, and the top of the infinite loop is CMD_NEXT two lines above. Starting from CMD_NEXT, it gets the next input stream byte using the GETBYTE macro. This is the opcode of a command. It first validates the opcode is within range of the dispatch table, then jumps to the target routine for the command via the dispatch table. This file is a template, meaning it provides the basic structure to make it easy to customize to a particular project. To implement a particular command, you add one line for the opcode in the dispatch table (TBL_CMD at line 329) and write the command routine. The XXX command routine is provided as a example only, and does nothing but show the mechanics of a command routine. Again, this module is a template, meaning parts are intended to be changed for a particular project. The thread magic is hidden inside the GETBYTE macro. This provides the very useful abstraction of appearing to go and get the next input byte when in reality input bytes come in when they come in. What GETBYTE really does is save the thread context and return to the main system code (LOOP_MAIN). It is broken into two pieces, the GETBYTE macro on line 92 and the GETBYTE2 subroutine on line 123 only to minimize the amount of code that is replicated each GETBYTE macro invocation. Logically this can all be thought of as one macro. This particular version of the command processor does assume a little cooperation from the main event loop. A input byte available is one of the events the main event loop handles. The way it handles that is to call CMD_BYTE, which is defined on line 170. This subroutine grabs the input byte, restores the thread context saved by GETBYTE, and restarts the thread. This whole mechanism is "glued" to the UART in only two places. One is on line 172 where CMD_BYTE calls UART_GET to get the next input byte. And the other is in the main event loop when it detects a new input byte is available. It should be pretty obvious that this module could be easily made to work on any input byte stream. Replace the call to UART_GET with whatever the call is to get the next byte from the input byte stream, and the flag in the main event loop that is signalled whenever there is a new byte available, and everything will work just fine. Calling this "superglued" to the UART is grossly unfair and just plain wrong with any reasonable interpretation of "superglued". On several PIC 18 projects I have done a similar command processor that uses a true thread. In this case the interface to the input stream is even easier. The only interface between the input stream and the command processor is a single call that returns the next input stream byte and blocks as necessary until one is available. That call may be UART_GET in one project and MY_FAVORITE_STREAM_GET in another. Having to modify a single line of code to customize a template module to the input stream used in that project is totally appropriate (it is a *template* after all) and certainly doesn't "superglue" the template to whatever example input stream it was written with. >> On a big system, there can be relatively high overhead just getting >> into and >> out of the system call to get data, so getting a bunch of data at a >> time is >> actually more efficient. This isn't the case on a PIC. On a PIC the >> "usual" get-buffer method is less efficient. My main point, >> however, is that with the right mindset you can embrace the byte at >> a time phylosophy and write more efficient and just as elegant, >> reusable, maintainable, cures >> cancer, etc, code as with the other mindset. > > I don't see how. Of course sometimes it's necessary to do it the way > you describe, because you don't have a choice (e.g., you don't have > enough clock cycles or RAM). However, this approach basically forces > you to write code that is inherently incohesive and tightly coupled. You keep saying this but haven't provided any evidence to support your assertion. In my stream processing example the entire interface between the command processor and the input stream subsystem is a means (global flag, call, whatever) to know when a new input stream byte is available, and a subroutine that returns the next input stream byte. On a PIC 18 the requirement is reduced to a single blocking call that returns the next input stream byte. What the heck more do you want? >> It just takes a different way >> of thinking about the whole problem, which apparently isn't easy for >> people that grew up calling ReadFile. > > [insert a counter-dig here] :o) It wasn't meant as a dig. Lots of folks learn computing from the top down where calling ReadFile or some equivalent routine to get a buffer full of input data is the way to do it. This concept gets taken for granted and they forget that there are other ways to look at the problem. If you look carefully most of the time a input stream eventually gets processed a byte at a time anyway, even if chunks of bytes are read from the underlying subsystem. ------=_NextPart_000_02EA_01CA4CA7.66E42E80 Content-Type: application/octet-stream; name="qqq_cmd.aspic" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="qqq_cmd.aspic" ; *************************************************************** ; * Copyright (C) 2005, Embed Inc (http://www.embedinc.com) * ; * * ; * Permission to copy this file is granted as long as this * ; * copyright notice is included in its entirety at the * ; * beginning of the file, whether the file is copied in whole * ; * or in part and regardless of whether other information is * ; * added to the copy. * ; * * ; * The contents of this file may be used in any way, * ; * commercial or otherwise. This file is provided "as is", * ; * and Embed Inc makes no claims of suitability for a * ; * particular purpose nor assumes any liability resulting from * ; * its use. * ; *************************************************************** ; ; Host command processor. The routines in this module interpret and ; process the command stream coming from the host computer via the ; RS-232 link. ; /include "qq2.ins.aspic" extern uart_get ;get next serial line input byte into REG0 extern uart_put ;send the byte in REG0 to the host extern loop_main ;jump back here after handling an event extern_flags ;declare global flag bits EXTERN ; ;*********************************************************************** ; ; Configuration constants. ; first_opcode equ 0 ;first valid opcode in opcodes jump table lbank equ 0 ;register bank for the local state of this = module ; ; Set MSKSAVE. This is the bit mask of all the registers that are to ; be saved accross GETBYTE. The remaining registers will be trashed. ; REG0 will not be saved because the new input byte is returned in ; REG0 by GETBYTE. ; msksave equ regf1 | regf2 | regf3 ; ; Derived constants. ; lbankadr equ bankadr(lbank) ;address within local state register = bank ; ;*********************************************************************** ; ; Global state. All this state is assumed to be in the GBANK register ; bank by other modules. ; defram gbankadr ; ;*********************************************************************** ; ; Local state. ; defram lbankadr ii set 1 ;init register number while ii < numregs if (1 << ii) & msksave ;save/restore this register accross GETBYTE = call ? savereg#v(ii) res 1 ;make save area for this register endif ii set ii + 1 ;advance to next register number endw injump res 2 ;where to jump on next input byte cmd code ; ;*********************************************************************** ; ; Macro GETBYTE ; ; The code in this module gets run whenever a new input byte is = available. ; This macro makes it appear as if the code in this module is a = separate ; thread that goes and gets the next input byte. It saves a restart = address, ; then returns to the main event loop. When CMD_BYTE gets envoked the = next ; time, it resumes execution at the restart address. ; ; This macro therefore appears like a subroutine call that returns = with ; the next input byte in REG0. The register bank assumptions must be ; correct when this macro is used. ; ; From the caller's point of view, REG0 is returned with the new input ; byte, and the registers listed in MSKSAVE are preserved with the ; remaining registers trashed. ; getbyte macro local restart ; ; Set the address at which to restart next time. This is the address ; immediately after this macro. ; dbankif lbankadr movlw low restart ;save where to restart next time movwf injump+0 movlw high restart jump getbyte2 ;to non-replicated code to do the rest ; ; Set the assembler state to indicate the register bank settings. ; The settings are a function of the code in CMD_BYTE that jumps to = the ; restart address. ; restart ;end up here next time CMD_BYTE is envoked dbankis lbankadr ;direct register set for access to local = state ibank? ;indirect bank setting is unknown endm ; ;********** ; ; This section of code is only run implicitly from the GETBYTE macro. ; There is only one copy of this code at a fixed location, whereas ; the GETBYTE code is replicated for every use of the macro. This = code ; is jumped to from GETBYTE to perform as much of the GETBYTE = operation ; as possible that is not unique to each individual invocation of = GETBYTE. ; This reduces redundant code which would otherwise be produced for = each ; GETBYTE invocation. ; getbyte2 ; ; The low byte of the restart address has already been saved in = INJUMP+0, ; and the high byte is in W. Now save the high byte into INJUMP+1. ; The direct register bank is set for access to the local state. ; dbankis lbankadr movwf injump+1 ; ; Save some of the registers locally. These will be restored when ; CMD_BYTE is run next before it jumps to the restart address. ; dbankif lbankadr ii set 1 ;init register number while ii < numregs if (1 << ii) & msksave ;save/restore this register accross GETBYTE = call ? movf reg#v(ii), w ;get the value of this register movwf savereg#v(ii) ;save it endif ii set ii + 1 ;advance to next register number endw gjump loop_main ;back to the main event loop ; ;*********************************************************************** ; ; Subroutine CMD_INIT ; ; Initialize the state managed by this module. ; glbsub cmd_init, noregs dbankif lbankadr movlw low cmd_start ;init to where to start processing next = in byte movwf injump+0 movlw high cmd_start movwf injump+1 leaverest ; ;*********************************************************************** ; ; Routine CMD_BYTE ; ; This routine is jumped to from the main event loop when there is an ; input byte available from UART_GET. ; glbent cmd_byte gcall uart_get ;get the input byte into REG0 ; ; Restore the registers that are preserved accross invocations of the ; GETBYTE macro. ; dbankif lbankadr ii set 1 ;init register number while ii < numregs if (1 << ii) & msksave ;save/restore this register accross GETBYTE = call ? movf savereg#v(ii), w ;get the saved copy of the register movwf reg#v(ii) ;restore the register endif ii set ii + 1 ;advance to next register number endw ; ; Jump to the restart address. The restart address was saved in = INJUMP ; by GETBYTE before it jumped back to the main event loop. ; dbankif lbankadr movf injump+1, w ;get restart address high byte movwf pclath ;set jump address high byte movf injump+0, w ;get restart address low byte movwf pcl ;jump to the restart address ; ; The restart address is jumped to with the direct register bank set = for ; access to the local state. The indirect register bank setting is = unknown. ; ;*********************************************************************** ; ; Routine CMD_NEXT ; ; Process the next command. This routine appears to run as an = infinite ; loop to the code here. It is actually run piecemeal from the main = event ; loop when an input character is available. The return to the main ; event loop is hidden inside the GETBYTE macro. ; cmd_ack unbank ;done with command, ACK it and get next = command cm_nop unbank ;NOP cmd, no operation but acts like normal = command ; ; *** INSERT ACK CODE HERE IF REQUIRED *** ; Code can be filled in here to send an ACK for the command that was = just ; completed. That is useful with protocols that do not use any low = level ; flow control, like XON/XOFF bytes or hardware RTS/CTS lines. In = these ; cases, each command is small enough to fit completely in the UART = input ; FIFO, and the host does not send a new command until the previous = one ; is acknowledged. ; ; Note that the code to send an ACK is commented out in this template. ; It can be left this way if the protocol does not use ACK. PUTBYTE ; and RSP_ACK are defined in the standard project include file = template. ; ; putbyte rsp_ack ;send ACK response to the host cmd_done unbank ;done processing the current command cmd_next unbank ;get and process the next command getbyte cmd_start ;initial restart adr set by CMD_INIT, REG0 = has opcode ; ; The new command byte is in REG0. ; ; Check for within range of jump table entries. ; if first_opcode !=3D 0 ;commands don't start at zero ? movlw first_opcode ;get first valid opcode value subwf reg0, w ;make table offset for this opcode skip_wle ;opcode not before table start ? jump cmd_next ;invalid opcode, ignore it else ;the command table starts at opcode 0 movf reg0, w ;get the opcode, which is also table offset endif ; ; The opcode is not before the start of the opcodes table, ; and the table offset is in W. ; movwf reg12 ;save the table offset in REG12 sublw tbl_cmd_n - 1 ;compare to last valid command ID skip_wle ;command ID is within range ? jump cmd_next ;invalid command ID, ignore it ; ; The opcode is within range of the opcodes jump table. The original ; opcode is in REG0, and the 0-N table entry number in REG12. Now ; jump to the selected table entry, which will in turn jump to the ; routine to perform the selected command. ; ; ; This section is for processors that have one address per ; program memory word, like the 12, 16, and 17 families. ; if adr_word =3D=3D 1 movlw high tbl_cmd ;get table start address high byte movwf pclath ;init jump address high byte movlw low tbl_cmd ;get table start address low byte addwf reg12, w ;make table entry address low byte skip_ncarr ;carry into entry address high byte incf pclath ;propagate the carry movwf pcl ;jump to the selected jump table entry endif ; ; This section is for processors that have 2 addresses per ; program memory word, like the 18 family. The table ; entry number must be multiplied by the addresses per ; word to make the address offset from the start of the ; table. ; if adr_word =3D=3D 2 movlw high tbl_cmd ;get table start address high byte movwf pclath ;init jump address high byte btfsc reg12, 7 ;add entry offset to address high byte incf pclath rlncf reg12, w ;make low byte of table offset andlw b'1111110' addlw low tbl_cmd ;make low byte of entry address skip_ncarr ;no carry into entry address high byte ? incf pclath ;propagate the carry movwf pcl ;jump to the selected jump table entry endif if adr_word > 2 ;no code here to handle this case ? error "No code in the QQ2_CMD module to handle ADR_WORD > 2." endif ; ;*********************************************************************** ; ; Macro COMMAND ; ; Indicate the start of a command routine that is jumped to from the ; commands jump table. This macro allows for setting particular ; register bank assumptions, etc, as the command dispatch code is ; modified. ; ; At the start of a command, REG0 contains the command opcode. The ; remaining registers are undefined. The command processing routine ; should eventually jump to CMD_DONE when done processing this = command. ; command macro dbankis lbankadr ;direct register bank is set for access to = local state ibank? ;indirect bank setting is unknown endm ; ;*********************************************************************** ; ; Command XXX ; ; This is an example command. ; cm_xxx command jump cmd_ack ;done with this command ; ;*********************************************************************** ; ; Commands jump table. ; ; Each entry is the address of the routine to envoke to service that ; particular command. All the target addresses must be within this ; module. The first table entry is for the opcode set by the ; constant FIRST_OPCODE at the top of this module. ; tbl_cmd ;jump table for top level commands jump cm_nop ; 0 - no operation but responds with ACK jump cm_xxx ; 1 - example command tbl_cmd_n equ ($ - tbl_cmd) / adr_word ;number of entries in TBL_CMD end ------=_NextPart_000_02EA_01CA4CA7.66E42E80 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline -- http://www.piclist.com PIC/SX FAQ & list archive View/change your membership options at http://mailman.mit.edu/mailman/listinfo/piclist ------=_NextPart_000_02EA_01CA4CA7.66E42E80-- ******************************************************************** Embed Inc, Littleton Massachusetts, http://www.embedinc.com/products (978) 742-9014. Gold level PIC consultants since 2000.