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

9. STM32. Программирование STM32F103. TIMER
(на русском языке)

Таймери загального призначення

Таймери у мікроконтролерах 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); // Вмикаємо тактування таймера TIM4

  	TIM_TimeBaseStructInit(&TIMER_InitStructure);
    TIMER_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // Режим рахунку
    TIMER_InitStructure.TIM_Prescaler = 8000; // Поділювач частоти для таймера
    // Треба ще враховувати як налаштовані поділювачі RCC_HCLKConfig( RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div1);
    // У нашому випадку обидва  = RCC_SYSCLK_Div1, тобто до поділювача таймера доходить частота зовнішнього кварцу (8МГц)
    TIMER_InitStructure.TIM_Period = 500; // Період, через який виконується переривання по переповненню  // F=8000000/8000/500 = 2 рази/сек.
    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); // Вмикаємо тактування таймера TIM4

  	TIM_TimeBaseStructInit(&TIMER_InitStructure);
    TIMER_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // Режим рахунку
    TIMER_InitStructure.TIM_Prescaler = 7200; // Поділювач таймера
    TIMER_InitStructure.TIM_Period = 10000; // Період, через який виконується переривання по переповненню  // F=72000000/7200/10000 = 1 раз/сек.
    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);
    		}
    	}
    }
}

Бажаю успіхів!

Дивись також:

5 комментариев: 9. STM32. Програмування STM32F103. TIMER

  • Дмитрий говорить:

    Это только мне кажется, что код для STM32 в разы длиннее и сложнее чем для AVR? Думал попробовать перейти на STM32, но понимаю, что не осилю. Может я просто еще не готов к STM32 …..
    Отличный цикл статей. Не останавливайтесь и продолжайте дальше.

    • andre говорить:

      Да, для AVR чтобы что-то включить нужно делать меньше телодвижений. У STM32 значительно больше возможностей, как следствие больше всяких настроек. Я бы не сказал что код сложнее, больше – это да. Тем не менее, STM32 самый интересный из широкодоступных микроконтроллеров.

  • Dima говорить:

    Здравствуйте. Спасибо за уроки, все понятно изложено, хорошие примеры. Только вот не пойму один момент, а именно: в фоновой задаче, строка 213 – по какому принципу идет пересчет в мс? Почему считываемое значение счетного регистра делится именно на 10? Извиняюсь за, возможно, глупый вопрос

    • andre говорить:

      Частота тактирования 72000000
      Делитель таймера TIM_Prescaler = 7200
      т.е. Таймер щелкает 72000000/7200 = 10000 раз в секунду.
      Если таймер насчитал 10000, тогда это 1000 миллисекунд. Результат нужно оделить на 10.

      Можно было бы установить делитель TIM_Prescaler = 72000, и тогда делить на 10 не пришлось бы, но… делитель может быть не более 65535.

  • Владимир говорить:

    Спасибо за уроки. Но, у меня вопрос. Я запустил первую программу на такой же плате. Все работает. Теперь хочу изменить частоту на ножке на 1 МГц. Ставлю TIMER_InitStructure.TIM_Prescaler = 8 и TIMER_InitStructure.TIM_Period = 1. По формуле должно быть на выходе 8000000/8/1=1000000 Гц. Однако на 13 ножке 40 КГц. Я проверяю осциллографом. Подскажите, почему не получается.

Translate
Архіви

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