Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Прерывание таймера Cortex-M3
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
karavaevas
Уважаемые специалисты,
я имею некоторый опыт работы с AVR и теперь пытаюсь освоить ARM. У меня AT91SAM3X8E и IAR EWARM 7.3.
Благодаря вашей помощи мне удалось продвинуться до рабочего проекта, переключающего состояние линии ввода-вывода, при последовательном опросе события сравнения "RC compare" таймера TC0.
Теперь мне нужно заменить последовательный опрос прерыванием, но я пока не понимаю, как это сделать.
На основании изучения немногочисленных примеров и документации у меня сложились такие представления:
1) нужно сказать TC0->TC_CHANNEL->TC_IER|=0x00000010; // Разрешено прерывание RC compare.
2) Нужно установить глобальный флаг маскируемых перрываний, используя, например, intrinsic функцию (-и) "__enable_interrupt();" и/или "__enable_fiq();" (не понимаю пока, какую или обе ) или ассемблерную вставку (типа asm("sei") в AVR)
3) Нужно написать функцию обработчик, причем пока совершенно не понимаю, как она оформляется. Найденные мной примеры инкапсулируют функционал и предлагают подключать некие библиотеки и действовать через функции в этих библиотеках, чего мне совсем не хотелось бы делать на этапе освоения железа.
4) Правильно ли я понимаю, что за всем блоком TC0 закреплено одно и только одно прерывание, а понять, какое конкретное событие (например, сравнение RC или переполнение), возбудило это прерывание можно только анализируя флаги в регистре статуса внутри обработчика этого прерывания? Если это так, то для всех ли ARM Cortex-Mxx это характерно?

Привожу свой рабочий код, который нужно модифицировать:
Код
#define __SAM3X8E__
#include <iosam3xa.h>

#define BITON(X, N)    (X)|=(1<<(N))
#define BITOFF(X, N)   (X)&=~(1<<(N))
#define GETBIT(X, N)   ((X&(1<<(N)))&&1)

void main(){
  WDT->WDT_MR=0x00008FFF; //Выключить сторожевой таймер
  
  // Переключаемся на внешний кварц (12МГц)
  PMC->CKGR_MOR=0x0137FF01;               // Включить внешний кварц 3-20 MHz (у нас 12 MHz) и выключить внутренний RC-генератор
  while (!(0x00000001&PMC->PMC_SR));      // Дождаться стабилизации кварца
  
  
  // Настройка ФАПЧ
  PMC->CKGR_PLLAR = 0x200A3F03;           // MUL=10, DIV=3 PLLACLK= 12 MHz*(MUL+1)/DIV = 44 MHz (но потом делится пополам в PMC_MCKR - PLLADIV2=1)
  while (!(0x00000002&PMC->PMC_SR));      // Дождаться захвата ФАПЧ
  
  // Выбираем вход Master Clock - PLLA без деления. Установка в 2 этапа - сначала смена делителей, потом смена источника такта
  PMC->PMC_MCKR=0x00001001;               // Выход Master Clock = Main Clock c делителем PLL на 2 (бит PLLADIV2=1)
  while (!(0x00000008&PMC->PMC_SR));      // Дождаться  установки Master Clock
  PMC->PMC_MCKR=0x00001002;               // Выход Master Clock = PLLA c делителем PLL на 2 (бит PLLADIV2=1)
  while (!(0x00000008&PMC->PMC_SR));      // Дождаться  установки Master Clock
  
  PMC->PMC_PCER0=1<<13; // Тактировать PIOC
  PMC->PMC_PCER0=1<<27; // Тактировать TC0
  
  PIOC->PIO_OER=1<<12; // PC12 as output pin
  PIOC->PIO_ODR=1<<13; // PC13 as input pin
  PIOC->PIO_PER=3<<12; // PC12, PC13 as PIO pins
  
  TC0->TC_CHANNEL->TC_RC=1000;         // Загружаем регистр сравнения RC (11MHz/1000 =11000 раз в сек событие сравнения RC)
  TC0->TC_CHANNEL->TC_CMR=0x0000C4000; // Timer_Clock1 selected (MCK/2=11MHz), Waveform Enabled, Up mode with autotrigger (autoreset) on RC

  while (GETBIT(PIOC->PIO_PDSR, 13));  // Ждем, пока не замкнута на GND тактовая кнопка на PC13
  TC0->TC_CHANNEL->TC_CCR=0x00000005;  //CLKEN=1, SWTRG=1 - RESET+Start
  
  while(1){
    if (TC0->TC_CHANNEL->TC_SR&0x00000010){ // Если было сравнение с RC (флаг автоматически сбрасывается после чтения регистра)
      if (GETBIT(PIOC->PIO_ODSR, 12))       // переключаем состояние ноги PC12
        PIOC->PIO_CODR=1<<12;
      else
        PIOC->PIO_SODR=1<<12;
    }  
  }
}

karavaevas
Мужики, ну подскажите же кто-нибудь, неужели никто на IAR не писал прерывания?! Затык полный, брожу третьи сутки в 3 соснах.
Не уверен, что понимаю, как объявлять обработчик. Судя по тому, что пишут (на сколько я понял), надо объявить функцию-обработчик со строго определенным названием, которое можно почерпнуть в некоем h-файле. Нашел в "sam3x8e.h" раздел "/* Peripherals handlers */", взял оттуда название, написал функцию, но не работает код. Может я неверно разрешаю прерывания? (Разрешить TC0_IRQn в NVIC + поставить флаг разрешения прерывания RC compare в регистрах TC0 + глобально разрешить прерывания - вроде я все выполнил).
Мой неработающий код (настройки портов и тактирования правильные - проверил в режиме последовательного опроса таймера):

Код
#define __SAM3X8E__
#include <iosam3xa.h>

#define BITON(X, N)    (X)|=(1<<(N))
#define BITOFF(X, N)   (X)&=~(1<<(N))
#define GETBIT(X, N)   ((X&(1<<(N)))&&1)

void TC0_Handler(void){
      if (GETBIT(PIOC->PIO_ODSR, 12))       // переключаем состояние ноги PC12
        PIOC->PIO_CODR=1<<12;
      else
        PIOC->PIO_SODR=1<<12;
    
      //NVIC_ClearPendingIRQ(TC0_IRQn);
}

void main(){
  WDT->WDT_MR=0x00008FFF; //Выключить сторожевой таймер
  
  // Переключаемся на внешний кварц (12МГц)
  PMC->CKGR_MOR=0x0137FF01;               // Включить внешний кварц 3-20 MHz (у нас 12 MHz) и выключить внутренний RC-генератор
  while (!(0x00000001&PMC->PMC_SR));      // Дождаться стабилизации кварца
  
  // Настройка ФАПЧ
  PMC->CKGR_PLLAR = 0x200A3F03;           // MUL=10, DIV=3 PLLACLK= 12 MHz*(MUL+1)/DIV = 44 MHz (но потом делится пополам в PMC_MCKR - PLLADIV2=1)
  while (!(0x00000002&PMC->PMC_SR));      // Дождаться захвата ФАПЧ
  
  // Выбираем вход Master Clock - PLLA без деления. Установка в 2 этапа - сначала смена делителей, потом смена источника такта
  PMC->PMC_MCKR=0x00001001;               // Выход Master Clock = Main Clock c делителем PLL на 2 (бит PLLADIV2=1)
  while (!(0x00000008&PMC->PMC_SR));      // Дождаться  установки Master Clock
  PMC->PMC_MCKR=0x00001002;               // Выход Master Clock = PLLA c делителем PLL на 2 (бит PLLADIV2=1)
  while (!(0x00000008&PMC->PMC_SR));      // Дождаться  установки Master Clock
  
  PMC->PMC_PCER0=1<<13; // Тактировать PIOC
  PMC->PMC_PCER0=1<<27; // Тактировать TC0
  
  PIOC->PIO_OER=1<<12; // PC12 as output pin
  PIOC->PIO_ODR=1<<13; // PC13 as input pin
  PIOC->PIO_PER=3<<12; // PC12, PC13 as PIO pins
  
  TC0->TC_CHANNEL->TC_RC=1000;         // Загружаем регистр сравнения RC (11MHz/1000 =11000 раз в сек событие сравнения RC)
  TC0->TC_CHANNEL->TC_CMR=0x0000C4000; // Timer_Clock1 selected (MCK/2=11MHz), Waveform Enabled, Up mode with autotrigger (autoreset) on RC

  while (GETBIT(PIOC->PIO_PDSR, 13));  // Ждем, пока не замкнута на GND тактовая кнопка на PC13
  TC0->TC_CHANNEL->TC_CCR=0x00000005;  //CLKEN=1, SWTRG=1 - RESET+Start
  
  // Разрешить прерывания
  TC0->TC_CHANNEL->TC_IER|=0x00000010; // Разрешено прерывание RC compare  
  NVIC_EnableIRQ(TC0_IRQn); // Разрешено прерывание TC0 в NVIC
  __enable_interrupt();     // Глобально разрешить маскируемые прерывания
  
  while(1){}
}

megajohn
Цитата(karavaevas @ Dec 23 2014, 10:49) *
Мужики, ну подскажите же кто-нибудь, неужели никто на IAR не писал прерывания?! Затык полный, брожу третьи сутки в 3 соснах.


выложите свой ВЕСЬ проект. Или хотя бы файл что-то вроде sam3x8e.s

дам должно быть типо (пример по LPC1778):
Код
__vector_table_0x1c
...
DCD     TIMER0_IRQHandler
...
        PUBWEAK TIMER0_IRQHandler
        SECTION .text:CODE:REORDER(1)
TIMER0_IRQHandler
        B TIMER0_IRQHandler

karavaevas
Цитата(megajohn @ Dec 23 2014, 10:56) *
выложите свой ВЕСЬ проект. Или хотя бы файл что-то вроде sam3x8e.s

дам должно быть типо (пример по LPC1778):
Код
__vector_table_0x1c
...
DCD     TIMER0_IRQHandler
...
        PUBWEAK TIMER0_IRQHandler
        SECTION .text:CODE:REORDER(1)
TIMER0_IRQHandler
        B TIMER0_IRQHandler


Боюсь, что я весь выложил, больше ничего нет в проекте, все остальное по-умолчанию (ну еще настроил пути к папкам с h-файлами для SAM3X, которые идут в комплекте с IAR).

Обнаружил в яровском "C/C++ Development Guide", что нужно взять за основу файл cstartup_M.c, который с IARом устанавливается, скопировать в папку с проектом, добавить в проект и в файле дописать в таблице векторов имена нужных обработчиков прерываний периферии. Файл нашел (привожу весь ниже), а вот как туда дописывать нужные имена и как их в основной программе использовать не понимаю.
Там еще какую-то "__iar_program_start()" предлагают вызывать, она мне совершенно не нужна, а переход на нее в самом начале таблицы векторов описан. Тоже не понимаю, зачем мне это и можно ли ее выкинуть.

Код
/**************************************************
*
* This file contains an interrupt vector for Cortex-M written in C.
* The actual interrupt functions must be provided by the application developer.
*
* Copyright 2007 IAR Systems. All rights reserved.
*
* $Revision: 66254 $
*
**************************************************/

#pragma language=extended
#pragma segment="CSTACK"

extern void __iar_program_start( void );

extern void NMI_Handler( void );
extern void HardFault_Handler( void );
extern void MemManage_Handler( void );
extern void BusFault_Handler( void );
extern void UsageFault_Handler( void );
extern void SVC_Handler( void );
extern void DebugMon_Handler( void );
extern void PendSV_Handler( void );
extern void SysTick_Handler( void );

typedef void( *intfunc )( void );
typedef union { intfunc __fun; void * __ptr; } intvec_elem;

// The vector table is normally located at address 0.
// When debugging in RAM, it can be located in RAM, aligned to at least 2^6.
// If you need to define interrupt service routines,
// make a copy of this file and include it in your project.
// The name "__vector_table" has special meaning for C-SPY, which
// is where to find the SP start value.
// If vector table is not located at address 0, the user has to initialize
// the  NVIC vector table register (VTOR) before using interrupts.


#pragma location = ".intvec"
const intvec_elem __vector_table[] =
{
  { .__ptr = __sfe( "CSTACK" ) },
  __iar_program_start,

  NMI_Handler,
  HardFault_Handler,
  MemManage_Handler,
  BusFault_Handler,
  UsageFault_Handler,
  0,
  0,
  0,
  0,
  SVC_Handler,
  DebugMon_Handler,
  0,
  PendSV_Handler,
  SysTick_Handler

};

#pragma call_graph_root = "interrupt"
__weak void NMI_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void HardFault_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void MemManage_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void BusFault_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void UsageFault_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void SVC_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void DebugMon_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void PendSV_Handler( void ) { while (1) {} }
#pragma call_graph_root = "interrupt"
__weak void SysTick_Handler( void ) { while (1) {} }


void __cmain( void );
__weak void __iar_init_core( void );
__weak void __iar_init_vfp( void );

#pragma required=__vector_table
void __iar_program_start( void )
{
  __iar_init_core();
  __iar_init_vfp();
  __cmain();
}


megajohn
__iar_program_start() выкидывать нельзя - она инницивлизирует ваши переменные =)

дык по примеру SysTick_Handler добавтье свое прерывание ( но чтобы оно былопо правильному адресу в таблице векторов
esaulenka
Сразу предупреждаю, что IAR я видел лет 5 назад, и то это был IAR AVR ;-)

Цитата(karavaevas @ Dec 24 2014, 06:26) *
Обнаружил в яровском "C/C++ Development Guide", что нужно взять за основу файл cstartup_M.c, который с IARом устанавливается, скопировать в папку с проектом, добавить в проект и в файле дописать в таблице векторов имена нужных обработчиков прерываний периферии.

Всё правильно, только одно НО: не надо дописывать типовой файл, надо поискать где-то рядом такой же файл, уже модифицированный под Ваш AT91SAM3X8E (или линейку AT91SAM3X.. ).
Просто даташит подсказывает, что у этого контроллера 44 вектора прерываний. Аккуратно заполнить их все можно, но зачем эта обезьянья работа?


У Keil'а, кстати, нужное Вам прерывание обзывается TC0_IRQHandler.

И ещё. FIQ у Cortex'ов отсутствует, для разрешения прерываний (если их запретил стандартный стартап, опять же не знаю специфики IAR'а) нужен __enable_irq().
karavaevas
Всем большое спасибо, с вашей помощью разобрался!
Пришлось еще крепко почитать, но наводки здорово помогли, т.к. информацию пришлось собирать из разных источников на основе наводок (datasheet, programmers guide от IAR, книжка Ю Дж., тупое рассматривание содержимого файлов в папках IAR и т.п.sm.gif )
Оказалось: после ресета по умолчанию Cortex-M3 (и вроде бы M0 и M4 аналогично) ожидает предоставления информации в жестком формате: начиная с позиции со смещением 0 в памяти находится 32 битное слово - адрес стека, следующее слово - точка входа в программу, сразу за ним таблица векторов прерываний, которая включает для M3 сначала вектора предопределенных немаскируемых исключений (9 штук+ зарезервированные пропуски) потом несколько прерываний периферии, которых может быть до 240 в зависимости от конкретной микросхемы (в моей, действительно, 44 шт), включая нужное сейчас мне:
Итого, я взял за основу cstartup_M.c, как советовали в мануале, потребовалось дописать объявления этих 44 функций (с любыми названиями) по образу и подобию исключений:
Код
#pragma language=extended
#pragma segment="CSTACK"
extern void NMI_Handler( void );
...
extern void     TC0_IrqHandler( void );    /* 27 Timer Counter 0 */
...
#pragma call_graph_root = "interrupt"
__weak void     TC0_IrqHandler( void ) { while (1) {} }     /* 27 Timer Counter 0 */
...

а от смущавшей меня "__iar_program_start()" я оставил в результате:
Код
void main( void );
#pragma required=__vector_table
void __iar_program_start( void ){main();} //Просто вызываем main()

Оказалось (откровением для меня) что точкой входа в программы для Cortex-M3 у IAR является в общем случае не "main()" а как раз функция "__iar_program_start()", которая содержит по умолчанию каркас для "правильного стиля программирования". Каркас успешно вымарался, чтобы не смущать нежную душу неофита и на мой вкус теперь все логично.
В основной программе написал функцию "TC0_IrqHandler()", трясущую выводами PIO, которая перекрыла "слабое" (__weak) описание и активировал прерывание:
Код
  TC0->TC_CHANNEL->TC_IER|=0x00000010; // Разрешено прерывание RC compare  
  NVIC_EnableIRQ(TC0_IRQn); // Разрешено прерывание TC0 в NVIC
  __enable_interrupt();     // Глобально разрешить маскируемые прерывания


Как обсуждалось на форуме, в обработчике потребовалось явно сбросить флаг события, вызвавшего прерывание в регистре статуса таймера и флаг отложенного прерывания "NVIC_ClearPendingIRQ(TC0_IRQn);"
Заработало!
Всем еще раз спасибо, удачи!
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.