weAut_01 / weAutSys    R 2.2.1
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines
main.c

This source is a test and demo application for weAut_01 and weAutSys. Also, it is a good starting point respectively pattern for the customer's own (productive) software.

Note:
The example file displayed is not the "real" C code.
So, for example documentation (Doxygen) comments are stripped off, but replaced by links to the respective documentation.
// Copyright (c) 2011, 2014 Albrecht Weinert, Bochum *
// $Id: main.c 2 2017-01-25 16:49:03Z albrecht $

#include "arch/config.h"  // for sake of Eclipse (4.2.x)
#include "we-aut_sys/ll_system.h"
#include "pt/pt.h" // for sake of Eclipse (4.2.x; can't handle nest'd includes)

#include "we-aut_sys/utils.h"
#include "we-aut_sys/timing.h"
#include "we-aut_sys/uart0.h"
#include "we-aut_sys/streams.h"
#include "we-aut_sys/log_streams.h"
#include "we-aut_sys/proc_io.h"
#include "we-aut_sys/syst_threads.h"
#include "we-aut_sys/cli.h"
#include "we-aut_sys/system.h"

#if defined(weAut_00) || defined(weAut_01) // have Ethernet and SMC
# include "we-aut_sys/network.h"
# include "we-aut_sys/modbus.h"
# include "uip/uip.h"

# include "we-aut_sys/smc.h"    // small memory card low level functions
# include "we-aut_sys/smc2fs.h" // small memory card file system adaption

# include "fatFS/ff.h"
# include "fatFS/diskio.h"
#endif  // have Ethernet and SMC (small memory card)

#include <string.h>

/* Hint to programmers:
 * Constant (final) strings should be declared in flash respectively
 * program memory (128K on ATmega1284, e.g.) and not in the RAM (16K on
 * ATmega1284). If put to RAM it would occupy the same amount of program
 * memory anyway.
 * To put something in flash memory use PROGMEM or better INFLASH().
 */

/* Our own greeting
 *
 * This will be output by our exemplary \ref appInitThreadF() in
 * initialisation phase two.
 */
INFLASH(char const helloReset[]) =
              "\n This is weAutSys OS  ... just starting   (reset) \n \n";
//             0 123456789.0123456789.123456789.1234 6789.123456789v 12 3

enum autDemo_t { none, count, discoBunny,
                                stepperF, stepperR, stepperW, randStep };

enum autDemo_t autDemo = randStep;

static uint16_t autDemoTime = 44;
static uint16_t autDemoTimeUse = 33; // for randStep demo variable speed
static uint16_t timCntDemo = 3; // demo applications cycle division counter

static struct timer_t testTimer0; // timer for app10msThreadF


/*  Command + explanation: application demo "disco bunny"
 *
 *  This must be a flash string.
 *
 *  Hint: The first word (discoBunny) is the command to be recogsised. Case
 *  does not matter and abbreviations will be accepted (dIsco). The first
 *  string will be part of the list of commands by command help or
 *  help -application  <br />
 *  The full text (with all \ref FOLLOW_UP) will be displayed as command
 *  specific help by e.g. disco -help
 */
INFLASH(char const comDiscoBun[])
         = "discoBunny [t/10ms] Demo is DO as flashing disco lights   \n"
FOLLOW_UP  "  The eight bit digital output (DO) should be connected   \n"
FOLLOW_UP  "  to eight powerful lights or beams of different colours. \n"
FOLLOW_UP  "  They will be actuated in a pseudo random manor to give  \n"
FOLLOW_UP  "  a disco lightning effect. The optional parameter is the \n"
FOLLOW_UP  "  period in 10ms (1..65000); for the disco bunny holds:   \n"
FOLLOW_UP  "  The faster the better! \n  \n\0";

/* Command + explanation: application demo "counter" */
INFLASH(char const comDemCount[])
         = "countDemo  [t/10ms] Demo is DO (digital outputs) count   \n"
FOLLOW_UP  "  The eight bit digital output (DO) should be connected  \n"
FOLLOW_UP  "  to a row of eight lights or LEDs DO[0] being the right \n"
FOLLOW_UP  "  one. They will display a (binary) count 0..256. Every  \n"
FOLLOW_UP  "  32 steps the DO will be disabled. The optional parameter  \n"
FOLLOW_UP  "  is the period in 10ms (1..65000); hence 100, e.g., will   \n"
FOLLOW_UP  "  count seconds.   \n  \n\0";

/* Command + explanation: application demo "stepper motor" */
INFLASH(char const comStepF[])
         = "stepFDemo  [t/10ms] Demo is DO rotate stepper motor forward \n"
FOLLOW_UP  "  The upper four bits of the eight bit digital output (DO)  \n"
FOLLOW_UP  "  should be connected to a four phase unipolar stepper    \n"
FOLLOW_UP  "  motor (in the sequence of forward rotation). The output \n"
FOLLOW_UP  "  pattern will effect a forward rotation in half phase    \n"
FOLLOW_UP  "  steps. The optional parameter is the period in 10ms     \n"
FOLLOW_UP  "  (1..65000); 31 e.g. will give a good seconds hand drive \n"
FOLLOW_UP  "  on some standard 4-phase unipolar stepper motors.       \n"
      "  \n\0";


INFLASH(char const comStepR[])
         = "stepRDemo  [t/10ms] like stepFDemo  but reversed  \n"
FOLLOW_UP  "  Enter stepFDemo -help for details.    \n  \n\0";

INFLASH(char const comStepW[])
         = "stepWDemo  [t/10ms] like stepF and stepR combined   \n"
FOLLOW_UP  "  The stepper motor will be driven forward and backward   \n"
FOLLOW_UP  "  with the same number of steps resulting in a kind of    \n"
FOLLOW_UP  "  windshield wiper motion. Enter stepFDemo -help for details. \n"
      "  \n\0";

INFLASH(char const comRandSt[])
         = "randStepDemo [t/10ms] stepper demo, random speed and direction  \n"
FOLLOW_UP  "  The stepper motor will be driven forward and backward quite  \n"
FOLLOW_UP  "  randomly. The optional parameter is the starting period. It  \n"
FOLLOW_UP  "  will speed up every second (stepFDemo -help for details).    \n"
      "  \n\0";

/*  Command + explanation: application demo "dirlist" */
INFLASH(char const comTest01[])
         = "test01     test command (may be empty)      \n";

/*  Command + explanation: application demo "testSD"
 *
 *  This is like \ref comStepF but weeper (back and forth).
 */
INFLASH(char const comTestMC[])
         = "testMC    [-opt] memory card test / debug command \n"
FOLLOW_UP  "  The command is experimental.  \n \n\0";

/* User command: set selected inputs to analogue (AI) */
INFLASH(char const comADinputs[])
         = "ADinputs [mask | -off] set/display analogue inputs   \n"
FOLLOW_UP  "  mask is 00 to FF (without 0x) where ones mark the AI \n"
FOLLOW_UP  "  channels. -off is equivalent to 00 turning all ti DI. \n  \n\0";
/*  User command: set  all (8) inputs to digital (DI) */
INFLASH(char const comADoff[]   )
         = "ADoff    set all inputs digital              \n";


/*  The application defined commands (+ their short explanation)
 *
 *  This is a flash array of pointers to flash strings.
 *  Index 1 .. n must point to all n user commands described above giving
 *  them thus both sequence and (case label) numbers. First (0) and last (n+1)
 *  element must point to an application command headline respectively NULL
 *  as in this example.
 */
INFLASH(char const * const userCommands[]) = {
      helpUserCm, // 0 is no command, is acts always just as separator text
      comADinputs, comADoff, // 1 2
      comDiscoBun, comDemCount, // 3 4
      comStepF,  comStepR,  comStepW,  // 5 6 7
      comRandSt,           // 8
      comTest01, comTestMC, // 9 10 (experimental)
      NULL};  // NULL must be here as end marker

INFLASH(char const weAreAt[]) = " + + address ";

/* Bad hack, experimental
 *
 * This is a proven [sic!] way to avoid the stupid AVR linker error,
 * "relocation truncated to fit ...". One has to experiment with
 * a) if or not and b) with a successful size (like 500).
 *
 http://stackoverflow.com/questions/8188849/avr-linker-error-relocation-truncated-to-fit
 */
#if defined(arduinoUno)
INFLASH(char const  pad[500]) = { 0 };
#endif

// test field :
extern uint8_t  _end;
// extern uint8_t  __stack;
uint16_t const  ramVarsEnd = (uint16_t)(&_end);
// end test field

/*  The user / application specific initialisation thread
 *
 *  Declared and described in syst_threads.h
 */
ptfnct_t appInitThreadF(struct pt* pt){
   PT_BEGIN(pt);

//---  Phase 1: pre system init  ---------------------------------------

   // Determine the standard streams (choose one of the following four)
   // initStdStreams(&serStreams); // serial out (UART) for stdio streams
   initStdStreams(&bufLogStreams); // bufLog streams for stdio
   //  initStdStreams(&nulStreams); // use nulStreams as standard streams
   //  bufLogSetConsumer(1); // log stream to UART (0): no consumer set

 // anything else to do here (no clocks are initialised yet)?
 // No, so we just yield to weAutSys to prepare  phase 2
   PT_YIELD(pt); // return from initialisation phase 1. Phase 2 enters below.

//--- Phase 2: post system init  ---------------------------------------

   initTestPins(ON, ON); // we use testpin0 & 1 (and not I²C / twoWire )

// serial communication works now if UART is used for that


/* The first greeting by printf or by uartPutCharsP / uartPutChars or by
   sendSerBytes / sendSerBytes_P in case of bootloader integration.
   This part (alone) could be used as Hello world example.
   Serial output works from phase 2 on; printf  and stdout goes to serial.
   printf and consorts is just good for intermediate tests and demos only
   but strongly deprecated for productive / real time applications.
   Better use  uartPutCharsP or sendSerBytes en lieu de printf.  */

  // printf("Hello, I'm weAutSys! Just starting ... \n\n"); // not recommended

   bufLogSetUART();     // use UART (0) as output for buffered log

#if USE_BOOTLOADER >= 2 // use full bootloader integration
   sendSerBytes_P(LOW_ADD(helloReset)); // this bootloader function uses
   sendSerBytes_P(resetCauseText);     // spin waiting. Do not ever use
   sendSerBytes_P(LOW_ADD(bLF2));     // spin waiting later on. (See below.)
#else                   // else not bootloader provided functions available
   uartPutCharsP(helloReset, 42);  // better 42 end in line 53 with 2 lf
   uartPutCharsP(resetCauseText, 26);
   uartPutCharsP(bLF2, 4);
   while (SER_SENDBUF_NOT_EMPTY); // Spin waiting for USART all sent
#endif
   // Do NOT useabove output method (i.e. spin waiting) anywhere else!
   // ... really ! as weAutSys is a non preemptive runtime (using A. Dunkel's
   // Protothreads). Spin waiting for possibly longer than a handful µs is a
   // crime in that context.
   // It may be tolerable (here) in initialisation, before the real time
   // PLC cycles start. So never ever use constructs like above later!

   PT_YIELD(pt); // return from initialisation phase 2. Phase 3 enters below.

//---  Phase 3: post persistence init  ------------------------------------

#if HAVE_ETHERNET
 // Persistent configuration data were read from EEPROM and (partially)
 // applied already. Those settings might be changed here, before the
 // Ethernet driver (ENC28J60) and stack (uIP) will be initialised.

   // setMACaddP(&...); //  set MAC address here, e.g., if appropriate

   curIpConf.useFlags =  DNS1_MSK | // be DNS client (experimental)
          DHCP_MSK | NTP_CLIENT_MSK; // be DHCP and NTP client
#endif // ethernet

   PT_YIELD(pt); // return from initialisation phase 3. Phase 4 enters below.

//---  Phase 4: post Ethernet init  ---------------------------------------

/* Register the user / application specific threads here:
   10ms, 100ms, 1s (PLC cycles) and command line interpreter (CLI)
   Omitted here is an 1ms application cycle / thread.  */

   registerAppThread(&app1sThread, &app1sThreadF);  // we have 1s cycle
   registerAppThread(&app100msThread, &app100msThreadF); // and 100ms

   // one time initialisations for 100 ms application thread
   // initialise and start the timer used there
   initTimer(&testTimer0, 1851, ms_TIMER_TYPE);    // 1,85 s
   restartTimer(&testTimer0);

   registerAppThread(&app10msThread, &app10msThreadF); // have 10 ms cycle, too
   // one time initialisation for 10 ms application thread
   // timCntDemo = autDemoTime;

   registerAppSerInpThread(&appSerInpThreadF); // we handle UART input for HMI

   /* Register the user CLI we have here */
   registerAppCli(&appCliThreadF, userCommands);  // we have an user CLI

/* Use (also) the UART for CLI in and out by initialising the appSerInpThread
 * structure accordingly. Note the serial input thread (appSerInpThreadF)
 * function and the userCommands already registered (just above).
 */
   initAsCLIthread(&appSerInpThread, &serStreams);

#if HAVE_ETHERNET
/*  Initialise the Ethernet interface usage here: */
   // (stupid) IP demo server app  [1234 conflicts with search agent]
   uip_listen(HTONS(1234)); // start listen on port 1234
   // (simple) echo server app
   uip_listen(HTONS(ECHO_PORT)); // start listen on .. server just to have it

   // enable the use of the (system's) Telnet server app
   uip_listen(HTONS(TELNET_PORT));  // start listen on ... server

   registerAppModFun(appModFun);
   uip_listen(HTONS(MODBUS_PORT)); // start listen on ..  server
#endif //  HAVE_ETHERNET

   PT_YIELD(pt); // return from initialisation phase 3. Phase 4 enters below.

  // bufLogHexHex_P(weAreAt, 12, 0, addrHere());
  // logStackS(14);

//---  Phase 4: post ??may be something in the future??  init
   //  in versions w/o ???? this phase won't be scheduled

   PT_YIELD(pt); // we yield not to get into trouble should phase 4 be added
   // at present (no phase 4) we effectively yield in phase 5 once too often


  // bufLogHexHex_P(weAreAt, 12, 1, addrHere());
  // logStackS(14);

//---  Phase 5: reschedule until ended

   // output the system (greeting) info.
   runOutSysInfoThread(pt, appSerInpThread.cliThrData.systCLIpt,
        appSerInpThread.cliThrData.repStreams);   //  may block more than once

   // This is the place to do all necessary checks and further initialisations
   // especially the time consuming ones before the real time online
   // work starts in the application / user threads registered above

   PT_END(pt) // we never come back to this thread except by reset / restart
} // appInitThreadF(struct pt*)

#if HAVE_ETHERNET
/* UDP implementation
 *
 *  We do not implement any extra UDP protocol here. System software
 *  handles NTP, DHCP etc. So this function just does nothing.
 *  And it hardly should ever be called.
 */
void udpAppcall(void){ }


/*  The uIP event function for the application software
 *
 *  We handle port 7 as just echo.
 *
 *  System software handles Telnet (server) port 23.
 *
 *  Otherwise (in this stupid example) we just handle port 1234 opened in
 *  \ref appInitThreadF(). As it is the only one left we do not even care to
 *  check the port.
 */
void uipAppcall(void){
   if(uip_conn->lport == HTONS(ECHO_PORT)   // Echo protocol
         && (uip_newdata() || uip_acked())) {  // and new (or ack'ed) data
      uip_send(uip_appdata, uip_datalen());
      return;
   } // echo protocol & new data only

   // put further user implemented protocols here
   // if(uip_conn->lport == HTONS(myFunnyPort) handleFunny(); // funny server

   // this (most stupid) example at the end of the chain would handle
   // the port 1234 (listened to above).

   // de facto the following would handle those two events for all ports
   // but (n.b.) system handled port / event combinations will never appear
   // here, as won't ports we don't listen to at all.
   // we don't listen too
   if(uip_newdata() || uip_rexmit()) {
      //        0123456789.12 3
      uip_send("weAutSys OK \n", 13);
   }
} // uipAppcall()

#endif // Ethernet


/*  Exemplary application / user serial input thread
 *
 *  Declared and described in syst_threads.h
 *
 *  This exemplary serial input processing thread will just take a line from
 *  UART input buffer if available and forward it to a command line
 *  interpreter (CLI) thread \ref initAsCLIthread "initialised" to use the
 *  UART as output.
 *
 *  This function may be \ref registerAppSerInpThread "registered" as
 *  appSerInpThread for demo (and is here).
 *
 *  \sa   registerSerInpThread
 */
ptfnct_t appSerInpThreadF(struct thr_data_t * serInpThread) {
   PT_BEGIN(&serInpThread->pt);  // standard first (re-) schedule entry point

   YIELD_FOR_BUSY_CLI(serInpThread);  // let running command execution work

   if ( ! serInpThread->flag  // this flag signals input
         || ( (! uartInRetBufferd() ) // no input
               &&  uartInBufferd() <= (UART_IN_BUF_CAP - UART_IN_SPACE_LIM) )) {
      serInpThread->flag = 0;      // reset Flag and
      PT_EXIT(&serInpThread->pt); // exit
   } // no (new) input to process

   uint8_t readC = uartGetCmdLine(serInpThread->cliThrData.line, LEN_OF_CLITHR_LINE);

   if (readC < 3) PT_EXIT(&serInpThread->pt); // no input line
   setCliLine(&serInpThread->cliThrData, serInpThread->cliThrData.line,
                                  readC); // parse line and prepare CLI

   YIELD_FOR_BUSY_CLI(serInpThread); // start command execution (should end)
   PT_END(&serInpThread->pt) // standard end of thread (no semicolon!)
} // appSerInpThreadF(void)


/*  Exemplary application / user CLI thread
 *
 *  Declared and described in syst_threads.h
 *
 *  This exemplary "command line interpreter" (CLI) function would only be
 *  called (respectively scheduled as thread function) for the
 *  \ref userCommands "user commands" defined
 *  and registered above. \ref systemCommands "System command" will be handled
 *  by system software. Especially the system command "help" would display
 *  the user command defined here when told so. <br />
 *  Notice the trick of having the commands and their short help texts
 *  together in the same structure.
 *
 *  The interpreter's repertoire can easily be extended.
 *
 *  \sa   registerAppSerInpThread
 *  \sa   systemAbort
 *  \sa   uartGetLine
 *  \sa   searchTokenStart
 *  \sa   searchTokenEnd
 *  \sa   searchTokenIn
 */
ptfnct_t appCliThreadF(struct cliThr_data_t * cliThread){
   if ( ! cliThread->commNumb) PT_EXIT(&cliThread->pt); // exit
   // would normally not be called scheduled with no user command
   PT_BEGIN(&cliThread->pt);  // standard begin of thread part

   uint8_t parsedParam = 0;
      switch (cliThread->commNumb) {
         case 1 : // AD inputs
            if (cliThread->optionNumb >= optOffNum
                      && cliThread->optionNumb <= optAbortNum) { // off etc
               goto fallThroughTo2;
            }
            if (cliThread->optionNumb != optNotGivenNum) goto outAIstate;
            parse2hex(&parsedParam, cliThread->line, cliThread->paramStart);
            goto fallThroughTo2; // Eclipse hates missing breaks
            break;              // jumped over for fall through
         fallThroughTo2:       // just to make Eclipse happy
         case  2 : // AD off
            setAIchannels(parsedParam);
         outAIstate:
            if (!cliThread->repStreams) break; // no report streams no output
            // done by sysCLI             PT_YIELD_OUT_SPACE(&cliThread->pt, 29, cliThread->repStreams);

            //             0123456789.123456789.1
            char outS[] = " * AD inputs  : non  \n \n";
            if (aiChannels) {
               if (aiChannels == 0xFF) {
                  outS[16] = 'a'; outS[17] = outS[18] = 'l';
               } else {
                  outS[16] = '0'; outS[17] = 'x';
                  twoHexs(&outS[18], aiChannels);
               }
            } else {
               outS[19] = 'e';
            }
            stdPutS(outS);
            break;

         case  8 :  // randomSt
            autDemo = randStep;
            goto case12_13_Parameter; // -->>>v
         case  7 :  // stepper
            autDemo = stepperW;
            goto case12_13_Parameter; // -->>>v
         case  6 :  // stepper
            autDemo = stepperR;
            goto case12_13_Parameter; // -->>>v
         case  5 :  // stepper
            autDemo = stepperF;
            goto case12_13_Parameter; // -->>>v
         case  4 :  //  countDemo
            autDemo = count;
            goto case12_13_Parameter; // -->>>v
         case  3 :  // discoBunny
            autDemo = discoBunny;
         case12_13_Parameter:   //---------<<<v
            if (cliThread->paramStart != optNotGivenNum) {
               parseWordNum(&autDemoTime, cliThread->line,
                                     cliThread->paramStart); // par * 10ms
               if (autDemoTime < 1) autDemoTime  = 1;
               autDemoTimeUse = autDemoTime;
               timCntDemo = 20; // new cycle after 200 ms
             }
            break;

         case 9: // test01 (experimental)
            if (!cliThread->repStreams) break; // no report streams no command

            //             0123456789.123456789.12345678 9
            char outT[] = " * End of bss(RAM): 0x----  \n";
            fourHexs(&outT[22],(uint16_t)(&_end));
            stdPutS(outT);

#if HAVE_SMC
            if (!lockFsWorkFor(SMC_FS_CLIUSE)) break; // others use FS
            // lock unlock file system structure ... but do nothing at present
            unlockFsWorkFrom(SMC_FS_CLIUSE);
#endif  // HAVE_SMC
            break;

         case 10: //  testMC  [-opt] memory card test / debug
            if (!cliThread->repStreams) break; // no report streams no command
#if HAVE_SMC
            if (!lockFsWorkFor(SMC_FS_CLIUSE)) break; // others use FS
#if defined(weAut_00) || defined(weAut_01)
            fsWork.fRes  = f_open(&fsWork.fil, "/test.txt", FA_OPEN_EXISTING | FA_READ);
            PT_YIELD_OUT_SPACE(&cliThread->pt, 31, cliThread->repStreams);
            PT_OR_YIELD_REENTER(); // yield anyhow after long fatFS op.

            if (!fsWork.fRes ) {
               uint16_t ii;
               char buffer[17];  // was 17
               fsWork.fRes1 = f_read(&fsWork.fil, buffer, 15, &ii);
               buffer[ii] = '\n';
               buffer[ii+1] = 0;
               stdPutS(buffer);
               PT_YIELD_OUT_SPACE(&cliThread->pt, 31, cliThread->repStreams);
               PT_OR_YIELD_REENTER(); // yield anyhow after long fatFS op.
             }



            PT_YIELD_OUT_SPACE(&cliThread->pt, 31, cliThread->repStreams);
            PT_OR_YIELD_REENTER(); // yield anyhow after long fatFS op.
            //              0123456789.123456789.123456789t123456789q
            char outR[] =  " * fil.op.cl: op rd cl  \n";

            if (fsWork.fRes) {
               twoHexs(&outR[14], fsWork.fRes);
             } else {
               if (fsWork.fRes1) twoHexs(&outR[17], fsWork.fRes1);
               fsWork.fRes2 = f_close(&fsWork.fil);
               //  PT_YIELD(&cliThread->pt); // yield anyhow after long op.
               if (fsWork.fRes2) twoHexs(&outR[20], fsWork.fRes2);
            }
            stdPutS(outR);
#endif // weAut_01 | weAut_00
            unlockFsWorkFrom(SMC_FS_CLIUSE);
#endif // HAVE_SMC
            break;

         default:
            break;
      } // switch

      PT_END(&cliThread->pt)
} // exemplary CLI application thread


// user periodic threads (Cycles)  common variables
static uint8_t eKeyWasPressed;  // enter key's past
static uint8_t setHigh;  // how DI thresholds were set on last enter pressed

/*  DO (digital output)  output pattern for disco ("bunny") lights  */
INFLASH(uint8_t bunnyMust[]) = {0x7D, 0x86, 0x6D, 0x43,
                              0x6D, 0xF6, 0x6D, 0x03,
                              0xED, 0x06, 0xED, 0x03,
                              0x6D, 0xB6, 0xCD, 0xDB };

/*  DO output pattern for unipolar stepper motor (upper nipple)
 *
 *  These are 8 steps for a 4 coil unipolar stepper on bits 4..7
 *  of the digital output (DO).
 *  In every step 1 pole (coil) or 2 adjacent poles are on.
 */
INFLASH(uint8_t steppMust[]) = {0x18, 0x39, 0x2A, 0x6B,
                              0x44, 0xC5, 0x86, 0x97, // 0..7
                              0x86, 0xC5, 0x44, 0x6B,
                              0x2A, 0x39, 0x18, 0x97, // 8..F
                              0x86, 0xC5, 0x44, 0x2B,
                              0x1A, 0x89, 0x48, 0x27, // 10 .. 17
                              0x16, 0x85, 0x44, 0x2B,
                              0x1A, 0x89, 0x48, 0x27,  // 18 .. 1F

                              0x16, 0x85, 0x44, 0x2B,
                              0x1A, 0x89, 0x48, 0x27,
                              0x16, 0x85, 0x44, 0x2B,
                              0x1A, 0x89, 0x48, 0x27,
                              0x16, 0x85, 0x44, 0x2B,
                              0x4A, 0x29, 0x18, 0x87,
                              0x46, 0x25, 0x14, 0x8B,
                              0x4A, 0x29, 0x18, 0x97 };
/* Exemplary application / user thread  (10ms)
 *
 *  Has been registered as app10msThread in (above) \c appInitThreadF .
 */
ptfnct_t app10msThreadF(struct mThr_data_t * uthr_data) {
   PT_BEGIN(&uthr_data->pt);
   exitNotFlaggedThread(*uthr_data);
   uthr_data->flag= 0; // work will be done, reset flag. Never forget!

   if  (enterKeyPressed()) { // enter key pressed
         eKeyWasPressed =
              upDIthresh4hyst = 0xFF;  // wider hysteresis all 8 DI channels
         upDIthreshForce = 0;
         toggleStatusLedGn();  // wild blink
   } else if (enterKeyReleased()) { // enter key released
         if (eKeyWasPressed) {
            eKeyWasPressed =
              upDIthresh4hyst = 0x00;  // normal hysteresis for all  DI
            if (setHigh) { // toggle low / high threshold for test
               setHigh = upDIthreshForce = 0;
               setStatusLedGn(OFF); //  // off for less sensitive
            } else {
               setHigh = upDIthreshForce = 0xFF;
               setStatusLedGn(ON); // on for more sensitive
            } // toggle high / low threshold for test
         } // was pressed
   }  // enter key  released


   if (! (--timCntDemo)) {  // counting down til 0
      timCntDemo = autDemoTime; // restart down counter (speed setting)
         // setTestPin1(toggle1 = ! toggle1);

         static uint8_t t1cnt;

         if (! doDriverEnabled()) {
            enableDOdriver(ON);
         } else if ( !doDriverOK() ) {
            enableDOdriver(OFF);
         } else {
            ++t1cnt;

            if (autDemo == count) {   // counter (DO) demo
            // Function: Count this timer's ticks in t1cnt and output this to
            // digital output. At mod 32 = 31 or at DO driver errors disable
            // the digital output driver for one timer tick.
               if ( (t1cnt & 0x1F) == 0x1F) {
                  enableDOdriver(OFF);
               } else {
                  toDOdriver( t1cnt );
               }
            } else  if (autDemo == discoBunny) {  // disco light demo
               toDOdriver(pgm_read_byte(&(bunnyMust[t1cnt & 0x0F])));
            } else  if (autDemo == stepperF) {  // stepper motor forward
               toDOdriver(pgm_read_byte(&(steppMust[t1cnt & 0x07] )));
            } else  if (autDemo == stepperR) { // stepper motor reverse
               toDOdriver(pgm_read_byte(&(steppMust[(t1cnt & 0x07) + 8] )));
            } else  if (autDemo == stepperW) {  // stepper motor back & forth
               toDOdriver(pgm_read_byte(&(steppMust[t1cnt & 0x0F] )));
            } else  if (autDemo == randStep) { // stepper motor random
               toDOdriver(pgm_read_byte(&(steppMust[t1cnt & 0x3F] )));
               timCntDemo = autDemoTimeUse;
            }
         }
   } // counting down til 0

   PT_END(&uthr_data->pt)
} // 10 ms exemplary application thread.


/* Exemplary application / user thread (100ms)
 *
 *  Has been registered as app100msThread (for demo) in (above)
 *  \c appInitThreadF .
 */
ptfnct_t app100msThreadF(struct mThr_data_t * uthr_data) {
   PT_BEGIN(&uthr_data->pt);
   exitNotFlaggedThread(*uthr_data);
   uthr_data->flag= 0; // work will be done, reset flag. Never forget!

   if (timerLapsed(testTimer0)) {  // timer 0 is elapsed
      static uint8_t t0cnt;
      ++t0cnt;
      if (t0cnt & 1) {
         setStatusLedGn(ON);  // blink with timer 0
      } else {
         setStatusLedGn(OFF); // blink with timer 0
      }
      // restartTimer(&testTimer0);  // just from now
      startNextInterval(&testTimer0);  // frequency exact
   } // timer 0 was elapsed

   PT_END(&uthr_data->pt)
} // 100 ms exemplary application thread.

/* Analogue input results hold */
static uint8_t aiResultHold[8] = {0,1,2,3,4,5,6,7}; // Improbable measurement

/* Exemplary application / user thread (1s)
 *
 *  Has been registered as app1sThread (for demo) in (above)
 *  \c appInitThreadF .
 *
 *  This example shows a cycle's subdivision in two cycles of
 *  half the frequency (here odd and even second schedule). Analogue input
 *  (if any channels are set so) is logged on UART every two seconds here.
 */
ptfnct_t app1sThreadF(struct mThr_data_t * uthr_data){
   PT_BEGIN(&uthr_data->pt);
   exitNotFlaggedThread(*uthr_data);
   uthr_data->flag = 0; // work will be done, reset flag. Never forget!

   if (aiChannels) { // output Analogue Channels (AI) if some set (+DI)
      PT_WAIT_ASYIELD_WHILE(&uthr_data->pt, bufLogStreamsOutSpace(NULL) < 122);
       //            01 23456789.123456789v123456789t123456789q12345678 9c
      char line[] = " \n * AI 7..0 : 7:L 6:L 5:L 4:L 3:L 2:L 1:L 0:L  \n";
      uint8_t msk = 0x80;
      char * digOut = line + 43;
      for (uint8_t chan = 7; msk; --chan, msk >>=1, digOut -= 4) {
         if (!(aiChannels & msk)) { // this channel is DI
            if (filDI() & msk) {
               digOut[2] = 'H';
               aiResult[chan] = 0xFF;
            } else {
               aiResult[chan] = 0;
            }
            continue;
         } // this channel is DI
         threeDigsB(digOut, aiResult[chan]);
      } // for
      if (memcmp(aiResultHold, aiResult,  8)) {
         memcpy(aiResultHold, aiResult, 8);
         bufLogTxt(line, 59);
      }
   }  // ADC

   if(!(--autDemoTimeUse)) autDemoTimeUse = autDemoTime;

   // Wait again until its flag set.   Phase odd
   PT_WAIT_UNTIL(&uthr_data->pt,  &uthr_data->flag);
   uthr_data->flag = 0; // work will be done, reset flag. Never forget!


   PT_END(&uthr_data->pt)
} // One second exemplary application thread.

// Other user threads .... --------------------------------------

#if HAVE_MODBUS

union {
   uint8_t v8[16];
   ucnt16_t c16[8];
} poi;

union {
   uint8_t v8[16];
   ucnt16_t c16[8];
} pii;

/*  Handle Modbus server events
 *
 *  This exemplary implementation does not distinguish the (old fashioned)
 *  Modbus data models. It maps all incoming data to a byte oriented
 *  POI (process output image) and all outgoing data to a PII (process
 *  input image). In this simple example the length of both is 16 and the
 *  addresses are just taken modulo 16. As lengths 1 .. 16 is accepted.
 */
ptfnct_t appModFun(struct modThr_data_t * const m){

   uint8_t wbC = m->inDataByteCnt; // no of incoming bytes (to POO)
   uint8_t rbC = m->outDataByteCnt; // no of outgoing bytes (from PIO)
   const uint8_t dMod = m->dataModel;

   #if DEBUG_MOD >= 3
   //           0123456789.123456789v123456789t12 3
   char tS[] = " ## ModbAppFun m- w - r - *.... \n";
   if (dMod) tS[16] = dMod;
   if (wbC) twoHexs(&tS[19], wbC);
   if (rbC) twoHexs(&tS[23], rbC);
   fourHexs(&tS[27], (uint16_t)m);
   bufLogTxt(tS, 33);
#endif

   if (dMod == COIL_SINGLE) { // special case single coil
      // const uint8_t bitNum = m->inDatTarg.v8[0] & 0x07;
      const uint8_t bytNum =  (uint8_t)(m->inDatTarg.v16 / 8);
      if (*m->inData ) { // set
         pii.v8[bytNum & 0x0F] |= setBitMask(m->inDatTarg.v8[0]);
      } else { // set else clear
         pii.v8[bytNum & 0x0F] &= clearBitMask(m->inDatTarg.v8[0]);
      } //  clear
      return PT_ENDED;
   } // special case single coil

   if (dMod ==HLD_MASK) { // special case mask single register
      uint8_t iInd = m->inDatTarg.v8[0] & 0x0F; // mod 16 start add
      pii.v8[iInd] = // for simple test only we put POI to PII
      poi.v8[iInd] = (poi.v8[iInd] & (* m->inData)) | (* m->inData + 2);
      pii.v8[iInd + 1] = // for simple test only we put POI to PII
      poi.v8[iInd + 1] = (poi.v8[iInd + 1] & (* m->inData + 1)) | (* m->inData + 3);


      return PT_ENDED;
   } // special case mask single register


   // as we map all models to one POI respectively PII and accept all
   // addresses as modulo 16 we just have to check lengths
   if (wbC > 16 || rbC > 16  || ( !wbC && !rbC)) {
      m->errorCode = MODB_EXC_DATA; // length is considered as data by Modbus
      return PT_EXITED;
   }

   if (wbC) {  // we must do write (to POI) before read
     uint8_t iInd = m->inDatTarg.v8[0] & 0x0F; // mod 16 start add
     uint8_t * inData = m->inData;
     for(;;){
        pii.v8[iInd] = // for simple test only we put POI to PII
        poi.v8[iInd] = *inData++;
        iInd = (iInd + 1) & 0x0F;
        if(!--wbC) break;
     }
   } // write to POI

   if (rbC) {  // we must do read (from PII) after write
     uint8_t iInd = m->outDatSou.v8[0] & 0x0F; // mod 16 start add
     uint8_t * outData = m->outData;
     for(;;){
        *outData++  = pii.v8[iInd];
        iInd = (iInd + 1) & 0x0F;
        if(!--rbC) break;
     }
   } // read from PII


   return PT_ENDED;
} // appModFun(struct modThr_data_t *)

#endif // Modbus