STM8+SDCC+SPL: функции delay_ms() и delay_us() на таймере TIM4

разделы: STM8 , дата: 16 июля 2016г.

8-битный базовый таймер TIM4 самый простой из набора STM8, но даже на таком простом таймере, можно познакомиться с некоторыми принципами всех таймеров STM8:

  • Введено такое понятие, как событие(event). Как правило, прерывание таймера может вызвать некоторый набор событий(events).
  • У некоторых регистров таймера имеются теневые/буферные регистры. Записать в них можно в любой момент, но вступят в действие новые значения только при "событии обновления/update".
  • Таймеры могут работать как в непрерывном режиме, так и одиночном, до первого прерывания.

TIM4 по возможностям примерно походит на 8-битный TIMER2 в AVR, за тем исключением, что он не может генерировать ШИМ сигнал, т.е. это тот таймер, который не жалко отдать на счетчики, поэтому попробуем на его основе сделать функции delay_ms() и delay_us(), которые широко используются при программной реализации различных протоколов.

Регистров относящихся к TIM4 сравнительно немного, в SPL их описывает следующая структура:

typedef struct TIM4_struct
{
  __IO uint8_t CR1;  /*!< control register 1 */
#if defined(STM8S103) || defined(STM8S003)
        uint8_t RESERVED1; /*!< Reserved register */
        uint8_t RESERVED2; /*!< Reserved register */
#endif
  __IO uint8_t IER;  /*!< interrupt enable register */
  __IO uint8_t SR1;  /*!< status register 1 */
  __IO uint8_t EGR;  /*!< event generation register */
  __IO uint8_t CNTR; /*!< counter register */
  __IO uint8_t PSCR; /*!< prescaler register */
  __IO uint8_t ARR;  /*!< auto-reload register */
}
TIM4_TypeDef;

CR1 - это главный управляющий регистр, IER - разрешает/запрещает прерывание, SR1- флаговый/статусный регистр прерывания, EGR - программная генерация события, нужно для обновления регистра предделителя, CNTR - счетчик, PSCR - содержит предделитель, ARR - содержит значение до которого считает таймер, после чего тот обнуляется и идет по новому кругу.

RSCR и ARR имеют теневые регистры, причем теневой регистр ARR отключаемый через TIM4_CR1.

Значения по умолчанию, которые загружаются в регистры при инициализации таймера функцией TIM4_DeInit():

#define TIM4_CR1_RESET_VALUE  ((uint8_t)0x00)
#define TIM4_IER_RESET_VALUE  ((uint8_t)0x00)
#define TIM4_SR1_RESET_VALUE  ((uint8_t)0x00)
#define TIM4_EGR_RESET_VALUE  ((uint8_t)0x00)
#define TIM4_CNTR_RESET_VALUE ((uint8_t)0x00)
#define TIM4_PSCR_RESET_VALUE ((uint8_t)0x00)
#define TIM4_ARR_RESET_VALUE  ((uint8_t)0xFF)

Пример простой реализации функций delay_ms() и delay_us() на таймере:

#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_clk.h"
#include "stm8s_tim4.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define TIM4_PERIOD       124
#define TIM4_US_PERIOD       4

volatile uint16_t count;

INTERRUPT_HANDLER(IRQ_Handler_TIM4, 23)
{
    if (count)
        count--;

    TIM4_ClearITPendingBit(TIM4_IT_UPDATE);
}

void delay_us(uint16_t us)
{
        TIM4_Cmd(DISABLE);       // stop
        TIM4_TimeBaseInit(TIM4_PRESCALER_4, TIM4_US_PERIOD);
        TIM4_ClearFlag(TIM4_FLAG_UPDATE);
        TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);

        count = us>>1;

        TIM4_Cmd(ENABLE);       // let's go

        while(count);
}


void delay_ms(uint16_t ms)
{
        TIM4_Cmd(DISABLE);       // stop
        TIM4_TimeBaseInit(TIM4_PRESCALER_128, TIM4_PERIOD);
        TIM4_ClearFlag(TIM4_FLAG_UPDATE);
        TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);

        count = ms;

        TIM4_Cmd(ENABLE);       // let's go

    while(count);
}


void delay_us_check(uint16_t time)
{
    while (time)
    {
        delay_us(1000);
        time--;
    }
}

ErrorStatus status = FALSE;

int main( void )
{
    // ----------- GPIO CONFIG -------------------
    GPIO_DeInit(LED_PORT);
    GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // set 16 MHz for CPU

    // uncomment if use HSE on Quartz
    //status = CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE,
    //                CLK_CURRENTCLOCKSTATE_DISABLE);

    // ---------- TIM4 CONFIG -----------------------
    TIM4_DeInit();

    enableInterrupts();

    for(;;)
    {
        delay_us_check(1000);
        GPIO_WriteReverse(LED_PORT, LED);
        delay_ms(1000);
        GPIO_WriteReverse(LED_PORT, LED);
    }
}

Здесь светодиод должен мигать с интервалом в одну секунду. Программа составлена на основе исходников из Examples SPL, константа TIM4_US_PERIOD была подобрана мною экспериментально.

Как рассчитывается период и почему этот вариант лучше и точнее варианта на TIMER0 в AVR?

Timer0 в AVR работает на прерывании по переполнению, которое происходит после 256 индексацией счетчика. Для счета микросекунд такой счетчик не пригоден в принципе, потому что для этого понадобился бы как минимум кварц на 256 MHz. Для счета миллисекунд прерывание должно вызываться на частоте 1кHz. 1MHz/(256*4) = 0.9765625 kHz. Стоит ли говорить о том как сильно 1kHz отличается от 0.9765625 kHz ?

Вернемся к STM8. 16MHz, предделитель 128. Имеем (16 MHz / 128) = 125 kHz. В TIM4 мы имеем возможность вызывать прерывание таймера при любом значении счетчика от 0 до 255. Т.е. если частота индексирования счетчика 125КHz, за 1мс он будет индексироваться 125 раз. Итого, прерывание нужно вызывать на значении 125-1=124, т.к. счет идет с нуля. Такое прерывание будет срабатывать именно один раз в миллисекунду.

Вернемся к SPL. Функционал библиотеки включает в себя следующие функции:

void TIM4_DeInit(void);
void TIM4_TimeBaseInit(TIM4_Prescaler_TypeDef TIM4_Prescaler, uint8_t TIM4_Period);
void TIM4_Cmd(FunctionalState NewState);
void TIM4_ITConfig(TIM4_IT_TypeDef TIM4_IT, FunctionalState NewState);
void TIM4_UpdateDisableConfig(FunctionalState NewState);
void TIM4_UpdateRequestConfig(TIM4_UpdateSource_TypeDef TIM4_UpdateSource);
void TIM4_SelectOnePulseMode(TIM4_OPMode_TypeDef TIM4_OPMode);
void TIM4_PrescalerConfig(TIM4_Prescaler_TypeDef Prescaler, TIM4_PSCReloadMode_TypeDef TIM4_PSCReloadMode);
void TIM4_ARRPreloadConfig(FunctionalState NewState);
void TIM4_GenerateEvent(TIM4_EventSource_TypeDef TIM4_EventSource);
void TIM4_SetCounter(uint8_t Counter);
void TIM4_SetAutoreload(uint8_t Autoreload);
uint8_t TIM4_GetCounter(void);
TIM4_Prescaler_TypeDef TIM4_GetPrescaler(void);
FlagStatus TIM4_GetFlagStatus(TIM4_FLAG_TypeDef TIM4_FLAG);
void TIM4_ClearFlag(TIM4_FLAG_TypeDef TIM4_FLAG);
ITStatus TIM4_GetITStatus(TIM4_IT_TypeDef TIM4_IT);
void TIM4_ClearITPendingBit(TIM4_IT_TypeDef TIM4_IT);

При инициализации таймера, сначала вызывается функция TIM4_TimeBaseInit, Которая устанавливает значение предделителя и порогового значения счетчика:

void TIM4_TimeBaseInit(TIM4_Prescaler_TypeDef TIM4_Prescaler, uint8_t TIM4_Period)
{
  /* Set the Prescaler value */
  TIM4->PSCR = (uint8_t)(TIM4_Prescaler);
  /* Set the Autoreload value */
  TIM4->ARR = (uint8_t)(TIM4_Period);
}

Сброс UIF бита:

void TIM4_ClearFlag(TIM4_FLAG_TypeDef TIM4_FLAG)
{
  /* Clear the flags (rc_w0) clear this bit by writing 0. Writing <91>1<92> has no effect*/
  TIM4->SR1 = (uint8_t)(~TIM4_FLAG);
}

разрешение прерывания:

void TIM4_ITConfig(TIM4_IT_TypeDef TIM4_IT, FunctionalState NewState)
{
  if (NewState != DISABLE)
  {
    /* Enable the Interrupt sources */
    TIM4->IER |= (uint8_t)TIM4_IT;
  }
  else
  {
    /* Disable the Interrupt sources */
    TIM4->IER &= (uint8_t)(~TIM4_IT);
  }
}

полностью аналогично функции TIM4_ClearFlag приведенной выше:

void TIM4_ClearITPendingBit(TIM4_IT_TypeDef TIM4_IT)
{
  /* Clear the IT pending Bit */
  TIM4->SR1 = (uint8_t)(~TIM4_IT);
}

включение/выключение счетчика:

void TIM4_Cmd(FunctionalState NewState)
{
  /* set or Reset the CEN Bit */
  if (NewState != DISABLE)
  {
    TIM4->CR1 |= TIM4_CR1_CEN;
  }
  else
  {
    TIM4->CR1 &= (uint8_t)(~TIM4_CR1_CEN);
  }
}

Теперь немного о там как все это работает. Ниже представлен управляющий таймером регистр TIM4_CR1:

    Итак:
  • Регистры TIM4_IER, TIM4_SR, TIM4_EGR имеют по одному флагу соответственно: UIE, UIF, UG.
  • Регистры TIM4_PSCR и TIM4_ARR имеют теневые регистры, и при записи в эти регистры, записываемое значение попадает именно туда. Попасть в реальные регистры они могут только при UEV - Update EVent.
  • При UEV - аппаратно устанавливается флаг UIF - Update Interrupt Flag в TIM4_SR. Его нужно сбрасывать вручную.
  • UEV может генерироваться при совпадении TIM4_ARR и TIM4_СNTR либо при установленном UG регистра TIM4_EGR. Если установлен флаг URS в TIM4_СR1, то UEV генерируется только в первом случае, при совпадении TIM4_ARR и TIM4_СNTR.
  • Если установлен флаг UDIS в TIM4_CR1, то генерация UEV будет заблокирована, а изменение TIM4_PCSR и TIM4_ARR будет невозможным. Однако, если при этом будет установлен флаг UE в регистре TIM4_EGR, то можно будет писать напрямую в TIM4_PSCR и TIM4_ARR.

Резюмируя, можно сказать, что есть два пути корректного изменения содержимого TIM4_PCSR и TIM4_ARR: в обработчике прерывания или при установленных флагах: (UG == 1 && UDIS == 1). Да, и кстати. Если флаг ARPE в TIM4_CR1 сброшен в ноль, то теневой регистр ARR не используется, т.е. он пишется напрямую.

Если взглянуть на текст приведенной программы, то думаю будет видно, что не все так сложно, нужно только лишь соблюдать определенный порядок действий.

Таймер TIM4 для чипа STM8L051F3 (добавлено 26 августа 2016г.)

В L-серии таймер TIM4 походит на свой аналог из S-серии, однако, различия имеются. Первое существенное различие состоит в том, что перед использованием таймера, его следует сначала включить, подключив в шине тактирования:

 CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);

За исключением этого момента, аналог вышеприведенной программы для stm8l051f3, алгоритмически не будет отличаться:

#include "stm8l.h"
#include "stm8l_gpio.h"
#include "stm8l_clk.h"
#include "stm8l_tim4.h"

#define PORT GPIOB
#define LED  GPIO_Pin_1
#define TIM4_PERIOD 124
#define TIM4_US_PERIOD 4

volatile uint16_t count;

INTERRUPT_HANDLER(IRQ_Handler_TIM4, 25)
{
  if (count)
    count--;

  TIM4_ClearITPendingBit(TIM4_IT_Update);
}

void delay_us(uint16_t us)
{
        TIM4_Cmd(DISABLE);       // stop


        TIM4_TimeBaseInit(TIM4_Prescaler_4, TIM4_US_PERIOD);
        TIM4_ClearFlag(TIM4_FLAG_Update);
        TIM4_ITConfig(TIM4_IT_Update, ENABLE);

        count = us>>1;

        TIM4_Cmd(ENABLE);       // let's go

        while(count);
}

void delay_ms(uint16_t ms)
{
        TIM4_Cmd(DISABLE);       // stop

        TIM4_TimeBaseInit(TIM4_Prescaler_128, TIM4_PERIOD);
        TIM4_ClearFlag(TIM4_FLAG_Update);
        TIM4_ITConfig(TIM4_IT_Update, ENABLE);

        count = ms;

        TIM4_Cmd(ENABLE);       // let's go

        while(count);
}

void delay_us_check(uint16_t time)
{
    while (time)
    {
        delay_us(1000);
        time--;
    }
}

int main( void )
{
  // ---------   GPIO Config ---------------------------

  GPIO_DeInit(PORT);

  GPIO_Init(PORT, LED, GPIO_Mode_Out_PP_Low_Fast);

  // ---------- CLK Config -----------------------
  CLK_DeInit();
  CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);
  CLK_PeripheralClockConfig(CLK_Peripheral_TIM4, ENABLE);

  // ---------- TIM4 Init -----------------------------
  TIM4_DeInit();

  enableInterrupts();

  for(;;)
    {
    delay_ms(1000);
    GPIO_ResetBits(PORT, LED);
    delay_us_check(1000);
    GPIO_SetBits(PORT,LED);
    }

}

Выглядит идентично, но отличия в системе регистров и интефейсе модуля TIM4 SPL все-таки имеются.

В SPL регистры описывается следующей структурой:

typedef struct TIM4_struct
{
  __IO uint8_t CR1;   /*!< control register 1 */
  __IO uint8_t CR2;   /*!< control register 2 */
  __IO uint8_t SMCR;  /*!< Synchro mode control register */
  __IO uint8_t DER;   /*!< DMA requests enable register */
  __IO uint8_t IER;   /*!< interrupt enable register  */
  __IO uint8_t SR1;   /*!< status register 1    */
  __IO uint8_t EGR;   /*!< event generation register */
  __IO uint8_t CNTR;  /*!< counter register  */
  __IO uint8_t PSCR;  /*!< prescaler register */
  __IO uint8_t ARR;   /*!< auto-reload register */
}
TIM4_TypeDef;

Как видно, добавлены были регистры CR2, SMCR, DER.

Интерфейс модуля так же был расширен:

void TIM4_DeInit(void);
void TIM4_TimeBaseInit(TIM4_Prescaler_TypeDef TIM4_Prescaler,
                       uint8_t TIM4_Period);
void TIM4_PrescalerConfig(TIM4_Prescaler_TypeDef Prescaler,
                          TIM4_PSCReloadMode_TypeDef TIM4_PSCReloadMode);
void TIM4_SetCounter(uint8_t Counter);
void TIM4_SetAutoreload(uint8_t Autoreload);
uint8_t TIM4_GetCounter(void);
TIM4_Prescaler_TypeDef TIM4_GetPrescaler(void);
void TIM4_UpdateDisableConfig(FunctionalState NewState);
void TIM4_UpdateRequestConfig(TIM4_UpdateSource_TypeDef TIM4_UpdateSource);
void TIM4_ARRPreloadConfig(FunctionalState NewState);
void TIM4_SelectOnePulseMode(TIM4_OPMode_TypeDef TIM4_OPMode);
void TIM4_Cmd(FunctionalState NewState);

/* Interrupts, DMA and flags management ***************************************/
void TIM4_ITConfig(TIM4_IT_TypeDef TIM4_IT, FunctionalState NewState);
void TIM4_GenerateEvent(TIM4_EventSource_TypeDef TIM4_EventSource);
FlagStatus TIM4_GetFlagStatus(TIM4_FLAG_TypeDef TIM4_FLAG);
void TIM4_ClearFlag(TIM4_FLAG_TypeDef TIM4_FLAG);
ITStatus TIM4_GetITStatus(TIM4_IT_TypeDef TIM4_IT);
void TIM4_ClearITPendingBit(TIM4_IT_TypeDef TIM4_IT);
void TIM4_DMACmd(TIM4_DMASource_TypeDef TIM4_DMASource, FunctionalState NewState);

/* Clocks management **********************************************************/
void TIM4_InternalClockConfig(void);

/* Synchronization management *************************************************/
void TIM4_SelectInputTrigger(TIM4_TRGSelection_TypeDef TIM4_InputTriggerSource);
void TIM4_SelectOutputTrigger(TIM4_TRGOSource_TypeDef TIM4_TRGOSource);
void TIM4_SelectSlaveMode(TIM4_SlaveMode_TypeDef TIM4_SlaveMode);
void TIM4_SelectMasterSlaveMode(FunctionalState NewState);

Во-первых, тут появился DMA, а во-вторых система менеджмента, позволяющая выстраивать таймеры цепочкой, когда один таймер тактирует другой. В режиме мастера таймер настраивается через регистр CR2, в режиме слейва - через SMCR.

Скачать архив с полными исходниками можно здесь: скачать

поделиться: