Работаем с MSP430 в Linux-е. Lesson 5

MSP430-GCC прерывания

Основа вытесняющей многозадачности — наличие в системе аппаратных прерываний.

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

Чтобы было более понятно я в качестве примера и для большей наглядности возьму конкретную задачу со своей работы.

Задача состоит в следующем. Имеется некоторое устройство, которое считает количество импульсов, которые приходят извне. Импульсы приходят неравномерно, случайно. Можно говорить только о предельных количествах импульсов в единицу времени. Если за единицу времени, точнее — за период подсчета, мы возьмем одну секунду, то количество поступивших импульсов может быть любое — от нуля и до нескольких десятков. Давайте ограничимся значением равным 32 импульса за одну секунду. Это значение я принял волюнтаритивно, основываясь на практических данных, на своем опыте работы с этими импульсами.

Нам нужно не просто посчитать количество импульсов за единицу времени, а еще записать это количество в буфер. Потом содержимое этого буфера вывести на графический ЖКИ — построить график. Иначе говоря, мы на ЖКИ должны наблюдать в реальном времени интенсивность импульсов. Чем больше импульсов поступает, тем выше должна идти линия графика.

В целях упрощения вывода графического представления я предлагаю выводить данные не в виде графика (колеблющейся линии), а в виде гистограммы (вертикальных столбиков).

Теперь мы должны оговорить еще один момент — как будем определять период подсчета импульсов. Вот тут маячат два варианта, которые по идее оба нужны на производстве. Мы их реализуем оба.

Первый вариант — длительность периода определяется по внутренним «часам» микроконтроллера. И второй вариант — это по внешнему сигналу. Предполагается, что сигнал будет точно так же поступать на ножку микроконтроллера, как и сами импульсы.

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

А теперь самое время поговорить о прерываниях. В начале каждого промежутка времени счетчик импульсов инициализируется нулем. По мере прихода импульсов его значение увеличивается, говорят — инкрементируется. Проблема в том, что импульсы счета и начало периода никак не синхронизированы. Может вполне случится такая ситуация когда одновременно наступят оба события — приход счетного импульса и начало периода. Ключевое слово — «события».

Событие — это то, что происходит внезапно для системы. Событие — это трансцендентный инициатор вычислений. Если в системе нет событий, системе делать нечего. Зачем системе что-то делать, если ее состояние не меняется? Система должна спать, то есть потреблять минимум энергии.

Конечно, не все программисты так делают. Многие программисты тупо пишут циклы, которые опрашивают источники событий — таймер тикнул? — Нет. Тогда может быть на ножку пришла «единичка»? — Тоже нет. Ну может АЦП закончил оцифровку аналогового сигнала? — И здесь нет. И по кругу — таймер, ножка, АЦП и так далее. Когда событий нет, система все равно крутится, ничего полезного не делает. А поскольку программа работает, то процессор жрет энергию. Не очень профессионально, да?

Хорошо скроенные системы обычно все время спят. Поэтому температурный режим у них очень приятный. Посыпаются такие системы только по какому-либо событию — тикнул таймер, на ножку пришла единичка, АЦП оцифровал сигнал.

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

На каждое событие в системе создается свой обработчик события. (другое название — обработчик прерывания.) Обработчик события — это подпрограмма или функция, которая вызывается не из какого-то другого места в программе, а контроллером прерывания. Грубо говоря мы заранее программируем контроллер прерываний так, что бы он реагировал на определенные события (условно — таймер, ножка, АЦП). Реакцией контроллера прерываний как раз и является вызов соответствующих обработчиков событий.

Например, пришла на ножку процессора единичка, в контроллере прерываний возникло событие. Если мы заранее запрограммировали его, то контроллер прерываний вызовет программу — обработчик события, которую мы должны также заранее описать. Тикнул таймер — контроллер прерываний вызывает обработчик событий от таймера. АЦП закончил преобразование, поставил флажок, контроллер прерываний вызвал обработчик событий АЦП. Каждый обработчик событий живет в системе своей как бы изолированной жизнью, ничего не зная о других обработчиках и ничего не зная об основной программе, то есть о функции main().

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

Возвращаемся к практике. Согласно вышеизложенной теории в нашем устройстве должен быть обработчик событий от порта микроконтроллера. Вызываться этот обработчик будет каждый раз, когда на ножку микроконтроллера будет поступать единичный импульс. В этом обработчике мы должны только инкрементировать счетчик импульсов. И это всё! Более ничего делать не надо! Заметьте, что это оборачивается еще и огромной скоростью отклика на возникшее событие — как только возникает на ножке «бэмс!», так сразу же происходит инкремент счетчика.

Второй обработчик события, который будет определять периоды подсчета, будет точно такой же. Это тоже будет изолированная ото всех других подпрограмма. Ее задача будет несколько сложнее — нужно будет скопировать значение счетчика в буфер, в текущую его ячейку, затем изменить указатель текущей ячейки на следующую, и наконец обнулить счетчик импульсов. Кроме того, мы должны предусмотреть чтобы указатель не «вышел» за пределы буфера. Если такое произойдет, то мы должны вернуть его в начало буфера. Иначе говоря — закольцевать буфер.

А теперь внимание, фокус! Следите за руками!

Как ведет себя контроллер прерываний, когда одновременно наступают два или несколько событий? Вообще говоря, это зависит от процессорного ядра. Некоторые ядра (процессоры) имеют несколько уровней прерываний. События (чаще говорят — прерывания) одного уровня не могут прерывать друг друга, а будут обрабатываться последовательно друг за другом.

События верхнего уровня могут прерывать события нижнего уровня. Если такое происходит, то обработчик события нижнего уровня временно приостанавливается, в работу включается обработчик события верхнего уровня, а когда он закончит свою работу, управление вновь вернется к обработчику нижнего уровня.

Ну и остается добавить, что события нижнего уровня не могут прерывать обработку событий верхнего уровня. Если в системе случается, что во время обработки события верхнего уровня происходит событие нижнего уровня, то в системе ничего кардинального не происходит. Система продолжает заниматься обработкой события верхнего уровня, но в контроллере прерывания как бы оформляется заявка на прерывание нижнего уровня. И как только система закончит обработку верхнего уровня, контроллер тут же вызовет обработчик нижнего уровня.

Я вас наверно испугал такой сложностью. В микроконтроллерах MSP430 нет уровней! Там все прерывания одного уровня. Поэтому ни одно прерывание не может быть прервано другим до тех пор пока обработчик события (обработчик прерывания) не закончит свою работу. Ну или когда в этом обработчике мы сами не разрешим контроллеру прерывать нас. Но мы не будем входить в это искушение.

Таким образом, когда у нас одновременно наступят одновременно события — придет импульс и наступит новый период, то они будут обработаны друг за другом. Какое из них будет обработано первым, я не знаю. Но это не так важно. Важно то, что пока один обработчик инкрементирует счетчик импульсов, второй в это время не обнулит его. Мы сумели одновременно принять оба события и сумели «развести их по разным углам». Они друг другу не мешают. И они друг о друге и знать не знают. Более того, оба события обрабатываются очень быстро, реально — за несколько микросекунд. Пока событий нет, процессор может спать.

Вот код обработчиков событий:

#define UVBUFSIZE 64

uint8_t _uvcounter;         // счетчик количества uv-импульсов в текущем периоде
uint8_t _uvbuf[UVBUFSIZE];  // Кольцевой буфер периодов накопления
uint8_t _iuv;               // Индекс текущего элемента в буфере, куда будет
                            // записываться значение из счетчика uv-импульсов

void _uv_pulse(void) __attribute__((interrupt(PORT1_VECTOR)));
void _uv_nextterm(void) __attribute__((interrupt(PORT2_VECTOR)));

// Обработчик импульса ---------------------------------------------------------
void _uv_pulse(void)
{
  _uvcounter++; // Инкрементируем счетчик импульсов
}

// Обработчик начала нового периода подсчета импульсов -------------------------
void _uv_nextterm(void)
{
  _uvbuf[_iuv] = _uvcounter;  // счетчик копируем в буфер
  if (++_iuv == UVBUFSIZE)    // Проверяем, что мы не вышли за пределы буфера
    _iuv = 0;  // иначе -- закольцовка буфера

  _uvcounter = 0;  // Начинаем новый период подсчета импульсов, обнуляем счетчик
}

// Инициализируем нашу систему подсчета импульсов ------------------------------
void UV_init(void)
{
  int i;

  for (i = 0; i < UVBUFSIZE; i++)
    _uvbuf[i] = 0;  // Обнуляем все ячейки буфера

  _iuv = 0;  // Устанавливает указатель на начало буфера (обнуляем индекс)
  _uvcounter = 0; // Обнуляем счетчик импульсов
}

Здесь приведена еще одна функция — UV_init(). Эта функция инициализирует нашу систему. И в самом деле, когда система начинает работать, то в памяти могут оказаться совершенно невменяемые значения (в счетчике, в буфере, индекс). Если мы начнем работать с неопределенными значениями мы рискуем вообще всё сломать.

Предположим, что в индексе _iuv в момент начала работы будет находится значение 100. То есть мы должны записать в ячейку памяти _uvbuf[100] какое-то значение из счетчика импульсов _uvcounter. Но у нас размер буфера только 64 элемента. Куда произойдет запись? Что мы испортим? Вы видели «синий экран смерти» BSOD? Эпично, да? Ну и зачем нам в нашем изделии такое счастье? Поэтому давайте не будем доводить до греха, давайте будем всегда производить инициализацию переменных.

В начале программы присутствуют следующие строки:

void _uv_pulse(void) __attribute__((interrupt(PORT1_VECTOR)));
void _uv_nextterm(void) __attribute__((interrupt(PORT2_VECTOR)));

Здесь мы определяем имена обработчиков прерываний. Мы так и говорим, что функция _uv_pulse() — это не просто функция, а обработчик прерывания от порта PORT1. Соответственно, функция _uv_nextterm() — это обработчик прерывания от порта PORT2.
Двойные скобочки (( и )) — это не ошибка, а обязательный элемент конструкции, продиктованный правилами оформления функций для обработки событий (обработчиков прерываний).

Откуда я взял имена PORT1_VECTOR и PORT2_VECTOR? О-о, это очень просто! Эти имена прописаны в заголовочном файле для каждого микроконтроллера. Я использую микроконтроллер MSP430G2452, поэтому я открыл файл /usr/local/msp430/include/msp430g2452.h, и в нем, почти в самом конце, я нашел вот такой фрагмент текста:

/************************************************************
* Interrupt Vectors (offset from 0xFFE0)
************************************************************/

#define PORT1_VECTOR        (0x0004)  /* 0xFFE4 Port 1 */
#define PORT2_VECTOR        (0x0006)  /* 0xFFE6 Port 2 */
#define USI_VECTOR          (0x0008)  /* 0xFFE8 USI */
#define ADC10_VECTOR        (0x000A)  /* 0xFFEA ADC10 */
#define TIMER0_A1_VECTOR    (0x0010)  /* 0xFFF0 Timer0_A CC1, TA */
#define TIMER0_A0_VECTOR    (0x0012)  /* 0xFFF2 Timer0_A CC0 */
#define WDT_VECTOR          (0x0014) /* 0xFFF4 Watchdog Timer */
#define COMPARATORA_VECTOR  (0x0016) /* 0xFFF6 Comparator A */
#define NMI_VECTOR          (0x001C) /* 0xFFFC Non-maskable */
#define RESET_VECTOR        (0x001E) /* 0xFFFE Reset [Highest Priority] */

Здесь описаны все векторы прерываний для этого микроконтроллера. Теперь если я буду использовать в качестве задатчика нового промежутка времени внутренний таймер Timer0, то мне нужно будет изменить строку для определения имени функции обработчика события следующим образом:

void _uv_nextterm(void) __attribute__((interrupt(TIMER0_A0_VECTOR)));

При этом имя функции обработчика события _uv_nextterm() здесь и далее в тексте менять не надо.

Мы описали обработчики событий, но чтобы они нормально заработали, то есть ядро процессора их начало вызывать в ответ на события, мы должны еще дать задание контролеру прерываний. В MSP430 как такового контроллера прерываний нет. Точнее он, конечно есть, но он не выделен во что-то одно целое. Он как бы размазан по периферийным устройствам, и каждое периферийное устройство программирует в нем только свою часть.

Поскольку в нашей задаче мы имеем дело только с портами, мы должны разрешить прерывания только от этих портов.

В MSP430 за прерывания от портов отвечают несколько регистров. Вообще, прерывания возможны только от двух портов PORT1 и PORT2. Другие порты, если они, конечно, есть в микроконтроллере, не могут вызывать прерывания. Далее маленькая буква «x» в именах регистров означает либо первый порт, либо второй, и заменят соответственно цифру 1 или 2.

Регистр PxIE — это регистр, который определяет, какие выводы порта будут вызывать прерывания. Регистр PxIFG — это регистр флагов, или, как я выше сказал, это регистр «заявок» на прерывание. Заявка на прерывание может быть и подана, но вызовет ли она прерывание, — это решает регистр PxIE. К стати говоря, если вам так неймется, то вы сами можете спровоцировать прерывание, установив «в ручную» единичку в соответствующем бите регистра PxIFG. Это называется программной генерацией событий.

Есть еще один регистр — PxIES. Он определяет по какому фронту сигнала будет происходить прерывание. По уровню сигнала прерывания не происходят. И в самом деле, фронт — это изменения, это событие. А уровень — это неизменное состояние сигнала. А раз состояние не меняется, то где тут, собственно, событие? Так что все верно — событие наступает либо по фронту (переднему фронту), либо по спаду (по заднему фронту).

Маленькое предупреждение.

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

Проблема возникает в момент когда вы сбрасываете флаги прерываний. Вы должны сбрасывать флаги программно, и вам обязательно нужно сбрасывать каждый флаг индивидуально. Иначе говоря, вы не можете в конце обработчика прерывания сбросить сразу все флаги «оптом».

Как работает ваш обработчик событий от порта? Вы входите в обработчик, сканируете бит за битом регистр PxIFG на предмет наличия «заявки» от того или иного вывода, и, если «заявка» от вывода имеется, запускаете обработку этого события. Итак бит за битом, вывод за выводом, а потом в конце обработчика события вы «оптом» обнуляете все восемь бит. Это не правильно!

Представьте себе, что вы только-что оценили 2-ой бит в регистре и не обнаружили «заявку» на прерывание. Что вы делаете далее? Вы переходите к следующему биту, допустим, 3-му. Потом точно так же к четвертому, и так до конца регистра. А теперь представьте, что при сканировании 5-го бита на 2-ом выводе появилось событие. Но мы-то уже обработали этот бит и выяснили, что события там нет. Точнее — не было. Оно появилось вот только-что! А в конце обработчика события мы обнуляем все биты… Мы потеряли событие! Так делать нельзя!

Кроме того, обнулять бит нужно строго индивидуально, и лучше делать это сразу после обнаружения «заявки» перед ее обработкой. Теперь даже если во время обработки «заявки» вновь наступит еще одно событие, то оно не потеряется, а будет обработано. После окончания работы обработчика событий он будет сразу же вызван вновь, где и будет обработано это повторное событие.

Интервалы между событиями — это все игра со временем. Если интервалы между событиями меньше, чем время для их обработки, то вы не сможете их различать. Вы их будете терять.

Давайте теперь пропишем инициализацию порта 1 для приема импульсов. Тут все просто.

  P1DIR = 0x00; // Все выводы развернем на вход
  P1IE  = 0x01; // Разрешим прерывание с нулевого бита
  P1IES = 0x00; // "Ловим" нарастающий фронт

Инициализация порта 2 для приема импульсов, определяющих период подсчета, точно такая же, за исключением того, что используется другой порт.

  P2DIR = 0x00; // Все выводы развернем на вход
  P2IE  = 0x01; // Разрешим прерывание с нулевого бита
  P2IES = 0x00; // "Ловим" нарастающий фронт

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

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

Если же за начало буфера принять индекс текущего элемента, то вывод на ЖКИ будет другой. Изображение будет прокручиваться справа налево. Новые данные будут возникать справа, а самые старые значения будут исчезать в правой части, как только они выйдут за пределы.

Во вторых, нам нужно знать момент, когда нужно подновлять изображение. Ведь состояние буфера меняется только по окончании периода. (В течение периода оно не меняется.) Таким образом, мы можем по окончании периода вывести новое состояние буфера и заснуть. Не имеет смысла многократно выводить на экран одно и то же изображение.

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

Я начну с описания работы других процов. Пусть это будет проц с ядром AVR.

Построение программ для всех процов (вне зависимости MSP430 это или какой-то другой) строиться примерно одинаково. Общая структура выглядит так. Я пишу условный псевдокод.

void INT_A(void)
{
...
}

void INT_B(void)
{
...
}

void INT_C(void)
{
...
}

int main(void)
{
  init_all();

  while (1)
  {
    show_LCD();
    go_to_sleep();
  }

  return 0;
}

В начале идут обработчики событий INT_A, INT_B, INT_C. Они все работают на системном уровне, готовят и обрабатывают какие-то данные. Вы можете считать их аналогами комповых драйверов, которые работают с портами, дисками, сетью. Ваши программы обращаются к драйверам чтобы получить готовые данные или, наоборот, передать данные. Получить состояние драйвера или передать драйверу какую-то команду. Важно то, что ваша прикладная программа понятия не имеет как эти данные передаются и принимаются. Она тупо берет уже готовые данными, которые драйвер принял и разместил для нее где-то в памяти. Каждый делает свое дело, причем делает это очень качественно. Это залог надежной системы!

После обработчиков событий идет главная функция. Она так и называется — main(). Вот тут есть одна тонкость.

Это сложилось исторически. На рынке сначала появились компьютеры, а вот микроконтроллеры появились позже. Любая компьютерная программа вызывается на исполнение операционной системой. После своей работы программа должна передать операционной системе код возврата. Таким образом, операционка может знать как завершилась программа — удачно/неудачно, с ошибкой/без ошибки, с какой ошибкой, ну и так далее. В зависимости от этого и по желанию пользователя операционка может что-то предпринять. Скажем, если программа дозвона завершилась с ошибкой «Линия занята», то операционка может повторить вызов этой программы.

В микроконтроллерах же программа одна единственная в памяти. Никаких операционок нет и в помине. Поэтому функции main() в принципе некому передавать код возврата. Более того, для микроконтроллерных программ понятие «вообще закончить работу» и выйти из main() — это нонсенс! Микроконтроллерные устройства, а значит и функция main(), включаются и работают до тех пор пока подано питание.

Но, как я уже говорил, исторически так сложилось, что функция main() должна возвращать код возврата. Поэтому в ее определении присутствует обязательный int. Другие дело, что одни компиляторы требуют этот int, другие смотрят на это «сквозь пальцы» и легко принимают вместо него void. На сколько мне известно, все компиляторы gcc требуют наличия int. Поэтому мы должны помимо этого int-а еще и писать в конце функции main() что-то типа return 0; .

Но я очень сильно отклонился от темы. Возвращаемся.

В начале функции main() идут всевозможные инициализации. После того как всё и вся проинициализировано, настроено и запущено, запускается бесконечный цикл. Бесконечный — потому, что программы в микроконтроллерах обычно заканчивают свою работу тогда, когда снимается питание.

Ну представьте себе ситуацию окончания работы мозгов у стиральной машины, микроволновки или роутера. В какой-то момент они решили далее не работать и остановились. Замечательно! Конечно, при желании можно придумать такие устройства, которые отработав какую-то задачу должны мертво остановиться и более не работать. Ну разве что пока их снова не перезагрузят. Но таких устройств единицы. Основная же масса работают, пока есть питание.

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

Любое событие (которое, естественно, запрограммировано) пробуждает все типы микроконтроллеры. Все типы микроконтроллеров просыпаются, отрабатывают возникшее событие (то есть отработает программа обработчика события), и …

А вот далее начинаются отличия у разных микроконтроллеров. Если это микроконтроллеры AVR, то после окончания обработки события они продолжают работать. Другими словами — выходят из функции go_to_sleep(). Таким образом, AVR-ки совершат очередной виток в бесконечном цикле.

В нашем случае с MSP430, если мы ничего специально не предпримем, микроконтроллер отработает событие и снова заснет. Иначе говоря, возврата из функции go_to_sleep() не последует.

Что получается? Если наша прога находится в бесконечном цикле, который отображает на экране ЖКИ картинку, а потом отправляет процессор в спячку, и мы используем AVR, то бесконечный цикл будет совершать «холостые выстрелы» на каждый пришедший импульс. Такого нам не надо! В случае же с MSP430 ситуация еще веселее — здесь мы вообще ничего нового на экране не получим, даже по окончанию периода подсчета импульсов. Что же делать?

В случае с AVR мы должны в программу вести какую-то переменную-флаг, которую мы будем просматривать на каждом витке бесконечного цикла. И в случае, если эта переменная будет равна единице, мы должны будем подновить экран, и заодно обнулить эту переменную-флаг. Вот как должен выглядеть этот код:

  while (1)
  {
    if (flag)
    {
      show_LCD();
      flag = 0;
    }
    go_to_sleep();
  }

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

Конечно, тут есть побочный эффект. Итерация будет совершаться всякий раз, вне зависимости надо или нет перерисовывать экран ЖКИ. Но введя в программу переменную-флаг мы уже сократили время прохождения итерации в случае, когда эта переменная равна нулю. То есть когда экран перерисовывать не надо, он и не будет перерисовываться, и очередной виток тут же закончится вызовом функции go_to_sleep. «Холостой выстрел» все равно присутствует, но он на столько короткий, что это практически не будет заметно. Полностью избавиться от «холостых выстрелов» у микроконтроллеров AVR — не представляется возможным. Такова природа их ядра.

У микроконтроллеров с ядром MSP430 совершенно иной подход. После окончания работы обработчика события ядро переходит в то же самое состояние, в каком оно было до возникновения события. Поэтому единожды «заснув» в бесконечном цикле, мы не сможем проснуться без специальных мер. «Специальная мера», собственно, это снять режим спячки перед выходом из обработчика.

Вот как это правильно делается:

// Обработчик начала нового периода подсчета импульсов -------------------------
void _uv_nextterm(void)
{
  _uvbuf[_iuv] = _uvcounter;  // счетчик копируем в буфер
  if (++_iuv == UVBUFSIZE)    // Проверяем, что мы не вышли за пределы буфера
    _iuv = 0;  // иначе -- закольцовка буфера

  _uvcounter = 0;  // Начинаем новый период подсчета импульсов, обнуляем счетчик

  __bic_SR_register_on_exit(LPM3_bits);
}

При этом в главной функции main() вместо гипотетической функции go_to_sleep() мы должны написать конкретную функцию __bis_SR_register() с указанием битов, какие из них мы хотим установить в регистре состояния SR. Мы должны обязательно установить бит общего разрешения прерываний GIE (если он до этого не был поднят), иначе у процессора случится «кома» и вообще не сможет ни на что реагировать. Кроме того, мы должны установить биты, которые отвечают за режим сна. Допустим, мы хотим отправить процессор в режим сна LPM3.

Вот этот код:

  while (1)
  {
    show_LCD();
    __bis_SR_register(LPM3_bits + GIE);
  }

Я подчеркну это особо. У микроконтроллеров с ядром MSP430 эффект «холостого выстрела» можно устранить полностью, что дает еще большую экономию энергии.

Готовых тестов работающей программы у меня нет. Я сначала писал эту публикацию, согласно моим знаниям. Но как раз сейчас я займусь написанием программы. Чуть позже я выложу ее исходные тексты, если не забуду как всегда, жизнь полна соблазнов…

Advertisements

8 responses to “Работаем с MSP430 в Linux-е. Lesson 5

  1. В функции _uv_nextterm(void) проверку индекса безопасней проводить до обращения к массиву

    • Это спорный вопрос.

      Переменная _iuv (индекс массива) — глобальная, имеет область видимости файла. Это значит, что к ней имеется доступ из любой функции файла. Если написать код не правильно, то можно случайно изменить ее значение. С точки зрения безопасности — это несколько напряжно. Но проект — небольшой. Накосячить с переменной просто негде! Переменная инициализируется в одном месте, и используется еще в одном — и это всё! Других мест обращения к переменной нет и быть не должно!

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

      А если вдруг проект начнет сильно разрастаться, то имеет смысл перейти на классы (переписать на C++). Но вполне достаточно выделить в отдельный файл переменную, массив и кое-какие функции, которые работают с ними. Тогда все косяки будут локализованы в этом файле.

      Еще раз, просто соизмеряйте угрозу и противодействие этой угрозе. В проекте этой переменной вообще ничего не угрожает.

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

    • «Можно чуть улучшить Ваш пример и показать …» — не понял, это вопрос ко мне, или это Вы хотите внести свою лепту в общее дело?

      Если второе, то Welcome! Стесняться здесь нечего. Ошибаться все могут.

      [b]Не важно как ты падаешь! Важно, что ты каждый раз поднимаешься.[/b]

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

  3. Вероятно я плохо пишу по русски, перехожу на C
    Ваш код:
    void _uv_nextterm(void)
    {
    _uvbuf[_iuv] = _uvcounter; // счетчик копируем в буфер
    if (++_iuv == UVBUFSIZE) // Проверяем, что мы не вышли за пределы буфера
    _iuv = 0; // иначе — закольцовка буфера
    _uvcounter = 0; // Начинаем новый период подсчета импульсов, обнуляем счетчик
    }

    Следует переписать так:
    void _uv_nextterm(void)
    {
    if (_iuv >= UVBUFSIZE) { // Если вышли за пределы буфера
    _iuv = 0; // закольцовка буфера
    }
    _uvbuf[_iuv++] = _uvcounter; // счетчик копируем в буфер
    _uvcounter = 0; // Начинаем новый период подсчета импульсов, обнуляем счетчик
    }

    и поправить код функции show_LCD();

    • А не могли бы Вы подсказать, где я выложил код show_LCD(), а то я его что-то найти не могу 😦

      — Блин! Так и расстрелять могут… (с) из анекдота

      • А я не видел show_LCD(). И так понятно, что show_LCD() использует _iuv.

      • но тогда я не понял фразу «… и поправить код функции show_LCD()» — а что там, в этой функции не так?

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s