Skip to content
This repository has been archived by the owner on Apr 21, 2024. It is now read-only.

Interrupts 1: delay_ms()

prosper00 edited this page Dec 10, 2020 · 5 revisions

In order for the LCD library to work, I need to provide it with a delay_ms() function for timing. Fortunately, the LCD isn't picky about exact timings, so 1 ms is fine enough. Initially, I just did a big for() loop to burn up approximately 1ms - and this did work fine:

void delay_ms(u32 ms)  //hack. Kinda-sorta approximates 1ms. Kinda.
{
  for(u32 j=0;j<ms;j++)
    for(u32 i=0;i<1000;i++)
      __asm__("nop");
}

However - a delay function is a really useful thing to have, so I decided to do a better job of it, and implement it as an #include-able library for future use. Delays are such a fundamental function that there are plenty of examples available, even for this specific platform. However, I didn't find one that I really liked, so I more-or-less rolled my own.

I want to use an interrupt-driven timer that increments a global variable each millisecond. I can use this variable directly in the same way that you'd use the arduino millis() function (heck I could even implement a millis() function of my own). To do this requires the use of a timer peripheral though, which does raise the possibilities of conflicts with other programs that may want (or need) to use the same timer. I decided to use timer4, because it's a fairly simple timer, and doesn't tie up the more advanced functions that I might want (for things like PWM, for example). 

creating the interrupt service routine (ISR)

The way the SPL 'wants' you to define interrupts is within a couple of really ugly files, stm8s_it.c for the definitions, and stm8s_it.h for the declarations. Personally, I wanted to keep the definitions of the interrupts within my own source files, in whatever module they'll be used. I found that I could just delete the stm8s_it* files without consequence, and define my own ISR's wherever I wanted.

NOTE: I'm not sure if this is because of the modified SPL that I'm using sets up the vector tables for me somewhere else, or how exactly the various interrupt vectors are created. Therefore, I don't know whether or not this approach would work using the 'vanilla' SPL, or what it would take to make it work.

I have no idea what the INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23) means, or where it's defined. I assume it's a macro that comes from something stm8s.h includes... in any case, look to stm8s_it.c for a list of all the available interrupt handlers that are available, and the datasheet details out each of the IRQ's (in this case, '23').

Declaring the ISR was actually really easy:

INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
  time_ms++;
  TIM4_ClearITPendingBit(TIM4_IT_UPDATE);
}

Note that variables (such as time_ms used here) need to be defined globally as 'volatile,'  Otherise, this ISR is really simple - it just increments our time_ms variable each time it's called. Now we need to configure the timer to generate an interrupt every 1ms.

configuring the TIM4 timer

The way that timer4 works is very simple: it counts up from a value we specify to 255 over and over and over again, forever.

All we need to do is to is tell it:

  1. the period: how many counts to do
  2. how fast to perform the counts
  3. what to do when we overflow (count beyond 255)

We need to figure out # 1 and 2 above. For #3, we know we want to ensure that we select vales appropriate for a one millisecond interval, and then increment a millisecond counter. To do this we need to know our CPU clock rate (which we've previously set to 16MHz), and them select appropriate values for 1. and 2.

mathematically, if we want 1ms, this looks like this:

1 / .001s = F_CPU / prescaler / period

we can only choose from a limited list of values for prescaler (1, 2, 4, 8, 16, 32, 64 or 128, as documented in the datasheet), but we can choose anything from 0 to 255 for n. So, let's pick 64 for prescaler, and solve for the period, p:

p = 16000000Hz / 64 * .001s

p = 250

NOTE: I should probably implement a function that looks at F_CPU and figures out an appropriate prescaler and 'n,' (especially if I ever do anything with power saving or on-the-fly clock changes that still needs an accurate ms counter). But, for the moment, I've hardcoded an assumption of a 16MHz clock.

void TIM4_Config(void)
{
  CLK_PeripheralClockConfig (CLK_PERIPHERAL_TIMER4 , ENABLE); //this is enabled by default, but, let's just make sure it's on
  TIM4_DeInit(); // load the default configurations for tim4

  TIM4_TimeBaseInit(TIM4_PRESCALER_64, 250); //TimerClock = 16000000 / 64 / 250 = 1000Hz
  TIM4_ClearFlag(TIM4_FLAG_UPDATE);           //Reset the update flag
  TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);      //enable the timer4 interrupt ISR

  enableInterrupts(); // make sure global interrupts are enabled
  TIM4_Cmd(ENABLE);  //Start the Timer 4
}

Creating a delay_ms() function

By this point, we now have a variable, time_ms, that increments once every millisecond. It should be relatively accurate as it's tied to the main CPU clock, and is low overhead. Let's construct a simple function that waits for the next millisecond increment

void delay_ms(uint16_t ms)
{
    uint16_t start = time_ms;
    while((time_ms - start) < ms);
}

Improving our delay function

The function we created previously works, however, there are still some improvements to be made. As it's written now, it will delay until the ms counter increases. Which means that if we request a 1ms delay, we will really only get a delay between 'now' and the time the counter rolls over. If 'now' is already halfway to the next millisecond, we'll only get a half a millisecond delay.

What we need to do is look at the TIM4 counter to see how close we are to the nearest millisecond. Then we delay to that nearest millisecond, and add back in the time it takes the TIM4 counter  to get back up to the value it was at before:

void delay_ms(uint16_t ms) 
{
  uint8_t start_tick = TIM4_GetCounter();  //tim4 ticks every 4us     
  uint16_t start_ms = time_ms;          

  while((time_ms - start_ms) < ms);   //wait until the start of our desired ms delay     
  while((TIM4_GetCounter() < start_tick)); //now wait until our 4us tick matches our start tick 
}