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.
// 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