The best way to program the electric plate reflow control?

What is the best way to program the electric plate reflow control? Using the TCC2 periodic interrupt (1s) I would set the flag in a single FreeRTOS job then test it, measure the temperature and use PID control to calculate the PWM 1s pulse width to control the power of the electric plate via the optocoupler and triac. I would then reset the flag (“false”). I would initiate the reflow when the button is pressed. I have the temperatures stored in an array.

Will there be no loss of synchronization between the TTC2 interrupt and the job?

In the program I am test generating PWM pulses 0, 10%, 20% … 100% after pressing the button.

app.c

#define PERIOD                         65000    // Period-1 For 1s (FDPLL96M 66560000/16/64=65000 (48 MHz divider 75, 103 a 0)
#define DUTY_INCREMENT 10.0

TickType_t xLastWakeTime;
const TickType_t xFrequency = pdMS_TO_TICKS(1000);

// GCLK_0 on PB14
// Core, Stack Size changed from 512 to 1024 for sprintf "%.2f\r\n", PWM_value

// *****************************************************************************
/* Application Data

  Summary:
    Holds application data

  Description:
    This structure holds the application's data.

  Remarks:
    This structure should be initialized by the APP_Initialize function.

    Application strings and buffers are be defined outside this structure.
 */

APP_DATA appData;

const uint8_t temp[235] = {20, 23, 26, 29, 32, 34, 37, 40, 42, 45, 47, 50, 52, 55, 57, 59, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 89, 91, 93, 94, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 112, 114, 115, 116, 117, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 135, 136, 137, 138, 138, 139, 140, 140, 141, 142, 142, 143, 143, 144, 144, 145, 145, 146, 146, 146, 147, 147, 148, 148, 148, 149, 149, 149, 149, 150, 150, 150, 150, 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 153, 153, 153, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 155, 155, 155, 156, 156, 156, 156, 157, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 161, 161, 162, 162, 163, 163, 164, 164, 165, 165, 166, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 196, 197, 198, 199, 200, 201, 203, 204, 205, 206, 207, 208, 210, 211, 212, 213, 214, 215, 216, 217, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 228, 229, 230, 231, 232, 232, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 237, 238};
volatile uint16_t temperature;
volatile uint8_t sec_counter = 0, old_counter = 0;

volatile bool LED_flash = false;
volatile bool Button_pressed = false;

volatile float Duty_percent = 0;

// *****************************************************************************
// *****************************************************************************
// Section: Application Callback Functions
// *****************************************************************************
// *****************************************************************************

/* TODO:  Add any necessary callback functions.
 */

/* This function is called after TCC period event */
void TCC_PeriodEventHandler(uint32_t status, uintptr_t context) {
    static uint16_t PWM_value = 58499;

    if (Button_pressed) {
        TCC2_PWM16bitDutySet(TCC2_CHANNEL0, PWM_value);

        Duty_percent += DUTY_INCREMENT; // 0, 10, 20 ... 100
        PWM_value = Duty_percent / 100 * PERIOD - 1;
        if (Duty_percent > 100) {
            Duty_percent = 0;
            PWM_value = 0;
            Button_pressed = false;
        }
        LED_Toggle();
    }
}

void TC5_TimerCallback(TC_TIMER_STATUS status, uintptr_t context) {
    __NOP();
    //    LED_Toggle();
}

void EIC_EXTINT_15_InterruptHandler(uintptr_t context) {
    //    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* Clear interrupt flag */
    EIC_REGS->EIC_INTFLAG = (1UL << 15);

    Button_pressed = true;
    //    LED_Toggle();

    //    xTaskNotifyFromISR (xAPP_PRINT_THREAD_Tasks,0,0,&xHigherPriorityTaskWoken);
}

// *****************************************************************************
// *****************************************************************************
// Section: Application Local Functions
// *****************************************************************************
// *****************************************************************************


/* TODO:  Add any necessary local functions.
 */


// *****************************************************************************
// *****************************************************************************
// Section: Application Initialization and State Machine Functions
// *****************************************************************************
// *****************************************************************************

/*******************************************************************************
  Function:
    void APP_Initialize ( void )

  Remarks:
    See prototype in app.h.
 */

void APP_Initialize(void) {
    /* Place the App state machine in its initial state. */
    appData.state = APP_STATE_INIT;

    appData.isInitDone = false;

    /* TODO: Initialize your application's state machine and other
     * parameters.
     */
}

/******************************************************************************
  Function:
    void APP_Tasks ( void )

  Remarks:
    See prototype in app.h.
 */

void APP_Tasks(void) {

    uint8_t strlen;

    /* Check the application's current state. */
    switch (appData.state) {
            /* Application's initial state. */
        case APP_STATE_INIT:
        {
            bool appInitialized = true;

            TC5_TimerCallbackRegister(TC5_TimerCallback, (uintptr_t) NULL);
            TC5_TimerStart();

            /* Register callback function for period event */
            TCC2_PWMCallbackRegister(TCC_PeriodEventHandler, (uintptr_t) NULL);
//            TCC_CTRLBCLR_LUPD(1U); // Enable double buffering
            TCC2_PWMStart();

            EIC_CallbackRegister(EIC_PIN_15, EIC_EXTINT_15_InterruptHandler, 0);
            LED_Set();

            /* Open the drivers if not already opened */
            if (appData.isInitDone == false) {

                appData.SPIhandle = DRV_SPI_Open(DRV_SPI_INDEX_0, DRV_IO_INTENT_READ);

                if (appData.SPIhandle == DRV_HANDLE_INVALID) {
                    /* Handle error */
                    __NOP();
                }

                appData.USARThandle = DRV_USART_Open(DRV_USART_INDEX_0, DRV_IO_INTENT_WRITE); // Or DRV_IO_INTENT_BLOCKING ?

                if (appData.USARThandle != DRV_HANDLE_INVALID) {
                    /* All drivers opened successfully */
                    appData.isInitDone = true;

                    strlen = sprintf((char*) appData.usartTxBuffer, "***FreeRTOS Simplified*** \r\n Press SW0 on IO1 Xplained board for latest temperature data\r\n");
                    DRV_USART_WriteBuffer(appData.USARThandle, appData.usartTxBuffer, strlen);
                } else {
                    /* Handle Error */
                    __NOP();
                }


                if (appInitialized) {

                    appData.state = APP_STATE_SERVICE_TASKS;
                }
                break;
            }
        }

        case APP_STATE_SERVICE_TASKS:
        {
            /* Submit a blocking SPI read request for temperature*/
            PORT_PinClear(SERCOM0_SS_PIN);
            if (true == DRV_SPI_ReadTransfer(appData.SPIhandle, (void*) appData.spiRxBuffer, 2)) {
                PORT_PinSet(SERCOM0_SS_PIN);
                appData.temperature = (((appData.spiRxBuffer[0] << 8) + appData.spiRxBuffer[1]) >> 5);
                appData.tempf = (((appData.spiRxBuffer[0] << 8) + appData.spiRxBuffer[1]) >> 5) + (((appData.spiRxBuffer[0] << 8) + appData.spiRxBuffer[1]) >> 3 & 3) * 0.25;
                //                app_SPIdata.tempf = 226.25;
            }

            //            if (Button_pressed) {
            //                TCC2_PWM16bitDutySet(0, PWM_value / 100 * PERIOD_1); // +Duty 0; 10; 20 ... 100
            ////                TCC2_PWMForceUpdate();
            //
            //                strlen = sprintf((char*) appData.usartTxBuffer, "%.2f\r\n", PWM_value / 100 * PERIOD_1);
            //                DRV_USART_WriteBuffer(appData.USARThandle, appData.usartTxBuffer, strlen);
            //                LED_Toggle();
            //                PWM_value += 10;
            //                if (PWM_value > 100) {
            //                    PWM_value = 0;
            //                    Button_pressed = false;
            //                }
            //            }

            //                vTaskDelay(APP_SAMPLING_RATE_IN_MSEC / portTICK_PERIOD_MS); //1 second
            vTaskDelayUntil(&xLastWakeTime, xFrequency);

            break;


            /* TODO: implement your application state machine.*/


            /* The default state should never be executed. */
            default:
            {
                /* TODO: Handle error in application's state machine. */
                break;
            }
        }
    }
}

Project Graph

How fast does the control loop need to execute? Often fast PID control is triggered by a hardware timer and performed in the ISR, but I presume your use case is much slower.

I will measure the temperature in 1 second interval and set the PWM control for the next second. Although I can do it in one task, the temperature measurement will be triggered by periodic TimerCallback where I set the boolean variable to true and I will check it in the task to start the temperature measurement, calculate the PID control, set the PWM Duty for the next 1 second and reset the boolean variable to “false”.

I am concerned about the synchronisation of TimerCallback and the task vTaskDelay (1 second).

Can you elaborate on what you mean by this? Do you mean the read/write of Button_pressed variable from TCC_PeriodEventHandler and EIC_EXTINT_15_InterruptHandler? If yes, do you need to use TCC_PeriodEventHandler? Can you not do something like the following:

void DutyCycleTask( void * param )
{
    static uint16_t PWM_value = 58499;

    for( ;; )
    {
        /* Wait for a notification from ISR. */
        xTaskNotifyWait( 0,
                         ~0,
                         NULL,
                         portMAX_DELAY );

        TCC2_PWM16bitDutySet( TCC2_CHANNEL0, PWM_value );

        Duty_percent += DUTY_INCREMENT; // 0, 10, 20 ... 100
        PWM_value = Duty_percent / 100 * PERIOD - 1;

        if( Duty_percent > 100 )
        {
            Duty_percent = 0;
            PWM_value = 0;
        }

        LED_Toggle();
    }
}

void EIC_EXTINT_15_InterruptHandler( uintptr_t context )
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* Clear interrupt flag. */
    EIC_REGS->EIC_INTFLAG = (1UL << 15);

    xTaskNotifyFromISR( DutyCycleTaskHandle, 0, 0, &( xHigherPriorityTaskWoken ) );

    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

Thank you @aggarg for your advice.

Note that by pressing the button I just start the actual temperature measurement and PWM control for about 200 seconds (reflow profile).

Maybe I can remove the TCC2 timer and simply do all the job in one task with 1 second period. I just need to check the button flag to start the reflow control.

I did not realize that you want to run every second till 200 seconds once the button is pressed. How about the following:

void DutyCycleTask( void * param )
{
    static uint16_t PWM_value = 58499;

    for( ;; )
    {
        /* Wait for a notification from the button pressed ISR. */
        xTaskNotifyWait( 0,
                         ~0,
                         NULL,
                         portMAX_DELAY );

        for( ;; )
        {
            blockTime = pdMS_TO_TICKS( 1000 );
            TCC2_PWM16bitDutySet( TCC2_CHANNEL0, PWM_value );

            Duty_percent += DUTY_INCREMENT; // 0, 10, 20 ... 100
            PWM_value = Duty_percent / 100 * PERIOD - 1;

            if( Duty_percent > 100 )
            {
                Duty_percent = 0;
                PWM_value = 0;

                /* Wait for the next notification from the button pressed ISR. */
                break;
            }

            LED_Toggle();
        }
    }
}

void EIC_EXTINT_15_InterruptHandler( uintptr_t context )
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* Clear interrupt flag. */
    EIC_REGS->EIC_INTFLAG = (1UL << 15);

    xTaskNotifyFromISR( DutyCycleTaskHandle, 0, 0, &( xHigherPriorityTaskWoken ) );

    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}