Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


Posts posted by spirilis

  1. I guess one risk I see is, the CC3200 Access Point mode only supports 1 client, and in AP mode you can't also have it connecting to others.  There is a Point-to-Point mode but I'm not sure if it supports multiple "points" (probably not?)


    So you'd need a converged unit with perhaps a CC3200 plus multiple CC3100 WiFi units attached via SPI.  I know Energia won't support that and I'm not sure that the TI SimpleLink WiFi library will support it properly either.


    An ESP8266 solution might help, in that you could have a bunch of ESP8266's talking over separate UART channels to a central MCU which switches/marshals the data and drives the mesh topology.  Each "node" would be a central MCU plus 2 or more "legs" which are separate ESP8266 transceivers which might be engaged in point-to-point comms with a different remote node (though I'm not sure if ESP8266 supports point-to-point, so take that as an "assumption" on my part without full qualification).  I wouldn't use the CC3200 for the central MCU, maybe a Tiva-C series TM4C123 or TM4C129 with lots of serial UART channels.

  2. That is a pretty cool concept.  What type of end-uses do you envision here, such as protocols used/etc?  Will it be intended for remote HTTP requests to e.g. pull web pages from far away webservers (possibly outside of Africa), or is it intended for specific devices that talk over lightweight protocols?

    (the canonical answer is probably "all of the above", but I am curious how well your intended use-cases and requirements have been thought-through)

  3. OK, brief rundown of the "analysis" process for getting started with uDMA.


    A cursory look at the uDMA chapter in the TM4C123GH6PM datasheet:

    Micro Direct Memory Access (?DMA)The TM4C123GH6PM microcontroller includes a Direct Memory Access (DMA) controller, knownas micro-DMA (?DMA). The ?DMA controller provides a way to offload data transfer tasks from theCortex



  4. Ah, yes, that looks like a quirk of the SSI. The default SSI mode apparently adds a delay of 1.5 cycles between bytes to pulse the SSInFss line off.




    I think that calling MAP_SSIAdvFrameHoldEnable(SSI2_BASE) during SSI setup might remove that delay.

    Huh... SSICR1 doesn't have the bit that SSIAdvFrameHoldEnable sets, at least on the TM4C123GH6PM, and check out driverlib/rom.h:

    #if defined(TARGET_IS_TM4C129_RA0) ||                                         \
        defined(TARGET_IS_TM4C129_RA1) ||                                         \
    #define ROM_SSIAdvFrameHoldEnable                                             \
            ((void (*)(uint32_t ui32Base))ROM_SSITABLE[20])

    Sounds like a Snowflake (TM4C129)-only feature.

  5. Email I received:


    "It's here - visit Avnet's new digital portal and receive free shipping!"


    "To help you get started, visit the new site today.  As a token of our appreciation for your continued support and for your business, we're offering free 2 Day Air shipping** on orders placed through April 22nd!


    **Note: Free 2 Day Air shipping is for orders delivered in North America only.  International orders will receive a $15 discount on preferred shipping carrier.  See details.  Promotion valid April 11 to April 22.  Offer ends 11:59 p.m. April 22, 2016."


    (link appears to be http://products.avnet.com/ and I don't see any "code" to redeem the shipping offer.)

  6. Well I don't see anything glaring wrong with the code (edit: other than using float's, although if it doesn't need blistering performance that might still be OK), but I should add if this is MSP430 there's a glitch in the way analogWrite() works where it will do short-cut updates to the PWM registers (without waiting for the period to reset) when you repeatedly analogWrite a port.  The result is glitchy garbage.  What chip (or launchpad) are you using?

  7. Yeah very good Q about the punctuated space between bytes. My guess is the peripheral does this.

    Elaborating, what I meant was the SSI peripheral may have this as a built-in thing... a quick way to find out may be to test different bitrates.  I have this configured for 8MHz presently but I'll try 500KHz, 1MHz, then 20MHz (I think it can do that).


    First, at 8MHz (also it appears it can't do a clean perfect 8MHz since it's dividing binary from 80MHz), the width between bytes is 0.24uS (just checked with the saleae)


    Now, 500KHz:









    (forget the "f" frequency for those last 2, the Saleae software is making the assumption that the space between bytes is part of the waveform)


    The timing between bytes is proportional to the speed, so it's probably a constant # of SPI clock cycles that it uses as a delay.  Unfortunately this does not appear to be configurable.


    You can do up to 32-bit SPI comms with this peripheral, which should provide a constant 32-bit waveform, but I would imagine each 32-bit word would then be spaced by a similarly speed-proportionate amount of time.

  8. Turns out that was only part of what I was missing.


    Also forgot this piece:

    MAP_uDMAChannelAssign( UDMA_CH13_SSI2TX );

    Yeah that might be important.  This configures the UDMACHMAPx registers to assign a particular DMA channel to a particular peripheral trigger that it supports.


    Then we get beautiful, tight, SPI output:





    Note the PF_2 waveform at the bottom, i.e. logic analyzer channel #2.  It transitions up or down every time an IRQ occurs.  Notice how infrequently that is.. relative to the sheer amount of data passing over the wire.  That's the value of DMA.

    In between those IRQ firings, other tasks may run - the SPI gatekeeper task isn't spinning CPU cycles polling the MAP_SSI_Busy() function.  This is implemented by the SPI Gatekeeper task pending on its own task-ISR binary semaphore (which makes the task sleep).  The real "running thread" for SPI I/O is inside dedicated hardware, separate from the Cortex-M4 CPU and separate from FreeRTOS control, that of the UDMA controller itself.

  9. For more clarification, the old Stellaris stuff was named by the original company that made it - Luminary Micro - which was acquired by TI.  So Tiva was probably a name to improve its pronunciation (IIRC a former TI'er on here mentioned TI's sales teams had trouble selling it in Asia due to pronunciation) and also introduce the letters "TI" into the product name.



  10. Okay, taking my first crack at uDMA...


    I do not have the time to properly document everything in this post, I'll follow up later with each and every decision along with what the datasheet and TivaWare Peripheral Driver Library documentation told me.


    For right now, I'll post main.c and explain why I *believe* I DIDN'T see any traffic on the SPI bus-

     * main.c
    #include <FreeRTOS.h>
    #include <task.h>
    #include <semphr.h>
    #include <queue.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/hw_types.h"
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_gpio.h"
    #include "inc/tm4c123gh6pm.h"
    #include "driverlib/gpio.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/rom_map.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/ssi.h" // for SPI
    #include "driverlib/udma.h"
    #include <string.h> // for strlen()
    #include "datastructs.h"
    // Declaring the space for these inside main() causes corruption, so make them global?
    spiTaskParams task1_Params, task2_Params;
    void Task_SPI_Transfer(void *);
    void Tiva_SPI_DMA_Gatekeeper_Init(void);
    void Tiva_SPI_DMA_Gatekeeper(void *);
    int main(void) {
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);  // SSI2 on PB4/PB6/PB7
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // Need this to configure PB for SSI usage
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); // uDMA controller
        while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)) ;
        while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ;
        while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA));
        // Configure PB4, PB6, PB7 as SSI pins
        // Init SSI2
        MAP_SSIClockSourceSet(SSI2_BASE, SSI_CLOCK_SYSTEM); // System CPU clock used
        MAP_SSIConfigSetExpClk(SSI2_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 8000000, 8); // 8MHz SPI, mode 0, 8 bits
        MAP_SSIEnable(SSI2_BASE);  // SPI peripheral is now live.
        // Configure PF_1, PF_2 for visual feedback of DMA processes
        while (!MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) ;
        // Gatekeeper task request queue
        xQueueHandle spiGatekeeperRequestQueue;
        spiGatekeeperRequestQueue = xQueueCreate(4, sizeof(spiRequest_t));  // Up to 4 asynchronously pending SPI requests at a time.
        task1_Params.spiQueue = spiGatekeeperRequestQueue;
        task1_Params.str = "The quick brown fox";
        task1_Params.delay = 0;
        xTaskCreate(Task_SPI_Transfer, "Task1", 48, (void *)&task1_Params, 4, NULL);
        task2_Params.spiQueue = spiGatekeeperRequestQueue;
        task2_Params.str = "bluehash runs a great forum";
        task2_Params.delay = 5;
        xTaskCreate(Task_SPI_Transfer, "Task2", 48, (void *)&task2_Params, 5, NULL);  // Higher priority
        // Create SPI gatekeeper task
        Tiva_SPI_DMA_Gatekeeper_Init();  // Initializes uDMA
        xTaskCreate(Tiva_SPI_DMA_Gatekeeper, "SPI_GK", 64, (void *)spiGatekeeperRequestQueue, 7, NULL);  // High priority
        vTaskStartScheduler(); // Start FreeRTOS!
        while(1) ; // Shouldn't get here.
        return 0;
    void Task_SPI_Transfer(void *pvParameters) {
        spiTaskParams *params = (spiTaskParams *)pvParameters;
        const char *str = params->str;
        const size_t slen = strlen(str);
        xQueueHandle spiQ = params->spiQueue;
        spiRequest_t request;
        // Don't have it generate a new semaphore with every request - FaultISR() eventually triggers, maybe out of memory?
        request.complete = xSemaphoreCreateBinary();
        while (1) {
            request.dataout = (const void *)str;
            request.length = slen;
            request.datain = NULL; // Send-only SPI request, so datain = NULL to signal no reading required
            // Submit SPI request to gatekeeper task, blocking if queue full
            xQueueSendToBack(spiQ, &request, portMAX_DELAY);
            // Block waiting for request.complete to signal SPI request has been fulfilled
            xSemaphoreTake(request.complete, portMAX_DELAY);
            /* If we had other work to do while waiting for our SPI request to be serviced, we could instead use:
             * if (xSemaphoreTake(request.complete, 0) != pdPASS) {
             *     // ... do work ...
             * }
             * However, keep in mind the SPI gatekeeper task needs CPU cycles to run too, and of particular note is how
             * its priority is below ours, so we'd be starving it of CPU until we try the xSemaphoreTake with a >0 delay value.
             * One alternative would be to run the SPI GK at a substantially higher priority (say 7 or 8), but have it Interrupt Driven
             * where an SSI hardware interrupt would trigger upon completing the current FIFO of SPI data so the high priority
             * SPI gatekeeper task isn't hogging CPU all the time, as it's spending most of its time waiting on its IRQ synchronization
             * binary semaphore (there'd be a binary semaphore between the gatekeeper task and its Interrupt Service Routine).
            if (params->delay != 0) {
                vTaskDelay(params->delay / portTICK_PERIOD_MS); // Task blocks for specified amount of time
    /* uDMA-enabled Gatekeeper task.  There's a lot of pieces to this... */
    // DMA Control Structure table must be aligned on 1024-byte boundaries.
    // #define DMA_MAX_SUPPORTED_CHANNELS defined in datastruct.h
    volatile uint32_t DMA_Control[4 * DMA_MAX_SUPPORTED_CHANNELS] __attribute__((aligned(1024)));
    volatile xSemaphoreHandle ssi2_udma_notify;
    const uint32_t udma_ch13_mask = 1 << 13;
    void Tiva_SPI_DMA_Gatekeeper_Init(void) {
        // Init DMA
        MAP_uDMAControlBaseSet( (void *) &DMA_Control ); // All writes to DMA_Control happen via driverlib calls
        // Using channel #13 (SSI2_Tx)
        MAP_uDMAChannelAttributeEnable( 13, UDMA_ATTR_REQMASK ); // Mask SSI2TX IRQ for uDMA's purposes
        MAP_uDMAChannelControlSet( (13 | UDMA_PRI_SELECT), (UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_8) );
        // Tell SSI to allow DMA
        ssi2_udma_notify = xSemaphoreCreateBinary();
        MAP_IntPrioritySet( INT_UDMAERR_TM4C123, 7 );  // low priority
        MAP_IntPrioritySet( INT_SSI2_TM4C123, 1 );  // high priority, max supported while using FreeRTOS syscalls
    volatile uint8_t periph_irq_toggle;
    void Tiva_SPI_DMA_Gatekeeper_IRQ_Handler(void) { // This must be registered for SSI2 Tx
        BaseType_t xHigherPriorityTaskWoken = false;
        // Visually indicate IRQ (also examinable via logic analyzer for timing)
        periph_irq_toggle ^= GPIO_PIN_2;
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, periph_irq_toggle);
        // Check uDMA to see if it's a uDMA request completion that triggered this
        if (MAP_uDMAIntStatus() & udma_ch13_mask) {
            // Signal ssi2_udma_notify if we're done writing
            xSemaphoreGiveFromISR( ssi2_udma_notify, &xHigherPriorityTaskWoken );
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    volatile uint32_t _last_udma_error;
    void Tiva_uDMA_Master_IRQ_Handler(void) {
        _last_udma_error = MAP_uDMAErrorStatusGet();
        if (_last_udma_error) {
    volatile bool did_binsem_take_before_udmachanenable_work;
    void Tiva_SPI_DMA_Gatekeeper(void *pvParameters) {
        xQueueHandle spiRequestQueue = (xQueueHandle)pvParameters;
        // Using SSI2 hardcoded for this example, so no need to provide the SSIx_BASE in pvParameters...
        spiRequest_t currentReq;
        portBASE_TYPE qRT; // xQueueReceive return value
        // SSI2 Tx FIFO ingress
        const volatile void * ssi2_Tx_FIFO_WRITEREG = &SSI2_DR_R; // from tm4c123gh6pm.h
        while (1) {
            qRT = xQueueReceive(spiRequestQueue, &currentReq, portMAX_DELAY);  // Wait for incoming request
            // Service request:
            // Note something we never did implement in previous SPI examples: READING.  We won't implement it here either.  Exercise for the reader.
            // Any thorough "gatekeeper task" for a comms peripheral does need to implement everything it can do or reasonably expect the user to want, however.
            if (pdPASS == qRT) {
                if (currentReq.dataout != NULL) {
                    // Set up uDMA to begin transfer
                    MAP_uDMAChannelTransferSet( 13,                             \
                                                UDMA_MODE_BASIC,                \
                                                (void *) currentReq.dataout,    \
                                                (void *) ssi2_Tx_FIFO_WRITEREG, \
                    // Clear semaphore in case it was given by the last IRQ firing
                    if (pdFAIL != xSemaphoreTake(ssi2_udma_notify, 0)) {
                        did_binsem_take_before_udmachanenable_work = true;
                    } else {
                        did_binsem_take_before_udmachanenable_work = false;
                    MAP_uDMAChannelEnable(13); // BOOM! Off to the races.
                    // Pend on binary semaphore for SSI2Tx IRQ to signal uDMA completion
                    xSemaphoreTake(ssi2_udma_notify, portMAX_DELAY);
                // Done; signal binary semaphore currentReq.complete
    // Stack overflow protection
    volatile signed char *stackovf_ptrs[8];
    void vApplicationStackOverflowHook(TaskHandle_t *pxTask, signed char *pcTaskName) {
        int i=0;
        for (i=0; i < 8; i++) {
            if (stackovf_ptrs[i] == NULL || stackovf_ptrs[i] == pcTaskName) { // ensure we record a task only once (return; is important)
                stackovf_ptrs[i] = pcTaskName;  // pointer to task name string, which should be easy to identify in the debugger

    The problem becomes evident when you look at the debugger window:



    The uDMA Control Structure format is four 32-bit entries:

    Pointer to Source Address

    Pointer to Destination Address

    Control word (config for this channel)



    The pointer to dest is in the peripheral space, specifically SSI2_DR (the correct write location for SSI2's TX FIFO).  But look at the source location:




    That's in Flash space.  Guess where uDMA can't touch ... It has no bus access to the Flash memory, only SRAM (and peripherals).  Next up, once I have more time, moving those SPI transfer strings into an in-SRAM memory buffer and trying it again.  This should only require modification of main() (along with a global SRAM buffer) and modifying the pointer provided to the pvParameters->str entry for the Task_SPI_Transfer tasks.

  11. Thanks for the help ,


    I have added a sleep(1000000); into the loop to put the MSP in to low current mode because if i add any form of LPM3 the tick ISR stops, do you think this is ok.

    Yeah, that's using Energia's sleep feature as intended... still a little curious why straight LPM3 isn't working but who knows (besides millis not incrementing properly?)

  12. Yes, so long as you use CCS's Energia integration - that is, make an Energia project inside CCS through its special way of doing so - and then use TivaWare libraries inside of that for stuff Energia doesn't already cover (e.g. if you use Energia's Serial interfaces, don't use the TivaWare driverlib UART stuff, likewise if you use the Energia SPI class, don't use the SSI driverlib functions).

  13. Okay ... next up, time to sit back with my tablet and the Tiva-C datasheet learning about uDMA.  I really don't want to get *too* much further into this without having a handle on uDMA and what it's about.  I understand (or vaguely intuit from past readings) that DMA is generally supposed to be a godsend for O/S processing models.

  14. Allrighty... got a handle on things here.


    Indeed it was insufficient memory, and FreeRTOS's options for investigating these matters is decent enough.


    They mostly reside around a hook function you can define (vApplicationStackOverflowHook()) which is required based on the value of #define configCHECK_FOR_STACK_OVERFLOW in your FreeRTOSConfig.h:



    #define configCHECK_FOR_STACK_OVERFLOW (1 or 2)
    Executes vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName ) - supplied by the user
    Method 1 (#define set to 1):
    A task's entire execution context is saved onto its stack each time it gets swapped out.  It is likely that this will be the time at which stack usage reaches its peak.  When configCHECK_FOR_STACK_OVERFLOW is set to 1, the kernel checks that the stack pointer remains within the valid stack space after the context has been saved.  The stack overflow hook is called if the stack pointer is found to be outside its valid range.
    Method 1 is quick to execute, but can miss stack overflows that occur between context switches.
    Method 2 (#define set to 2):
    When a task is created, its stack is filled with a known pattern.  Method 2 tests the last valid 20* bytes of the task stack space to verify that this pattern has not been overwritten.  The stack overflow hook function is called if any of the 20 bytes have changed from their expected values.
    Method 2 is not as quick to execute as method 1, but is still relatively fast, as only 20 bytes are tested.  Most likely, it will catch all stack overflows; however, it is possible (but highly improbable) that some overflows will be missed.
    * - per include/StackMacros.h, it's actually 16 bytes, not 20?
    My stack size assigned to all 3 tasks was initially 32.  As it turns out, the value needs to be 39 at the minimum (for task1/task2, GK task needs less) to satisfy all tasks' needs.  I'll show you how I discovered (please copy this function all you want):

    volatile signed char *stackovf_ptrs[8];
    void vApplicationStackOverflowHook(TaskHandle_t *pxTask, signed char *pcTaskName) {
    	int i=0;
    	MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1); // red LED comes on indicating stack overflow
    	for (i=0; i < 8; i++) {
    		if (stackovf_ptrs[i] == NULL || stackovf_ptrs[i] == pcTaskName) { // ensure we record a task only once (return; is important)
    			stackovf_ptrs[i] = pcTaskName;  // pointer to task name string, which should be easy to identify in the debugger

    For quick reference, here's what actually happens during context switch with configCHECK_FOR_STACK_SIZE == 1, from FreeRTOS/include/StackMacros.h:

    #if( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH < 0 ) )
    	/* Only the current stack state is to be checked. */
    	#define taskCHECK_FOR_STACK_OVERFLOW()																\
    	{																									\
    		/* Is the currently saved stack pointer within the stack limit? */								\
    		if( pxCurrentTCB->pxTopOfStack <= pxCurrentTCB->pxStack )										\
    		{																								\
    			vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName );	\
    		}																								\
    #endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */

    and for configCHECK_FOR_STACK_OVERFLOW == 2:

    #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) && ( portSTACK_GROWTH < 0 ) )
    	#define taskCHECK_FOR_STACK_OVERFLOW()																\
    	{																									\
    		const uint32_t * const pulStack = ( uint32_t * ) pxCurrentTCB->pxStack;							\
    		const uint32_t ulCheckValue = ( uint32_t ) 0xa5a5a5a5;											\
    		if( ( pulStack[ 0 ] != ulCheckValue ) ||												\
    			( pulStack[ 1 ] != ulCheckValue ) ||												\
    			( pulStack[ 2 ] != ulCheckValue ) ||												\
    			( pulStack[ 3 ] != ulCheckValue ) )												\
    		{																								\
    			vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName );	\
    		}																								\
    #endif /* #if( configCHECK_FOR_STACK_OVERFLOW > 1 ) */

    Method #2 is a stack coloration technique albeit using a single byte, not a pattern, but that's obviously easier to implement (and probably cheaper on CPU) at context-switch time.  I find it odd that Richard's book (the FreeRTOS maintainer) says it requires 20 bytes yet the code clearly only compares four 32-bit integers... so I assume it only needs 16 bytes.


    Using method#2, I've set all 3 tasks (task1 The quick brown fox, task2 bluehash runs a great forum, and the Gatekeeper task) to 36 (words), which would be the original value of 32 plus 4 words to account for the required 16-byte (4-word) buffer in the stack coloration:


    Debugger showing that all three tasks overran their stack:
    Incidentally, despite overrunning their stack, 36 words is enough to make the SPI traffic look correct:
    Method #2 for stack analysis is rather nice I would say since it lets you detect stack overrun with a margin of error and correctly ascertain between-context-switch limits.
    Using a value of 37 for all 3 of the tasks:


    All three tasks ran within or over 16 bytes of their stack.


    Value 38:



    Value 39:



    Jumped ahead, the gatekeeper task quit overrunning at 41:



    Finally the main SPI transfer tasks quit at 43:





    So technically the gatekeeper task requires (41-4 =) 37 words of stack space, and client transfer tasks require (43-4 =) 39 words.


    Testing that with method #1, which doesn't color the stack and doesn't require a margin... everything looks good.


    As a quick tip, when going from configCHECK_FOR_STACK_SIZE 2 to 1, it registered stack violation with sizes 37 and 39 (for GK and transfer tasks).  I forgot to Rebuild Project - thus any time you modify FreeRTOSConfig.h be sure to rebuild the whole project instead of just hitting the build button.  After rebuilding the project it ran cleanly with no red LED (and no entries in stackovf_ptrs).

  15. Are some you could answer one of my next question.   how to setup a 2 or 4 hr Alarm.


    I guess you let me know when you have updated the doc.

    For a consistently reliable 2-4 hour alarm that runs in perpetuity, I would recommend basing it on a 1-second periodic tick interrupt.  The problem with using attachScheduledInterrupt is you constantly have to advance the day counter and that may vary based on what month it is (it won't "roll over" properly if you, say, add 1 day to the 30th or 31st, it'll clamp it at 31 if applicable).



    volatile uint16_t _4hr_ticker = 0;
    volatile boolean dostuff_every_4hr = false;
    void flagFourHourTick() {
      if (_4hr_ticker >= (4 * 60*60)) {
        _4hr_ticker = 0;
        dostuff_every_4hr = true;

    and up in setup() perhaps:

    rtc.attachPeriodicInterrupt(1, flagFourHourTick);

    and your loop() might have a:

    if (dostuff_every_4hr) {
      // ....

    That flagFourHourTick can be as complicated as you need it to be.  If you do have more complex scheduling needs, the attachPeriodicInterrupt() might run a master function which runs several others which all manage their own counters and flag other stuff (e.g. their own dostuff_every_xx)

    Any one of those can run wakeup() to wake the CPU.  The CPU wakes up once the initial handler function (the one passed to attachPeriodicInterrupt) exits.

  • Create New...