PSOC: Millisecond Timer The Easy Way

It is extremely easy to add a millisecond timer to your project. With a 24 MHz or 48 MHz ARM CPU processor speed, a timer only takes a few microseconds to execute. Let’s get started.

Add Clock and ISR

Go to the schematic sheet, right click on a bottom tab and on “Add Schematic Page.” Right click on that new tab and on “Rename Page.” I chose to name it millisecond.

Go to the right hand page, in the search area, enter “clock,” and press enter. Several clocks will be shown. See following image:

Drag the “Clock [v2.20]” onto the sheet. Likewise, in the search area, enter “isr,” press enter, and drag the “Interrupt [v1.70]” (or later version) onto your page. You should have something like the following:

At this point, you have several choices to finish wiring. First choice is to grab isr_1 and drag the box over Clock_1’s box. This will connect them. Second choice is to hit the “w” key on the keyboard and click on isr_1’s box and then move the mouse onto Clock_1 and click again. The result will look like the following:

Double Click on Clock_1 to open the configuration dialog. Change it to look like the following:

Note that I changed the name to Clock_1ms. The Frequency has a 1, and the dropdown has kHz. mHz is million times per second, kHz is thousand times per second, and Hz is one time per second. Our millisecond timer will interrupt the processor 1,000 times per second.

Now double click the isr_1 and in its dialog, change it to the following:

Millisecond Timer Code

In my projects, a millisecond timer is always used for something, so I create it as a matter of habit.

The purpose of a millisecond timer is to give you a counter that lets you know how much time has passed. Generally, a 32 bit counter is long enough for practical uses.

For the ARM in the PSOC, the native integer length in the processor is 32 bits. That allows the processor to read the 32 bit value without it being changed during the read.

An Interrupt Issue and Solution

If the counter were a 64 bit variable, you might have a problem where the lower 32 bits were 0xFFFFFFFF when you read it. During the process of using the millisecond counter, the lower 32 bits are read and then the upper 32 bits are read with a different set of instructions. In between these two instructions, the millisecond interrupt could occur, and then your variable would be of by about 4 billion milliseconds.

This is because the upper 32 bits would be incremented by 1, and the lower 32 bits will be set to 0. However, since the processor already has the lower 32 bits loaded into registers, it still sees them as 0xFFFFFFFF.

To handle this problem, if you are needing to read complex items that can be changed due to an interrupt, you will need to “bracket” your code as you see in the following code snippet:

...
   // the enableInterrupts variable saves the current interrupt 
   // masking state inside the cpu registers
    uint8 enableInterrupts = CyEnterCriticalSection();
// do your complex code here

    // restore the interrupt masking state
    CyExitCriticalSection(enableInterrupts);

Back to the items at hand…..

Milliseconds.h

Create a header file and name it “Milliseconds.h” (See earlier posts if you don’t know how to do this.) Put the following in it:

#ifndef MILLISECONDS_H_
    #define MILLISECONDS_H_
/* ========================================
 * Copyright Wade Maxfield, 2020
 * All Rights Reserved
 * LICENSED SOFTWARE.
 *  Under the GPL v3 license
 * This license does not override previous licenses
 * Some information may be Proprietary to 
 * Cypress (http://www.cypress.com) for their
 * PSoC 5LP®--Cypress Semiconductor and
 * only usable on their devices.
 * PROPERTY OF Wade Maxfield.
 * Commercial license available
 * ========================================
*/
#include <project.h>
    
    // the following counter can be used to start things on 1 millisecond boundaries
extern volatile uint16 ms,seconds, minutes, hours;    
extern volatile uint32 milliseconds;
extern volatile int16 MsLedCounter;
 

// initialize function
void init_milliseconds();

#endif
/* [] END OF FILE */

Milliseconds.h Notes:

The keyword “volatile” forces the compiler to read the variable every time it is referenced in the code. This is because the variable is changing quite often. Otherwise, the compiler will hold onto the last read value and reuse it, so you may delay seeing a millisecond tick during execution of your code.

The “#include <project.h>” is used when you need the hardware definitions included in your code. This assists the PSOC Creator automatic code checker in properly flagging errors in your code.

Milliseconds.c

/* ========================================
 * Copyright Wade Maxfield, 2020
 * All Rights Reserved
 * LICENSED SOFTWARE.
 *  Under the GPL v3 license
 * This license does not override previous licenses
 * Some information may be Proprietary to 
 * Cypress (http://www.cypress.com) for their
 * PSoC 5LP®--Cypress Semiconductor and
 * only usable on their devices.
 * PROPERTY OF Wade Maxfield.
 * Commercial license available
 * ========================================
*/
#include "main.h"

volatile uint32 milliseconds;
volatile uint16 ms,seconds, minutes, hours;
volatile int16 MsLedCounter;

CY_ISR(MillisecondInterrupt) {
    milliseconds++;
    MsLedCounter++;
    
    if (++ms>=1000){
        ms=0;
        if (++seconds>=60){
            seconds=0;
            if (++minutes >=60) {
                minutes=0;
                if (++hours>=24){
                    hours=0;
                }
            }
        }
    }
        
}

void init_milliseconds() {
 
    isr_1ms_StartEx(MillisecondInterrupt);
    
}


/* [] END OF FILE */

Milliseconds.c Notes:

The function init_milliseconds() enables the interrupt placed on the millisecond schematic page. It does that by calling the isr_1ms_StartEx() function. This function is provided by the PSOC Creator after it builds the hardware on the schematic page. This function places the address of the millisecond ISR into the appropriate vector in the CPU memory.

The ISR (Interrupt Service Routine) function CY_ISR(MillisecondInterrupt) runs code every time the interrupt caused by the clock occurs. In our case, it runs 1,000 times per second, or once a millisecond. In our case, it increments two millisecond counter, and after 1 second, a second counter, and after 60 seconds, a minute counter, and after 60 minutes, an hour counter, which resets at 24 hours.

Timer Use

Our first use of a millisecond timer is to blink an LED. This sounds silly and trivial, but watching this LED will give you an indication of whether or not your program is running, and if it is handling events effectively. If the Led stutters, you are spending too much time in a routine somewhere.

Led Setup

Create a new schematic page and name it LED. On the CY8CKIT-059, pin 2[1] is the pin that has an LED attached. Search for Pin in the right hand pane, and select Digital Output Pin [v2.20] or newer. Drag it to the sheet, name it P2_1_Led. Uncheck “HW Connection.” See following:

The Pin on the schematic will lose its green wire an box. This is the visual indication the pin does not need to be attached to any hardware. It is only software controlled.

Connecting Schematic Pin To PSOC Port

Click on Pins in the left hand pane under Design Wide Resources, and select P2[1] for the pin, as follows:

Led Code

Add a new Leds.h header file, and fill as follows. I always name the file Leds.h, because almost all of my projects have more than one led.

Leds.h

#ifndef LEDS_H_
#define LEDS_H_
/* ========================================
 * Copyright Wade Maxfield, 2020
 * All Rights Reserved
 * LICENSED SOFTWARE.
 *  Under the GPL v3 license
 * This license does not override previous licenses
 * Some information may be Proprietary to 
 * Cypress (http://www.cypress.com) for their
 * PSoC 5LP®--Cypress Semiconductor and
 * only usable on their devices.
 * PROPERTY OF Wade Maxfield.
 * Commercial license available
 * ========================================
*/
#include <project.h>

extern void BlinkLed1(int16 millisecondsBetweenBlinks);
void Led1(int16 OnOff);
extern void init_leds();
#endif
/* [] END OF FILE */

Next, create the Leds.c file, and fill it as follows:

Leds.c

/* ========================================
 * Copyright Wade Maxfield, 2020
 * All Rights Reserved
 * LICENSED SOFTWARE.
 *  Under the GPL v3 license
 * This license does not override previous licenses
 * Some information may be Proprietary to 
 * Cypress (http://www.cypress.com) for their
 * PSoC 5LP®--Cypress Semiconductor and
 * only usable on their devices.
 * PROPERTY OF Wade Maxfield.
 * Commercial license available
 * ========================================
*/
#include "main.h"



#define LED1_PIN_WRITE(a)  P2_1_Led_Write(a) 
#define LED1_PIN_READ(a)  P2_1_Led_Read(a) 

//---------------------------------------------------
// initialize the LEDs
//---------------------------------------------------
void init_leds() {
    LED1_PIN_WRITE(1);
}
//---------------------------------------------------
// Turn the LED On or Off based on the OnOff variable
//---------------------------------------------------
void Led1(int16 OnOff) {
    if (OnOff) {
        LED1_PIN_WRITE(1);
    } else {
        LED1_PIN_WRITE(0);
    }
}
//---------------------------------------------------
// blink led based on rate in millisecondsBetweenBlinks
//---------------------------------------------------
void BlinkLed1(int16 millisecondsBetweenBlinks){
    if (MsLedCounter>=millisecondsBetweenBlinks) {
        MsLedCounter=0;// led 1
        Led1(!LED1_PIN_READ());                 
    }
}

/* [] END OF FILE */

Leds.c Notes:

There is a mixture of good practice and bad practice in the code previous. However, as long as your code works, does not fail, and is fast enough to get the job done, good and bad practices tend to be theoretical niceties that only professor types worry about.

An Aside

Before The Firestorm is generated over the previous statement, be assured that some software engineers are professor types. Sometimes they are your boss and you have to do what they say. There is nothing like a project failure to force them into either early retirement (or leave-taking), or an open mindset where getting the job done (and keeping that job) becomes more important than theory.

Having said that, please be assured that theory is extremely important where it lines up properly with performance. For instance, a Bell Labs white paper from the 1980’s opened my eyes about system design. It stated that if you describe every piece of data in your system, and what you do to or with that data, you have designed your system. (Note: Data is plural. Datum is singular. In the U.S.A., we normally use the word data for both plural and singular, so I will too.)

What is data? Data is how quickly and rhythmically a blinking LED blinks. Data is how many milliseconds have elapsed. Data is where the cpu is in the decision process. Data is the temperature in the room.

Back to the Code Notes:

First, the #defines are all upper case. This is just to allow you to easily identify that you need to look elsewhere to see what code or data that particular #define references.

Second, use long variable and function names. By doing this, you can describe what the variable really is and what you want to do to it or with it to yourself a year or two from now when you have forgotten what you were trying to do in the code. Make your code as self documenting as possible.

Third, I have a horrible mixture of styles. I use underscores to separate words, as classic C is taught. I use CamelCase to jam words together, similar to how you are taught in Pascal. I Capitalize some variables, and some I do not.

All of these problems can be fixed with search and replace once you are finished with working code and want to show it to someone. I am generally not one to fix these issues because the cost of my engineering time to my clients is so high they prefer to pay for working code. If they want it pretty, they hire admins to beautify it.

Fourth (and the meaty part), most of my variables are int16 (16 bit integer). That is because the RAM memory inside the PSOC is word based and a 16 bit access requires only one bus-access to obtain. Int8 (byte) operations may require more than one access in order to change the byte.

This also keeps reads “atomic” (meaning indivisible). This allows the code to do accurate comparisons without shutting down interrupts (and thus impacting real time operations, in some cases).

Finally, never write a variable that can be changed by an interrupt outside of that interrupt. Only read those variables. This is a mistake that is sometimes made by careless seasoned veterans (i.e. me). It can have bad consequences.

Tying It All Together

Open main.h, and add the leds.h and Milliseconds.h lines:

#include <project.h>
#include <stdio.h>
#include "parseCommands.h"
#include "USBSerial.h"
#include "ds18b20.h"
#include "Milliseconds.h"
#include "Leds.h"

Open Main.c, and initialize the Led’s and Millisecond timer and run the blinking code. Examine the following for the changes from previous posts:

Updated main.c

/* ========================================
 * Copyright Wade Maxfield, 2020
 * All Rights Reserved
 * LICENSED SOFTWARE.
 *  Under the GPL v3 license
 * This license does not override previous licenses
 * Some information may be Proprietary to 
 * Cypress (http://www.cypress.com) for their
 * PSoC 5LP®--Cypress Semiconductor and
 * only usable on their devices.
 * PROPERTY OF Wade Maxfield.
 * Commercial license available
 * ========================================
*/
#include "main.h"

int16_t TemperatureReadingAvailable;
float temperature;
int main(void)
{
    // the following two variables are for USBSerial/Command Parser
    int16 bufNum;
    USBSerialMessage *USBRxPtr;

    init_milliseconds();
    init_leds();
    
    CyGlobalIntEnable; /* Enable global interrupts. */

    initUSBSerial();// setup the USB serial interface.
    init_parse();
    
    
    for(;;)
    {
        
        BlinkLed1(100); // blink 5 times per second (every 100ms change LED state to on or off)

        //======================
        // usb serial port
        //======================
        if ((bufNum=handleUSBSerial())>=0){
            USBRxPtr=&USBRxBuffer[bufNum];
           parse((char*)(USBRxPtr->msg),(uint16*)&(USBRxPtr->size)); // handle possible command   
        }
        
        temperature=getTemperatureF(&TemperatureReadingAvailable); 
        if (TemperatureReadingAvailable) {
             // write your code here to do something
        }


    }
}

/* [] END OF FILE */

Testing

Compile, load, and run your code. You should see the Blue LED on the CY8CKIT-059 blink rapidly. If you examine it with an oscilloscope, you should see it blink, with a period of 100 milliseconds on, and 100 milliseconds off. This is roughly 5 blinks per second, a little faster than you can count.

Next Time

If life allows, next time we will start looking at capturing Air Conditioner Remote Control IR signals. During my investigations, I found that the information about how IR controls work does not 100% apply to A/C units. I had to engineer a different approach, which turned out to be a universal approach, and is similar to how learning remotes work.

To prepare for this, try to obtain one or two TSOP38438 preferred (or TSOP38238)  infrared receivers. They are available on Amazon, or from Mouser.com. There are some Asian clones with different part #s, but their quality appears to be suspect.

The key to the IR receiver is the 38 KHz filter in the receiver section. This allows the receiver to work in bright rooms with many IR (infrared) sources.

Enjoy!

Add a Comment

Your email address will not be published. Required fields are marked *