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

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

Зовнішні переривання

Зовнішні переривання викликаються при зміні стану логічного сигналу на вході мікроконтролера. Зовнішні переривання використовують, коли треба слідкувати за зміною вхідного сигналу і оперативно реагувати. Коли нам потрібен такий функціонал, ми налаштовуємо потрібний пін, налаштовуємо зовнішнє переривання (по спаду, по фронту або по обом фронтам) і, як тільки відбудеться зміна стану сигналу, буде викликано обробник переривання.

У STM32 зі зовнішніми перериваннями можуть працювати будь-які лінії вводу-виводу. Тобто, будь-який пін можна налаштувати на роботу з перериванням. У STM32F103 всього 19 ліній зовнішніх переривань:

  • EXTI0…EXTI15 – для роботи з пінами портів
  • EXTI16 – підключений до PVD
  • EXTI17 – RTC Alert event (будильник)
  • EXTI18 – USB Wakeup event
  • EXTI19 – Ethernet Wakeup event

У різних серіях STM32 кількість EXTI може бути різною. Не полініться зазирнути до документації. У деяких мікроконтролерах через EXTI заведені переривання від периферії. І не тільки типу wakeup. Наприклад, на лінії EXTI можуть бути заведені виходи аналогових компараторів. Давайте розглянемо наступну структурну схему:

stm32-exti

Зі схеми видно, що переривання EXTI0 можна задіяти для роботи з нульовими лініями усіх портів. Переривання EXTI1 можна задіяти для роботи з першими лініями усіх портів. І так далі.

Якщо ми налаштуємо переривання на лініях PA0, PB0, при змінах на будь-якому з цих двох пінів буде викликатися один обробник – EXTI0. Тому нам, можливо, знадобиться додаткова перевірка стану входів.

Ініціалізація зовнішнього переривання

Ініціалізація зовнішнього переривання виконується наступним чином:

  • Вмикається тактування порту GPIO та AFIO
  • Налаштовується пін порту на вхід
  • Налаштовуємо EXTI у NVIC
  • Вказуємо порт і пін, який буде використовуватися як джерело для EXTI
  • Налаштовуємо параметри EXTI

stm32-exti1

Приклад використання EXTI

Використання EXTI на прикладі роботи з сонаром HC-SR04. У цьому прикладі мікроконтролер посилає сонару імпульс (Trigger), який запускає вимірювання. Через деякий час сонар має “підняти” сигнал Echo – саме у цей момент викликається перивання і починається відлік часу. А потім сонар сигнал Echo “опускає”. У цей момент викликається перивання і вимірювання закінчується. З таймера зчитуються показник лічильника і, в залежності від виміряної довжини зворотного імпульсу Echo вираховується відстань від сонара до перешкоди.

Діаграма сигналів HC-SR04:

ultrasonic- timing-diagram

Схема підключення сонара до тестової плати STM32F103C8:

STM32F103C8_Sonar

Текст програми:

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

volatile uint8_t FLAG_ECHO = 0;
volatile uint16_t SonarValue;

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 sonar_init() {
	GPIO_InitTypeDef gpio_cfg;
	GPIO_StructInit(&gpio_cfg);

	/* Timer TIM3 enable clock */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	/* Timer TIM3 settings */
	TIM_TimeBaseInitTypeDef timer_base;
	TIM_TimeBaseStructInit(&timer_base);
	timer_base.TIM_CounterMode = TIM_CounterMode_Up;
	timer_base.TIM_Prescaler = 72;
	TIM_TimeBaseInit(TIM3, &timer_base);
	TIM_Cmd(TIM3, ENABLE);

	//Trigger Pin
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
	gpio_cfg.GPIO_Pin = GPIO_Pin_15;
	GPIO_Init(GPIOB, &gpio_cfg);

	//EXTI

	/* Set variables used */
	GPIO_InitTypeDef GPIO_InitStruct;
	EXTI_InitTypeDef EXTI_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;

	/* Enable clock for AFIO */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

	/* Set pin as input */
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);

	/* Add IRQ vector to NVIC */
	/* PB0 is connected to EXTI_Line0, which has EXTI0_IRQn vector */
	NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
	/* Set priority */
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
	/* Set sub priority */
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
	/* Enable interrupt */
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	/* Add to NVIC */
	NVIC_Init(&NVIC_InitStruct);

	/* Tell system that you will use PB0 for EXTI_Line0 */
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);

	/* PD0 is connected to EXTI_Line0 */
	EXTI_InitStruct.EXTI_Line = EXTI_Line0;
	/* Enable interrupt */
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	/* Interrupt mode */
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	/* Triggers on rising and falling edge */
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
	/* Add to EXTI */
	EXTI_Init(&EXTI_InitStruct);
}

void EXTI0_IRQHandler(void) {
	/* Make sure that interrupt flag is set */
	if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) != 0) {
			// Rising
			TIM_SetCounter(TIM3, 0);
		}
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) {
			// Falling
			SonarValue = TIM_GetCounter(TIM3);
		}

		/* Clear interrupt flag */
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

void sonar_start() {
	int i;

	GPIO_SetBits(GPIOB, GPIO_Pin_15);
	//Delay 0x72000
	for(i=0;i<0x7200;i++);
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);
}

unsigned int sonar_get() {
	unsigned long Sonar;
	// 354000 - Sound speed (mm/sec)
	// 72000000 - F_CPU
	// 16 - Timer Prescaler
	// Result = mm
	Sonar = (354/2) * (unsigned long)SonarValue / (72000 / 72);
	if (Sonar > 4000) Sonar = 4000;
	if (Sonar < 20) Sonar = 20;

	return (unsigned int)Sonar;
}


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(char *pucBuffer)
{
    while (*pucBuffer)
    {
        USART_SendData(USART1, *pucBuffer++);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
        {
        }
    }
}

void TIM4_IRQHandler(void)
{
        if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
        {
        	sonar_start();
			FLAG_ECHO = 1;
        	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
        }
}

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

	SetSysClockTo72();

    // TIMER4 Двічі за секунду викликає sonar_start(); і встановлює FLAG_ECHO = 1;
    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 = 5000;
    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();
	sonar_init();

    while(1)
    {
    	if (FLAG_ECHO == 1) {
    		sprintf(buffer, "%d\r\n", sonar_get());
    		USARTSend(buffer);

    		FLAG_ECHO = 0;
    	}
    }
}

Interrupt/Event

У STM32, крім звичайного нам Interrupt(переривання) є таке поняття як Events(події). Для того, щоб зрозуміти, що таке Events(події) і навіщо з’явився ще один термін, уявіть, що усі переривання(Interrupts) визвано подіями(Events). Тобто, таймер переповнився – це подія, прилетів байт у USART – це подія. А події можуть визвати переривання. А можуть і не визивати. Тоді навіщо вони? Справа у тому, що події можуть взаємодіяти з периферією напряму без виконання програмного коду, як це роблять переривання. Наприклад подія може запустити перетворення ADC. При цьому зовсім не потрібно викликати обробник переривання. Подія сама може “штовхнути” периферію і не відволікати мікроконтролер від виконання основного коду. Використання подій ми розглянемо пізніше.

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

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

Translate
Архіви

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