Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Настройка прерываний в LPC1778
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
ДЕЙЛ
Листинг в общих чертах такой:

Инициализация и настройка тактирования

1: U0IER|=0x4; //Разрешение прерывания по приёму байта в RX
2: __enable_irq(); //Разрешаем прерывания (CMSIS)
3: __set_BASEPRI(0xFFFFFFFF); //Разрешаем все прерывания (CMSIS)
4: NVIC_EnableIRQ(UART0_IRQn); //Разрешение прерывания UART0 (CMSIS)

while(1)
{
Циклическая отправка данных на ПК
}


5: __irq void UART0_IRQ1(void)
{
for (k=0; k<200; k++)
{
FIO1SET=0x2000000; //Подёргаем ножкой 200 раз
FIO1CLR=0x2000000;
}
}


Хочу сделать так, чтобы при появлении байта в буфере приёма UART вызывалось прерывание. Вот с этим вызовом прерывания проблема и пока не знаю, с какой стороны к ней подходить. Вопросы такие:
1. Правильно ли я выполнил настройки? (строки 1-4)
в строке 4 в качестве параметра функции передаётся UART0_IRQn. Что значит эта n? Её так и нужно оставлять или вместо n нужно какое-то число стаить? Хотя компилятор ругается.

2. Правильно ли я оформил обработчик прерывания? (строка 5). В этой строке пробовал писать название функции от UART0_IRQ1 ло UART0_IRQ10. Компилятор не ругался, но и толку никакого не было, на осциллографе не видел дёрганий ножки после отправки байта с ПК.
На что влияет выделенное число?


Данные в буфер приёма однозначно приходят правильные с ПК.
На скрине моя программа в сыром виде. При нажатии кнопки "ТЕСТОВАЯ" с COM-порта отправляется 1 байт 0xAA, который я вижу принятым программой от МК (выделено). Т.е. с приёмом нормально всё, но не совсем удобно каждый раз смотреть на буфер, куда удобнее считывать из него байт по прерыванию. Ну и с настройкой прерываний разобраться нужно.

И ещё имеется такой регистр
//SETENA0=0xFFFFFFFF;
В описании написано, что каждый бит этого регистра разрешает или запрещает соответствующее прерывание. Как понять, к какому прерыванию относится конкретный бит?
ДЕЙЛ
Сегодня утром вчитался получше в описание регистра BASEPRI

Похоже, что вместо __set_BASEPRI(0xFFFFFFFF); надо было написать __set_BASEPRI(0); или не трогать его, оставив по умолчанию 0.
menzoda
Цитата(ДЕЙЛ @ Jul 7 2014, 23:43) *
Правильно ли я оформил обработчик прерывания? (строка 5). В этой строке пробовал писать название функции от UART0_IRQ1 ло UART0_IRQ10. Компилятор не ругался, но и толку никакого не было, на осциллографе не видел дёрганий ножки после отправки байта с ПК.
На что влияет выделенное число?


Не правильно. Ни на что не влияет. Для Cortex-M обработчики всех прерываний задаются статически, обычно в startup.s-файле, но можно это сделать и в C-файле. В стандартном startup.s обработчики помечены модификатором WEAK. Не вдаваясь в подробности это значит, что если определить свою функцию с таким же именем, то будет вызываться она, иначе будет вызываться обработчик по умолчанию, который в startup.s.

Значит, тебе нужно заглянуть в этот файл и посмотреть имя интересующего тебя обработчика, никаких гаданий и переборов тут не нужно. Когда найдешь имя - реализуешь свою функцию с таким же именем, это и будет обработчиком. Никаких __irq не нужно, это было актуально для ARM7 (но все же лучше уточни, а то я давно этим занимался, может уже позабыл чего). Естественно можно при желании периименовать обработчик как душе угодно, можно выкинуть обработчик по умолчанию, можно еще много чего, но это наверное уже не входит в твои планы.

Ах, да. В прерывании возможно потребуется сбрасывать флаг прерывания. Я тут тоже не очень помню, в документацию лезть неохота, посмотри сам.
ДЕЙЛ
Цитата(menzoda @ Jul 8 2014, 12:33) *
Никаких __irq не нужно, это было актуально для ARM7 (но все же лучше уточни, а то я давно этим занимался, может уже позабыл чего)

как тогда указать компилятору, что эта функция выполняется по прерыванию?
в startup откопал вот эту функцию: UART0_IRQHandler
Только в модуле UART0 несколько источников прерываний, скрин описаний в прикреплённом файле.
Как программе указать, что прерывание нужно именно после приёма байта?

Цитата(ДЕЙЛ @ Jul 7 2014, 23:43) *
И ещё имеется такой регистр
//SETENA0=0xFFFFFFFF;
В описании написано, что каждый бит этого регистра разрешает или запрещает соответствующее прерывание. Как понять, к какому прерыванию относится конкретный бит?

по этому вопросу откопал в даташите:
menzoda
Цитата(ДЕЙЛ @ Jul 8 2014, 15:05) *
как тогда указать компилятору, что эта функция выполняется по прерыванию?

Никак. Она уже указана в startup. В архитектуре ARMv4 (ARM7) __irq был нужен потому, что функция-обработчик сама должна была сохранять текущие значения регистров ядра в стэке, а потом сама же их восстанавливать. Атрибут __irq говорил компилятору, чтобы он вставил специальный код в начале и в конце функции. В архитектуре ARMv7-M (Cortex-M) ядро само гоняет туда-сюда регистры, и поэтому от функции-обработчика ничего не требуется, что бы отличало ее от простой функции, то есть указывать ничего не надо.

Цитата(ДЕЙЛ @ Jul 8 2014, 15:05) *
в startup откопал вот эту функцию: UART0_IRQHandler. Только в модуле UART0 несколько источников прерываний, скрин описаний в прикреплённом файле.
Как программе указать, что прерывание нужно именно после приёма байта?

Никак. Нужно самому проверять флаги прерываний в соответствующем регистре UART. К примеру, стоит флажок №1 - значит причиной прерывания является прием символа, стоит флажок №2 - причиной является завершение отправки символа, стоят флажки №1 и №2 - значит оба события возникли (практически) одновременно. В последнем случае можно обработать сразу и то и другое событие, а можно обрабатывать их по отдельности (обрабатываешь одно событие, флаг второго остается несброшенным, при выходе из обработчика сразу возникает это же прерывание, во флагах торчит уже только одно второе событие, обрабатываешь его, выходишь).
mempfis_
Цитата(ДЕЙЛ @ Jul 8 2014, 14:05) *
как тогда указать компилятору, что эта функция выполняется по прерыванию?
в startup откопал вот эту функцию: UART0_IRQHandler
Только в модуле UART0 несколько источников прерываний, скрин описаний в прикреплённом файле.
Как программе указать, что прерывание нужно именно после приёма байта?


Вот пример обработчика прерывания UART0 LPC17xx.
Название обработчика совпадает с названием в стартапе UART0_IRQHandler.
В ARM на одну переферию обычно идёт один вектор прерываний.
А там по флагам разгребают в чём именно причина прерывания.
Вам ранее уже много чего рассказали про прерывания в CORTEX-M3. И всё что было рассказано сущая правда.
Для приёма байта нужно разрешить прерывания IER_RBR и IER_RLS. Посмотрите внимательно на обработчик прерывания и будет ясно почему именно эти прерывания.

С отправкой данных интересней. Если Вы разрешите IER_THRE прерывание, то никогда не вылезите из обработчика т.к. пустой передающий буффер будет непрерывно генерить прерывание. Поэтому для передачи данные закидывают в буффер и разрешают IER_THRE. а в обработчике прерывания мониторят наличие данных на передачу и запрещают IER_THRE, когда данных больше нет.

CODE
void UART0_IRQHandler (void)
{

unsigned char IIRValue, LSRValue;
volatile unsigned char Dummy;


IIRValue = U0IIR;
IIRValue >>= 1; /* skip pending bit in IIR */
IIRValue &= 0x07; /* check bit 1~3, interrupt identification */
if ( IIRValue == IIR_RLS ) /* Receive Line Status */
{
LSRValue = U0LSR;
/* Receive Line Status */
if ( LSRValue & (LSR_OE|LSR_PE|LSR_FE|LSR_RXFE|LSR_BI) )
{
/* There are errors or break interrupt */
/* Read LSR will clear the interrupt */
Dummy = U0RBR; /* Dummy read on RX to clear interrupt, then bail out */


NVIC_ClrPend(NVIC_UART0);
return;
}

if ( LSRValue & LSR_RDR ) /* Receive Data Ready */
{
/* If no error on RLS, normal ready, save into the data buffer. */
/* Note: read RBR will clear the interrupt */

//приём завершён, ложим байт в фифо
Rx0Buf[Rx0Head++] = U0RBR;
if (Rx0Head >= US0_RXBUFSIZE) Rx0Head = 0;

}
}
else if ( IIRValue == IIR_RDA ) /* Receive Data Available */
{
/* Receive Data Available */

//приём завершён, ложим байт в фифо
Rx0Buf[Rx0Head++] = U0RBR;
if (Rx0Head >= US0_RXBUFSIZE) Rx0Head = 0;

}
else if ( IIRValue == IIR_CTI ) /* Character timeout indicator */
{
/* Character Time-out indicator */
/* Bit 9 as the CTI error */
}
else if ( IIRValue == IIR_THRE ) /* THRE, transmit holding register empty */
{
/* THRE interrupt */
LSRValue = U0LSR; /* Check status in the LSR to see if valid data in U3THR or not */
if ( LSRValue & LSR_THRE )
{

//передача байта завершена
if (Tx0Tail != Tx0Head)
{
U0THR = Tx0Buf[Tx0Tail++];
if (Tx0Tail >= US0_TXBUFSIZE) Tx0Tail = 0;
}
else
{
//запрещаем прерывания по опустошению передающего буффера
U0IER &= ~IER_THRE;
}

}
}

NVIC_ClrPend(NVIC_UART0);
return;
}
ДЕЙЛ
Цитата(mempfis_ @ Jul 8 2014, 18:29) *
Вот пример обработчика прерывания UART0 LPC17xx.

Для меня это уже следующий этап, сейчас нужно как-то зайти в этот обработчик. Насчёт источников прерывания тоже понятно - аналогия с MSP430, где двум портам соответствуют два вектора, а в обработчике уже разбор битов ножек.

Упростил свою программу до такоо вида, оставив саму суть вопроса:

CODE
#include "C:\10062014LPC\iolpc1778.h"
#include "C:\10062014LPC\src\inc\LPC17xx.h"
int N, pin;
void main(void)
{

//INIT
//Настройка ножек
IOCON_P1_25=0; //PIO
FIO1DIR=0x2200000; //Выходы PIN1_25, PIN1_11
PCONP|=0x8; //Питание модуля UART0
IOCON_P0_02=0x1; //На ножках переферийный модуль (TX, RX)
IOCON_P0_03=0x1;
FIO0DIR=0x4+0x8;

//Настройка тактирования
SCS|=0x20; //подключение осциллятора
while(!(SCS&0x40)){} //ожидание запуска

PCLKSEL=1;
CLKSRCSEL|=1;
U0LCR|=0x83; //разрешение доступа к делителю
U0DLL=0x14; //настройка делителя
U0LCR&=~0x80; //запрет доступа к делителю

U0IER|=0x1+0x4; //Разрешение прерываний UART0
__set_PRIMASK(0); //Разрешение
__set_FAULTMASK(0); //всех прерываний
__set_BASEPRI(0); //Отключение маскирования
NVIC_EnableIRQ(UART0_IRQn); //Enable IRQ UART0 (ISER0=32)

pin=0x200000; //изначально импульсы на ножке P1.11



while(1)
{
FIO1CLR=pin; //0
FIO1SET=pin; //1
FIO1CLR=pin; //0
}
}

void UART0_IRQHandler(void)
{
N=U0RBR; //прочитаем на всякий случай принятое

pin=0x200000+0x2000000; //после выхода из прерывания
//точно такие же импульсы появляются на P1.25

NVIC_ClearPendingIRQ(UART0_IRQn);
return;
}

По моей задумке при выполнении этой программы на одной ножке кристалла (P1.11) в бесконечном цикле генерируются прямоугольные импульсы. Эта часть работает, что наблюдаю на осциллографе. При отправке одного байта с ПК в прерывании выполняется операция, после которой в основной программе в бесконечном цикле точно такие же импульсы формируются на второй ножке(P1.25), т.е. на осциллографе второй канал станет рисовать копию первого сигнала. Это будет оворить о том, что был выполнен вход в прерывание, выполнились соответствующие действия и нормально вышли в главную программу.

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

Что я не досмотрел в коде?

Цитата(menzoda @ Jul 8 2014, 12:33) *
Ах, да. В прерывании возможно потребуется сбрасывать флаг прерывания. Я тут тоже не очень помню, в документацию лезть неохота, посмотри сам.

вот это не глянул, утром почитаю и вечером попробую, рабочий ноут на проверку диска поставил, но всё же хотелось бы почитать критику моего кода со стороны более опытных коллег sm.gif
menzoda
Критиковать пока особо нечего, это же просто наброски для того, чтобы разобраться с периферией. Разве что, не нужно в инклюдах использовать абсолютные пути, их вообще надо везде где можно избегать: перенесешь проект в другое место и замучаешся править. После while не нужно фигурные скобки ставить, достаточно точки с запятой, так читается легче. Операторы (арифметические, логические, присвоение и т.д.) лучше окружать пробелами, иначе код превращается в кашу, очень трудно читать. В будущем группируй функционал программы в разных функциях и модулях, например, сделай модуль system.c/system.h в котором будут отдельные функции (возможно параметризированные) для базовой инициализации системы. Что-то вроде system_set_sysclk(int clock), system_init(void), system_init_gpio(void) и тому подобное. Выравни комментарии по вертикали, постарайся уменьшить их количество не потеряв в понятности происходящего (разбиение на функции тут как раз поможет). Присвоение регистрам конструкций вида 0x1+0x4 выглядит сомнительно: я должен каждый раз в уме считать результат. Как-то так.

Но пока не работает - при отправке байта процессор зависает и бесконечный цикл главной программы останавливается, импульсы не формируются.
1. Есть отладчик? Можно поставить точки остановки в разных местах и посмотреть где он зависает. Если нету и ты намерен часто общаться с ARM - то покупай. Можно совсем дешевый китайский MT-Link или что-то аналогичное. Если боишся потенциального геморроя с клоном отладчика (слет прошивки и т.п.), то покупай оригинальный J-Link EDU - версию стандартного J-Link, предназначенную для обучения. Из ограничений основное - это лицензионное соглашение, что ты не будешь делать коммерческое ПО. Стоит около 3000 рублей.

2. Поставь в прерывании бесконечный цикл с дерганьем. Если программа все-таки заходит в прерывание, то ты это хотя бы увидишь.

3. Убери цикл из прерывания и поочередно поставь в обработчики исключений (Data Abort, Prefetch Abort и т.д., не помню их названия, первые несколько векторов прерываний). Это нужно для того, чтобы убедится, что ядро не вылетает с каким-нибудь исключением (маловероятно, но все же).

Больше идей пока нету.
ДЕЙЛ
Прогнал в отладчике через JTAG пошагово.

Запустил, отправил байт и сделал Break. Программа зависла в указанном на рисунке месте и никуда не сдвигается. В чём тут может быть причина? В обработчик программа не входит, т.к. переменная pin не изменилась. Каким образом мой байт 0xAA всегда попадает в регистр U0DLL? Это же не приёмный буфер, а делитель.

mempfis_, можно увидеть Ваш код программы, к которой относится показанный обработчик?
menzoda
Все логично. Она застряла в бесконечном цикле обработчика по умолчанию, видишь, там всего одна команда безусловного перехода на себя (B UART0_IRQHandler). А вот почему компоновщик использовал эту метку вместо твоей мне не понятно (я тут использую слово "метка", потому что с точки зрения компоновщика никаких функций нету, только участки кода и метки, указывающие на них). Можешь попробовать выкинуть обработчик по умолчанию и посмотреть, что будет. Для этого закомментируй это:
Код
    PUBWEAK UART0_IRQHandler
    SECTION .text:CODE:REORDER(1)
UART0_IRQHandler
    B UART0_IRQHandler


и добавь это перед таблицей векторов прерываний (туда, где EXTERN __iar_program_start и EXTERN SystemInit):
Код
    EXTERN UART0_IRQHandler


Эй, стопэ! Только сейчас посмотрел твойпроект. Зачем ты сделал main файл C++? Функции C++ нельзя просто так вызывать из С или ассемблера. Сделай файл сишным, или добавь перед функцией extern "C". Предыдущий совет по удалению обработчика по умолчанию можешь не воплощать в жизнь.
ДЕЙЛ
Вечером попробую. Интересно узнать ещё мнение насчёт моего байта 0xAA в регистре U0DLL, который по логике должен находиться в U0RBR.
ДЕЙЛ
Цитата(menzoda @ Jul 10 2014, 09:05) *
Эй, стопэ! Только сейчас посмотрел твойпроект. Зачем ты сделал main файл C++? Функции C++ нельзя просто так вызывать из С или ассемблера. Сделай файл сишным, или добавь перед функцией extern "C".

Заработало 08.gif Спасибо большое!
ДЕЙЛ
Продолжаю начинать осваивать LPC. Читаю про вложенные прерывания. Логику работы я понимаю так: если во время выполнения прерывания появляется запрос от источника с более высоким приоритетом, то текущая процедура обработчика приостанавливается на время выполнения обработчика более высокоприоритетного прерывания и после этого продолжается выполнение. Более высокоприоритетное прерывание приостанавливает работу низкоприоритетного так же, как обычное прерывание приостанавливает главную программу в 8битных МК?

написал вот такой код:
Код
#include "C:\10062014LPC\iolpc1778.h"
#include "C:\10062014LPC\src\inc\LPC17xx.h"
void main(void)
{
    
{ //INIT
//Инициализация ножек
IOCON_P1_25 = 0; //PIO
FIO1DIR     = 0x2200000; //Выходы PIN1_25, PIN1_11
PCONP      |= 0x8;
IOCON_P0_02 = 0x1;
IOCON_P0_03 = 0x1;
FIO0DIR     = 0x4+0x8;

//Настройка тактирования
SCS        |= 0x20; //подключение осциллятора
while(!(SCS&0x40)){} //ожидание запуска

PLL0CON   |= 0x01;
PLL0CFG   |= 0x09;
PLL0FEED   = 0xAA;  
PLL0FEED   = 0x55;    
CCLKSEL   |= 0x100;
PCLKSEL    = 1;
CLKSRCSEL |= 1;

//Настройка UART0
U0LCR     |= 0x83;   //razrecchenie dostupa k delitely
U0DLL      = 0xC8;   //0xF0;//0xA0; //0x14;  //0x4E;  //nastrojka delitelya
U0LCR     &=~0x80;   //zapret dostupa k delitely
U0IER     |= 0x1+0x4;//Разрешение прерываний UART0

NVIC_EnableIRQ(UART0_IRQn);    //Enable IRQ UART0 (ISER0=32)

//Настройка системного таймера
SYSTICKCSR  = 0;
SYSTICKRVR  = 0xF423F;
SYSTICKCVR  = 2;
SYSTICKCSR |= 7;
}

//Пустой бесконечный цикл
while(1){ }
}
/////////////////////////////  IRQ  /////////////////////////////////////

//IRQ_UART0
void UART0_IRQHandler(void)
{
for (int i=0; i<2; i++) //2 импульса (канал_2 осциллографа)
  {  
  FIO1CLR   = 0x2200000; //pin; //0  
  FIO1SET   = 0x2000000; //pin; //1    
  FIO1CLR   = 0x2200000; //pin; //0
  }

  while(1) {} //здесь должна висеть процедура обработчика IRQ_UART0
  
  NVIC_ClearPendingIRQ(UART0_IRQn);
  return;
}

//IRQ_SYSTICK
void SysTick_Handler(void)
{ //1 импульс (канал_1 осциллографа)
  FIO1CLR   = 0x2200000; //pin; //0  
  FIO1SET   = 0x200000; //pin; //1
  FIO1CLR   = 0x2200000; //pin; //0
  return;
}


Логика задумана такая: имеются два прерывания - UART0 и от системного таймера. После запуска и инициализации в первом канале осциллографа вижу работу обработчика прерываний системного таймера - короткие импульсы с частотой 120Гц. Тут всё нормально. Далее отправляю байт на вход UART0, во втором канале появляются два импульса, т.е. в прерывание вошли и после этого в обработчике стоит бесконечный цикл while(1) {}. Это для того, чтобы МК не выходил из обработчика IRQ_UART0. Но почему-то данный бесконечный цикл не прерывается для выполнения прерывания от системного таймера. В чём тут может быть проблема? Неправильная настройка или я неправильно понял смысл вложенности прерываний?
menzoda
Код
NVIC_EnableIRQ(UART0_IRQn);    //Enable IRQ UART0 (ISER0=32)

Не вижу аналогичной конструкции для SysTick.

Код
NVIC_ClearPendingIRQ(UART0_IRQn);

Это обычно добавляют в начало обработчика. Для SysTick наверное тоже надо добавить соответствующую конструкцию.


И все же, как прерывания могут работать без extern "C" в cpp-файле? Если ты просто в настройках указал, чтобы cpp-файлы считались сишными, то это очень плохо. Не надо вводить людей в заблуждение, *.cpp - это C++, *.с - это C. Или пишешь на C, и тогда у тебя файлы имеют расширение *.c и не надо никаких extern, или пишешь на C++, при этом файлы у тебя *.cpp и в коде стоят extern "C".
ДЕЙЛ
Цитата(menzoda @ Jul 16 2014, 10:12) *
Код
NVIC_EnableIRQ(UART0_IRQn);    //Enable IRQ UART0 (ISER0=32)

Не вижу аналогичной конструкции для SysTick.

Данная функция нужна для разрешения исключений с номером 16 и выше, как я понял. К системному таймеру относится исключение №15.
Чуть выше была картинка из даташита - http://electronix.ru/forum/index.php?act=a...st&id=85886 Судя по этому, аналогичное разрешение прерывания системного таймера не требуется в виде такой функции - его приоритет выше строжевого таймера.

Цитата(menzoda @ Jul 16 2014, 10:12) *
Код
NVIC_ClearPendingIRQ(UART0_IRQn);

Это обычно добавляют в начало обработчика. Для SysTick наверное тоже надо добавить соответствующую конструкцию.

Пробовал вставлять такую же функцию для SysTick - cрабатывает прерывание Hard Fault. NVIC_ClearPendingIRQ(UART0_IRQn) - можно и в начало поставить, но насколько это принципиально? Это просто сброс бита прерывания, в данном случае прерывания от UART0, чтобы после завершения текущего обработчика он не вызвался снова.

Цитата(menzoda @ Jul 16 2014, 10:12) *
И все же, как прерывания могут работать без extern "C" в cpp-файле? Если ты просто в настройках указал, чтобы cpp-файлы считались сишными, то это очень плохо. Не надо вводить людей в заблуждение, *.cpp - это C++, *.с - это C. Или пишешь на C, и тогда у тебя файлы имеют расширение *.c и не надо никаких extern, или пишешь на C++, при этом файлы у тебя *.cpp и в коде стоят extern "C".

Прерывания работают и вызываются в данном случае, чуть выше в этой теме была такая проблема. Если в низкоприоритетном обработчике IRQ_UART0 не ставить while(1){}, то всё нормально - таймер тикает, принятый байт тоже вызывает прерывание. Бесконечный пустой цикл поставил в обработчике IRQ_UART0 намеренно, чтобы посмотреть, как будет вести себя более высокоприоритетный обработчик IRQ_SysTick во время работы зависшего в бесконечном цикле while(1) обработчика IRQ_UART0.
menzoda
Цитата(ДЕЙЛ @ Jul 16 2014, 11:12) *
Данная функция нужна для разрешения исключений с номером 16 и выше, как я понял.

Да, действительно. Исключения (внутренние прерывания) работают не так как внешние прерывания, то есть Enable делать не нужно...

Цитата(ДЕЙЛ @ Jul 16 2014, 11:12) *
Пробовал вставлять такую же функцию для SysTick - cрабатывает прерывание Hard Fault.

...и Clear Pend наверное тоже не нужно. Мне просто казалось, что там все однообразно делается.

Цитата(ДЕЙЛ @ Jul 16 2014, 11:12) *
NVIC_ClearPendingIRQ(UART0_IRQn) - можно и в начало поставить, но насколько это принципиально?

В данном случае не принципиально, но вообще можно придумать такую ситуацию, когда прерывание начнет бесконечно вытеснять самого себя. Не знаю возможно ли такое именно с NVIC, но в других МК я нарывался на подобное поведение. Например в TMS32F28x от TI, заходишь в обработчик прерывания, при этом все прерывания автоматом запрещаются, и чтобы разрешить вытеснение нужно их заново разрешить. Так вот, разрешаешь прерывания и тут же срабатывает это же прерывание, флаг же не сброшен, и так до бесконечности. Конечно, это несколько притянуто за уши, но все же. Кроме того, в рекомендованном Keil'ом варианте реализации вложенных прерываний для старичка ARM7, настоятельно рекомендовалось первым делом сбросить флаг. В общем, перенос этой процедуры в начало хуже не сделает, а вот если она будет в конце, то в некоторых условиях возможны интересные эффекты.

Если по делу, то идей почему не работает Sys Tick у меня нету. Включать прерывание, выходит, не надо, да оно по твоим словам уже и так работает, только вот не вытесняет. Хм, пока я смотрел действительно ли не нужно его включать, перед глазами промелькнул код в котором для него задавались приоритеты, а у тебя такого нету, но может я опять чего-то путаю. Во всяком случае поиск выдал кучу статей по настройке этого Sys Tick, можно и подсмотреть.
ДЕЙЛ
Цитата(menzoda @ Jul 16 2014, 15:40) *
Хм, пока я смотрел действительно ли не нужно его включать, перед глазами промелькнул код в котором для него задавались приоритеты, а у тебя такого нету, но может я опять чего-то путаю. Во всяком случае поиск выдал кучу статей по настройке этого Sys Tick, можно и подсмотреть.

Приоритеты прерываний не менял - они по умолчанию уже установлены. Пробовал менять местами обработчики, т.е. зацикливание делал в обработчике системного таймера, а отправка байта в UART0 вызывала соответствующее прерывание, которое по идее должно было бы вытеснить IRQ_SysTick, если бы у него был бы более низкий приоритет. Равенство приоритетов этих двух прерываний слишком маловероятно, поэтому руководствовался документацией, в которой IRQ_SysTick имеет приоритет 15, а UART0 - 21. В LPC17XX.h указан аналогичное соотношение приоритетов
Код
SysTick_IRQn                  = -1,       /*!< 15 Cortex-M3 System Tick Interrupt            

..........
UART0_IRQn                    = 5,        /*!< UART0 Interrupt


Может быть нужно перевести главную программу в режим потока установкой бита CONTROL[0]? Сейчас главная программа работает в режиме обработчика, как и должно быть по умолчанию после сброса. Просто в данный момент нет платы под рукой, чтобы проверить.
ДЕЙЛ
На другом форуме сказали посмотреть регистры уровней приоритетов PRI_x. Думаю, что у меня было заблуждение насчёт приоритетов прерываний от системного таймера, входящего в состав ядра и от переферийного модуля. Вполне возможно, что по умолчанию у всех прерываний с программируемым уровнем приоритета он одинаковый и равен нулю. Вечером отпишусь насчёт этой версии после проверки.
ДЕЙЛ
С уровнями приоритетов разобрался. Ошибка была в том, что я заблуждался относительно предустановки уровней приоритетов. По умолчанию все программируемые прерывания имеют приоритет 0, т.е. самый высокий. Выше только немаскируемые с приоритетами -1 -2 и -3. А у меня получилось, что прерывание от UART0 и от системного таймера имели одинаковый приоритет и поэтому не было вложенности.

Добавил две строки в коде при инициализации:
Код
AIRCR = 0x05FA0200;  //приоритет группы - поле [7:3], субприоритеты отключены, т.к. [2:0] не задействованы
IP1   = 0x800;           //уровень приоритета - 1, биты [2-0] не задействованы.

Остался открытым вопрос по настройке приоритета прерывания системного таймера. Он программируемый, судя по описанию ядра CORTEX-M3, но в даташите пока не откопал ничего по этому поводу. Ещё в описании рекомендуют указывать размер стека. Как он указывается?
menzoda
В даташите про него ничего не будет, надо искать в документации на ядро. Насчет размера стека, он указывается в единственном месте - в стартапе (точнее не размер, а границы), и то непонятно зачем, больше не знаю где его можно задать.
ДЕЙЛ
Цитата(menzoda @ Jul 17 2014, 21:59) *
В даташите про него ничего не будет, надо искать в документации на ядро. Насчет размера стека, он указывается в единственном месте - в стартапе (точнее не размер, а границы), и то непонятно зачем, больше не знаю где его можно задать.

в описании ядра сказано, что при многоуровневой вложенности нужно думать о размере стека, вот и интересуюсь. Какая функция задаёт этот размер?
ПЫСЫ Имел ввиду не даташит, а user manual, т.е. руководство пользователя
menzoda
Никакая. Точнее так, можно сообщить библиотеке времени исполнения (CRT) границы стека, реализоваав _user_initial_stackheap, но имелось в виду не это. Размеры стека не указываются никакой функцией, под стек выделяется отдельная секция (какая - можно посмотреть в startup), размеры этой секции задаются в настроечном файле компоновщика (scatter-файл, *.sct). Проверить, хватает ли текущего размера стека не совсем тривиальная задача, никаких явных сообщений о переполнении стека не будет, нужно выкручиваться самому.

Например, перед началом работы программы заполняем стек одинаковым, по возможности редко встречающимся магическим значением. Запускаем программу и нагружаем ее как можно сильнее. После некоторого времени останавливаем и смотрим на область памяти со стеком: там где магические числа будут затерты - поработала наша программа. Считаем длину оставшейся незатертой части, если осталось совсем мало, или даже совсем не осталось, то лучше увеличить размер стека, иначе можно оставить все как есть.

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

Третий вариант - прикинуть эмперически, какова максимальная вложенность вызовов может наблюдаться в твоей программе. Например, в фоне крутиться функция Foo, использующая 12 байт стека, которая вызывает функцию Bar, использующую 15 байт стека. Получается, в фоне максимальная вложенность равна двум функцииям, при этом максимальное потребление стека 12 + 15 = 27 байт стека. Но есть еще прерывания: Tar и Gar, потребляющие 30 и 40 байт стека соответственно. Прерывания могут вытеснять друг-друга, а также прерывать фоновую задачу, в итоге имеем максимальную вложенность Foo + Bar + Tar + Gar и максимальное потребление стека 12 + 15 + 30 + 40 = 97 байт. Размер стека выбираем соответствующий, только нужно не забывать, что прерывания используют стек для сохранения текущих значений регистров ЦПУ, и учитывать это в вычислениях потребляемой памяти.

В общем то ARM имел в виду именно это - если у тебя прерывания начинают вытеснять друг-друга, то вложенность вызовов и соответственно потребление стека могут значительно увеличиться.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.