сайт для палких паяльників

Таймеры общего назначения

Таймеры в микроконтроллерах STM32 делятся по функционалу на:

  1. basic timers (базовые таймеры)
  2. general-purpose timers (общего назначения: TIM2, TIM3, TIM4)
  3. advanced-control timers (продвинутые таймеры: TIM1)

В различных микроконтроллерах количество таймеров разная. Согласно документации к контроллеру STM32F103C8 имеем 3 таймера general-purpose, и один advanced-control.

STM32_Timers

Сегодня мы рассмотрим таймеры общего назначения. Базовых таймеров у нас нет, но это не страшно, таймеры общего назначения имеют такой же функционал, как и базовые, плюс еще кое-что.

Вообще, счетчики могут не только генерировать прерывания через определенное время и измерять время между событиями. Таймеры могут генерировать PWM сигнал, и одиночные импульсы, работать с периферией, например запускать преобразования АЦП, выполнять захват сигнала, работать с энкодером и тому подобное. Таймером будет посвящено несколько статей. Начнем с самого простого.

Генерирование прерывания через равные промежутки времени

Например, настроим таймер TIM4 и заставим его мигать светодиод через определенные промежутки времени. Помните самый первый пример? Там задержка делалась с помощью цикла. Это не правильно. Старайтесь так не делать. Давайте сделаем мигание светодиодом “по фэншую”.

Итак, мы настроим таймер таким образом, чтобы он считал до определенного числа и вызывал прерывания по переполнению. Таймеры считают импульсы, которыми они тактируются (Смотри статью о тактировании STM32). В STM микроконтроллерах есть делитель частоты для каждого таймера. Значение делителя могут быть от 1 до 65535. Кроме того, мы можем задать число, досчитав к которому таймер будет вызывать прерывания по переполнению, обнуляться и считать сначала. Комбинируя эти два параметра, можно добиться нужной частоты, с которой будет вызываться прерывания от таймера. В нашем примере контроллер будет тактироваться от внешнего кварца частотой 8МГц. Таймер также будет работать на этой частоте, так как значение делителя APB1 = 1. Обратите внимание на желтые блоки схемы тактирования.

STM32_Clock_TIM

То есть, если APB1 = 1, тогда таймер TIM4 будет тактуватися частотой 8МГц.

Если APB1 = 2, тогда таймер TIM4 будет тактуватися тоже частотой 8МГц. Потому, как показано на рисунке, частота умножается на 2.
Если APB1 = 4, тогда таймер TIM4 будет тактуватися частотой 4МГц. Потому, как показано на рисунке, если APB1> 1, то частота, деленная на APB1 затем домножиться на 2. Не забывайте об этой особенности.

Мы установим делитеь 8000. Таймер будет считать с частотой 8000000/8000 = 1000 раз в секунду. Установим период = 500. То есть, досчитав до 500 (это 2 раза в секунду) будет вызываться прерывание. В обработчике прерывания мы пропишем команду для изменения состояния светодиода.

Код программы:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "misc.h"

void SetSysClockToHSE(void)
{
	ErrorStatus HSEStartUpStatus;
	/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK */
        RCC_PCLK1Config(RCC_HCLK_Div1);

        /* Select HSE as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_HSE);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x04)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}

void TIM4_IRQHandler(void)
{
        if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
        {
        	// Обязательно сбрасываем флаг
        	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
			GPIOC->ODR ^= GPIO_Pin_13;
        }
}

int main(void)
{
	SetSysClockToHSE();

	/* Initialize LED which connected to PC13 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	// Enable PORTC Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	/* Configure the GPIO_LED pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_ResetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")

    // TIMER4
    TIM_TimeBaseInitTypeDef TIMER_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

  	TIM_TimeBaseStructInit(&TIMER_InitStructure);
    TIMER_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIMER_InitStructure.TIM_Prescaler = 8000;
    TIMER_InitStructure.TIM_Period = 500;
    TIM_TimeBaseInit(TIM4, &TIMER_InitStructure);
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM4, ENABLE);

    /* NVIC Configuration */
    /* Enable the TIM4_IRQn Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    while(1)
    {
    	// В главном цикле делаем что хотим.
    }
}

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

Измерение времени между двумя событиями

Когда нам нужно измерять длину импульса или время между двумя событиями, тоже применяется таймер. Мы рассмотрим пример, в котором будем запускать таймер одним событием (нажатием кнопки, подключенной к PB0), останавливать – другой (аналогичная кнопка, но подключена к PB1). При остановке будем высчитывать время между этими двумя событиями и отправлять результат в последовательный порт USART.

Код программы:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_tim.h"
#include "misc.h"

volatile int TimeResult;
volatile int TimeSec;
volatile uint8_t TimeState = 0;

void SetSysClockTo72(void)
{
	ErrorStatus HSEStartUpStatus;
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* Enable Prefetch Buffer */
    	//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);

        /* Flash 2 wait state */
        //FLASH_SetLatency( FLASH_Latency_2);

        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK/2 */
        RCC_PCLK1Config( RCC_HCLK_Div2);

        /* PLLCLK = 8MHz * 9 = 72 MHz */
        RCC_PLLConfig(0x00010000, RCC_PLLMul_9);

        /* Enable PLL */
        RCC_PLLCmd( ENABLE);

        /* Wait till PLL is ready */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }

        /* Select PLL as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}


void TIM4_IRQHandler(void)
{
        if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
        {
        	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
        	TimeSec++;
        }
}

void usart_init(void)
{

	    /* Enable USART1 and GPIOA clock */
	    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

	    /* Configure the GPIOs */
	    GPIO_InitTypeDef GPIO_InitStructure;

	    /* Configure USART1 Tx (PA.09) as alternate function push-pull */
	    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	    GPIO_Init(GPIOA, &GPIO_InitStructure);

	    /* Configure USART1 Rx (PA.10) as input floating */
	    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	    GPIO_Init(GPIOA, &GPIO_InitStructure);

	    /* Configure the USART1 */
	    USART_InitTypeDef USART_InitStructure;

	  /* USART1 configuration ------------------------------------------------------*/
	    /* USART1 configured as follow:
	          - BaudRate = 115200 baud
	          - Word Length = 8 Bits
	          - One Stop Bit
	          - No parity
	          - Hardware flow control disabled (RTS and CTS signals)
	          - Receive and transmit enabled
	          - USART Clock disabled
	          - USART CPOL: Clock is active low
	          - USART CPHA: Data is captured on the middle
	          - USART LastBit: The clock pulse of the last data bit is not output to
	                           the SCLK pin
	    */
	    USART_InitStructure.USART_BaudRate = 115200;
	    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	    USART_InitStructure.USART_StopBits = USART_StopBits_1;
	    USART_InitStructure.USART_Parity = USART_Parity_No;
	    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	    USART_Init(USART1, &USART_InitStructure);

	    /* Enable USART1 */
	    USART_Cmd(USART1, ENABLE);
}

void USARTSend(const unsigned char *pucBuffer)
{
    while (*pucBuffer)
    {
        USART_SendData(USART1, *pucBuffer++);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
        {
        }
    }
}

int main(void)
{
	char buffer[80] = {'\0'};
	SetSysClockTo72();

	/* Initialize LED which connected to PC13 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	// Enable PORTC Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	/* Configure the GPIO_LED pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_SetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")

	/* Initialize Button input PB0 PB1 */
	// Enable PORTB Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	/* Configure the GPIO_BUTTON pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

    // TIMER4
    TIM_TimeBaseInitTypeDef TIMER_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

  	TIM_TimeBaseStructInit(&TIMER_InitStructure);
    TIMER_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIMER_InitStructure.TIM_Prescaler = 7200;
    TIMER_InitStructure.TIM_Period = 10000;
    TIM_TimeBaseInit(TIM4, &TIMER_InitStructure);
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM4, ENABLE);

    /* NVIC Configuration */
    /* Enable the TIM4_IRQn Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    usart_init();

    while(1)
    {
    	if (TimeState == 0) {
    		// Кнопка запускает отсчет таймера
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) {
    			TIM_Cmd(TIM4, ENABLE);
    			TIM_SetCounter(TIM4, 0);
    			TimeSec = 0;
    			// Set Status "ON"
    			TimeState = 1;

    			// OFF LED
    			GPIO_ResetBits(GPIOC, GPIO_Pin_13);
    			USARTSend("Started...");
    		}
    	}

    	if (TimeState == 1) {
    		// Кнопка останавливает таймер
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) {
    			TimeResult = TIM_GetCounter(TIM4)/10 + TimeSec * 1000; // Time im msec
    			TIM_Cmd(TIM4, DISABLE);
    			TimeState = 0;

    			// ON LED
    			GPIO_SetBits(GPIOC, GPIO_Pin_13);

        		sprintf(buffer, "Time: %d ms\r\n", TimeResult);
        		USARTSend(buffer);
    		}
    	}
    }
}

Желаю успехов!

Смотри также:

Translate
Архіви

© 2011-2018 Андрій Корягін, Кременчук - Київ, Україна