Coding the QDECODE command |
||||||||||||
State-driven processing in a custom command(View more Software Techniques or download the command module.) This article provides a detailed tour through the complete coding for a non-trivial custom processing command. Along the way, it also provides a quick introduction to quadrature encoding and how to build state-driven processing in a custom processing command. If you wish, you can view the final code listing. Complete details about using the finished command are documented in the QDECODE Command Reference Page. To build a downloadable binary module, all you need is a copy of the Developer's Toolkit for DAPL from the DAPtools Professional CD — and the compiler of course. If you prefer, you can download a pre-compiled module for testing on your DAP system. Quadrature encoding and decoding The purpose of the Because of the way that the encoder signals are generated, when the device rotates in the positive direction, the pulse sequence observed is:
and this cycle repeats. But when the direction of rotation reverses:
In these sequences, each of the four possible bit patterns has three possible bit changes: first bit changes, second bit changes, or nothing changes. Each has a unique interpretation as a movement forward, a movement backward, or no movement. All of this is summarized in the following state table. // State 1 (Previously L-L) State 2 (Previously L-H) // L-L / State 1 ( +0 ) L-H / State 2 ( +0 ) // L-H / State 2 ( +1 ) H-H / State 3 ( +1 ) // H-L / State 4 ( -1 ) L-L / State 1 ( -1 ) // // State 3 (Previously H-H) State 4 (Previously H-L) // H-H / State 3 ( +0 ) H-L / State 4 ( +0 ) // H-L / State 4 ( +1 ) L-L / State 1 ( +1 ) // L-H / State 2 ( -1 ) H-H / State 3 ( -1 ) Using the state table, each time a new report of the digital port status is received, the bit pattern can be classified as an incremental move upward, or downward, or no change. If there is a change, the table gives the state for analyzing the next change. The accumulation of incremental changes indicates the current angular position. Structuring the QDECODE commandWe will need the following sections in the command code.
The first three sections are declarative: they define what to do but not when to do it. The last section is active: it invokes previously defined actions in the proper sequence. Command identification The command will need to use features of the Developer's
Toolkit for DAPL to run within the DAPL embedded environment, so
it must include the #include "DTDMOD.H" #include "DTD.H" #define BUFFER_LENGTH 256 #define FOREVER 1 The registration process will invoke a callback function. This function must be present to tell the DAPL system the command's name and entry point. This is ugly boilerplate coding, but fortunately there is not very much of it. extern "C" int __stdcall QDECODE_entry (PIB **plib); // Define the system callback function for identifying // the command prior to execution runtime. // extern "C" __declspec(dllexport) int __stdcall ModuleInstall(void *hModule) { return ( CommandInstall( hModule, "QDECODE", QDECODE_entry, NULL)) ; } State processing definitionsFor processing in a state machine table, you need to know: Which is the current operating state? typedef struct st_trans STATE; This is a forward type declaration. We temporarily defer the details about what this element contains. Given the input, which state is next? typedef long int STATE_TRANSITION ( unsigned short, STATE * ); Once again, this is only a data type declaration, and we defer the details for a moment. How should the accumulated count be adjusted? The state transition functions return an integer value that reports the adjustment to apply to the accumulator. State table representationNow we will code the state table, and define how to respond to each possible input pattern. Each state transition function receives access to the current state and the current bit pattern from the digital port. To classify the input combinations that could cause changes in state, define the four possible bit combinations. Also define a mask pattern to help isolate these bits from all of the others on the digital input port. #define STATEMASK ( 0x0003 ) #define STATE1_BITS ( 0x00 ) #define STATE2_BITS ( 0x01 ) #define STATE3_BITS ( 0x03 ) #define STATE4_BITS ( 0x02 ) What to do in each state is decided by a function. Every state will need one of these state transition functions. In addition, declare an artificial transition function for establishing the starting state. static long int STATE0_TRANSITION ( unsigned short inbits, STATE * pState ); static long int STATE1_TRANSITION ( unsigned short inbits, STATE * pState ); static long int STATE2_TRANSITION ( unsigned short inbits, STATE * pState ); static long int STATE3_TRANSITION ( unsigned short inbits, STATE * pState ); static long int STATE4_TRANSITION ( unsigned short inbits, STATE * pState ); Now we can code the state transition functions according to
the state table. A static long int STATE1_TRANSITION ( unsigned short inbits, STATE * pState ) { long int count_update; switch ( inbits ) { case STATE1_BITS: case STATE3_BITS: count_update = 0L; break; case STATE2_BITS: pState->pTransition = &STATE2_TRANSITION; count_update = 1L; break; case STATE4_BITS: pState->pTransition = &STATE4_TRANSITION; count_update = -1L; break; } return count_update; } We can now
review how the state updates work in full detail. The runtime
code determines which state transition function to call by
fetching the function pointer from its The code for the other three transition functions is almost
the same and will be omitted here. You can see this other code
in the complete code listing. Also
omitted here is the coding for the artificial We temporarily overlooked one detail. The state transition functions all presume that the encoder bits are in the 0-1 bit positions. We can prepare for this by defining a small utility function to force this presumption to be true. inline unsigned short get_inbits( unsigned short inval, unsigned short inshift ) { inval >>= inshift; return (inval &= STATEMASK); } Runtime code The rest of the code is for the runtime processing. It begins
when the DAPL system receives a
Local allocations Each task has a completely isolated namespace, so it
does not hurt to define variables with module scope.
Most of the time it is easiest to declare variables
"on the stack" with
void **argv; // list of task parameter handles int argc; // number of parameters in list
PIPE * pDigPipe; // digital port values PBUF * pDigHandle; // buffer control unsigned short * pBufStorage; // storage location
short unsigned in_shift; // bit addressing
long volatile * pOutVar; // accumulator
STATE current_state; int iFetched, iNext; Establishing system connections This is not exactly "boilerplate" code, but it is at least
stereotypical. First, invoke a DTD function to establish a list of
locally-valid pointers, given the list of task parameter handles
provided by the DAPL system when the task starts. There is a
minimum of 3 and maximum of 3 parameters, so we don't need to do
anything further with the argument count argv = param_process( plib, &argc, 3, 3, T_PIPE_W, // pipe to access bit patterns T_CONST_W, // address of bit pair to use T_VAR_L }; // output accumulator Use the returned list of locally-valid pointers to obtain access to the system connections.
pDigPipe = (PIPE *)(argv[1]); pipe_open(pDigPipe,P_READ); pDigHandle = pbuf_open(pDigPipe,BUFFER_LENGTH); pBufStorage = (unsigned short *) pbuf_get_data_ptr(pDigHandle);
in_shift = *(unsigned short *)(argv[2]); in_shift &= 0x000E;
pOutVar = (long volatile *)(argv[3]); State initialization One input pattern from the digital port is required to
initialize the state. Take this value directly from the pipe,
without buffering a block of values in memory. Apply the
*pOutVar = STATE0_TRANSITION ( get_inbits((unsigned short)pipe_get(pDigPipe), in_shift), ¤t_state ); Run-time loopThe run-time loop looks like a continuously-running infinite loop. Samples must be very fast to capture all of the transitions, but the counting can be slightly delayed, resulting in a small backlog of data. Each pass through the loop receives and processes one block of data sampled from the digital port. while (FOREVER) { // Fetch and buffer any available data from digital port iFetched = pbuf_get(pDigHandle); // Process the data block .... The loop is not really continuously executing, however. When
all available input data are processed, the next Within the "process the data block" section of the main runtime loop, values received from the digital port are processed in sequence by invoking the transition function for the current state. The returned increments are applied to update the globally-visible accumulator. Each state transition function prepares the next state automatically, so there is nothing more that needs to be done. The following is the complete runtime loop. while (FOREVER) { // Fetch any available buffered data from digital port iFetched = pbuf_get(pDigHandle); // Process each received sample for (iNext=0; iNext<iFetched; ++iNext) { *pOutVar += (current_state.pTransition) ( get_inbits(pBufStorage[iNext], in_shift), ¤t_state ); } } return 0; This particular command never reaches the dummy Variations As described, this command is what you would want for a
processing configuration in which a The fundamental goal of the command, decoding quadrature encoded signal pairs, remains the same however. The DAPL system allows a single processing command to accept both variants. The details of doing this are not difficult but beyond the scope of this note. You can see how this is done in the code listings provided with the pre-compiled module download. ConclusionsYou have just seen full implementation details of a non-trivial custom processing command. The result is a potentially useful command for monitoring the position of a rotating device or a device driven by rotating gears, decoding quadrature encoder signals without using any specialized quadrature decoding hardware. A useful technique for coding a state machine in software is shown. There are also examples of accessing DAPL system pipes and variables from a custom processing command. Worthy of notice is the fact that no direct hardware control functions are necessary. The coding style mostly avoids advanced features of
C++. There are some reasons for this. Within the microcosm of
one independent custom command, there is not much
incremental benefit from advanced C++ features (classes,
references, templates, overloaded operators, constructors, etc.).
Staying mostly with C-compatible language features makes
development more accessible to embedded systems programmers
familiar with C but not full C++. And finally, some of the
alternative schemes in C++ for representing state machines
are truly awful. Be careful. Making states into
polymorphic variants of a generic state class can cause the
compiler to invoke
Return to the Software Techniques page or download the command module. |