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

// Программа из урока Lesson-4
// версия 0.01 от 19.07.2012
// (с) Жевак Александр, 2012 г.

// Назначение и описание программы:
// Программа моргает светодиодами, которые выбираются нажатием кнопки

#include <msp430f2001.h>
#include <stdint.h>

// Назначаем очеловеченные имена ресурсам
#define RED_LED   (BIT0)
#define GREEN_LED (BIT6)

#define BUTTON    (BIT3)

// Светодиод, которым будем моргать.
volatile uint8_t led;

// Объявление функций-обработчиков прерываний
void port1_isr(void) __attribute((interrupt(PORT1_VECTOR)));
void wdt_isr(void)   __attribute((interrupt(WDT_VECTOR)));

// Обработчик прерывания от порта
// Это действие редкое. Поэтом нагрузим его максимум обработкой
void port1_isr(void)
{
  P1IFG &= ~BUTTON; // Сбросить флаг прерывания

  // определим текущий светодиод
  if (led == GREEN_LED)
    led = RED_LED;
  else
    led = GREEN_LED;

  // Погасим оба светодиода, иначе может оказаться так, что текущий светодиод
  // начнет моргать (как и задумано), но предыдущий светодиод останется зажженным
  P1OUT &= ~(GREEN_LED + RED_LED);
}

// Обработчик прерывания от сторожевого таймера
// Поскольку это действие происходит часто и многократно, то будет более разумным
// выполнять в нем минимум работы
void wdt_isr(void)
{
  P1OUT ^= led;
}

// Основная программа
int main(void)
{
  // Переводим сторожевой таймер в режим периодического таймера
  // Настраиваем тактирование WDT от сигнала ACLK
  WDTCTL = WDTPW + WDTTMSEL + WDTCNTCL + WDTSSEL + WDTIS1;
  // Источником сигнала ACLK будет низкочастотный встроенный генератор со сверхнизким
  // энергопотреблением VLO. Частота ACLK = 12 кГц
  BCSCTL3 = LFXT1S1;

  // Настраиваем порт P1
  P1DIR = ~BUTTON;  // Разворачиваем все выводы на выход, за исключением вывода, к
  // которому подключена кнопка
  P1IES =  BUTTON;  // Определяем фронт импульса по которому будет возникать
  // прерывание. Фронт нарастающий
  P1IE  =  BUTTON;  // Разрешаем прерывание только от кнопки

  led = GREEN_LED;  // Инициализируем глобальную переменную

  IE1 |= WDTIE;     // Рарешаем прерывания от WDT
  __bis_SR_register(LPM3_bits + GIE);  // Одновременно разрешаем процессору
  // реагировать прерывания и переводим
  // его в режим глубокого сна.

  // Главный цикл программы
  while (1)
  {
    // Ничего не делаем. (Только спим)
  }

  // Сюда мы никогда не попадем
  return 0;
}
Advertisements

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

  1. Спасибо. Хорошие статьи.
    Я только начал, из любопытства, знакомится с темой и Ваши обьяснения рабочего кода (особенно про специфику регистров и таймеров), очень помогли.

    • Всегда Welcome!

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

      А вам — спасибо за теплые («ламповые» 🙂 ) слова. Такие отзывы еще больше вдохновляют.

  2. Подскажите ,почему компилятор IAR ругается на эти строки ?
    21 void port1_isr(void) __attribute((interrupt(PORT1_VECTOR)));
    22 void wdt_isr(void) __attribute((interrupt(WDT_VECTOR)));

    • Вообще-то мне было бы намного легче ответить, если бы Вы предоставили чуть-чуть больше информации. А то мне пришлось додумывать. В следующий раз указывайте, что сообщает IAR, какая у него версия (AVR, MSP430, ARM, …), номер версии.

      Вообще обработчики прерываний в IAR для AVR имеют следующую структуру, состоящую из двух строк, следующих единым кортежем друг за другом (— это важно!):

      #pragma vector = ADC_vect
      __interrupt void my_handler(void);

      здесь ADC_vect — это число, адрес памяти, где находится вектор (не обработчик!!!). Например, для ADC_vect это 0x38. Соответствие символов и адресов определено в хэдерном файле. Например, для процессора Mega16 — это файл iom16.h.
      my_handler — имя обработчика прерывания. Вы его вольны задавать любое (разумеется, в разумных пределах).

      Я понимаю, что я ответил немного «мимо кассы», но у меня давно уже нет ни IAR-а, ни Винды, так что более четко ответить на Ваш вопрос я не могу. Я просто посмотрел свои старые тетрадки. Оказалось, что информация есть только для AVR. Думаю, что по аналогии Вы сами сможете раскопать, как оно правильно должно быть для MSP430.

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

  3. Подскажите пожалуйста, а если кнопок несколько, то как определить какая из них нажата?

    • Предположим Вы используете несколько кнопок. Предположим, Вы подсоединили их к выводам только на одного порта. Предположим, что это PORT1. Предположим, что кнопок на других портах нет.

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

      Итак, Все кнопки висят на порту PORT1.

      Удобно сразу их определить по именам. Например так (см. текст проги выше, строка 15 ):

      #define START_BTN    (BIT2)
      #define STOP_BTN      (BIT3)
      #define LEFT_BTN       (BIT4)
      #define RIGHT_BTN     (BIT5)
      

      Соответственно, инициализация порта должна быть следующей (строка 59):

      // Настраиваем порт P1
      P1DIR = ~BUTTON;  // Разворачиваем все выводы на выход, за исключением вывода, к которому подключена кнопка
      
      // Определяем фронт импульса по которому будет возникать
      // Так как мы работаем с неперекрывающимися битами, то можно
      // вместо операции сложения ("+") использовать логическую
      // операцию "ИЛИ" ("|"). Результат будет тот же.
      P1IES =  START_BTN + STOP_BTN + LEFT_BTN + RIGHT_BTN;
      
      // Разрешаем прерывание только от кнопки. Фронт нарастающий
      P1IE  =  START_BTN + STOP_BTN + LEFT_BTN + RIGHT_BTN;  
      

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

      P1REN = START_BTN + STOP_BTN + LEFT_BTN + RIGHT_BTN;
      

      Да! И не забудьте про дребезг контактов, который всегда присутствует в реальных схемах. Для борьбы с этим явлением на LaunchPad параллельно кнопке установлен конденсатор. В своей схеме Вы тоже должны установить конденсаторы параллельно кнопкам. В противном случае Вам придется бороться с дребезгом программным способом. Сложного ничего нет, но это тема отдельной статьи. Чтобы не усложнять пример, давайте сейчас обойдемся конденсаторами.

      Таким образом, теперь у нас все проинициализировано и нам остается только правильно обработать прерывание от порта.

      Сложность состоит в том, что событие на любом выводе (с кнопкой) вызывают одно и тоже прерывание. Иначе говоря, мы входим в обработчик прерывания по нажатию любой нашей кнопки. Наша задача — определить, какая из них привела к возникновению прерывания.

      Для этого нам нужно считать регистр флагов прерываний порта и «нащупать» установленный бит. Этот код должен располагаться между 27-ой и 28-ой строкой:

      uint8_t ifg;
      
      ifg = P1IFG;  // лучше сделать копию регистра и работать с копией.
      
      // теперь по-битно ощупываем кнопки.
      // И если что, то вызываем соответствующий код
      
      if (ifg &amp; START_BTN)
      {
        // Выполнить этот код, если нажата кнопка "Старт"
        ...
      
       // Сбросить флаг прерывания от кнопки "Старт"
       P1IFG &amp;= ~START_BTN;
      }
      
      if (ifg &amp; STOP_BTN)
      {
        // Выполнить этот код, если нажата кнопка "Стоп"
        ...
      
       // Сбросить флаг прерывания от кнопки "Стоп"
       P1IFG &amp;= ~STOP_BTN;
      }
      
      if (ifg &amp; LEFT_BTN)
      {
        // Выполнить этот код, если нажата кнопка "Влево"
        ...
      
       // Сбросить флаг прерывания от кнопки "Влево"
       P1IFG &amp;= ~LEFT_BTN;
      }
      

      Ну и так далее. Для обработки кнопки «Вправо» и других кнопок, если будут, — аналогично.

      Если объем работы небольшой, типа включить/выключить светодиод, то код можно разместить прямо здесь же — в обработчике прерывания. Но если объем вычислений большой, например, передать строку символов в UART или отобразить что-то на LCD, то в теле обработчика прерывания лучше не заниматься этой работой.

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

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

  4. Супер серия статей! Хотелось бы продожения) Как моргать по команде с компьюетра, как менять яркость.

    • Спасибо, Сергей, за Ваш отзыв о моей работе.
      И спасибо за озвученные желания, чего вам (всем) хотелось бы освоить.

      У меня сейчас чуть-чуть другие планы. Я пытаюсь освоить работу с Git-ом.

      Ну как «освоить»? В смысле — освоить на хорошем высоком уровне. Я планирую, выкладывать свои учебные проекты куда-нибудь на github или bitbucket. Это нужно для того, чтобы те, кто учится на этих примерах, просто могли набрать одну команду типа git clone ПУТЬ_К_ПРОЕКТУ, и учебный проект целиком, один-в-один через мгновение будет создан на их (его) компе. Копипастить отсюда, конечно, тоже можно, но все это выглядит как-то не кошерно.

      А когда я сам получу достаточный опыт работы с внешними репами, я обязательно продолжу серию статей про работу MSP430. Это должно случиться уже скоро, Вы, в некоторой степени, своим отзывом подстегнули меня.

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

  5. И мне очень нравится Ваш цикл статей. Небольшая поправка к тексту программы. Строка 38, лучше написать:
    P1OUT &= ~(GREEN_LED + RED_LED);

    • А, да! Мой косяк. Сейчас поправлю. Спасибо за замечание!

      Вообще в данном случае оно итак отработает, но это все-таки есть ошибка. Это заложенная мина. И она рванет. Обязательно рванет. Но потом (с)

      И спасибо Вам за положительный отзыв о моей деятельности. Что ни говори, но такое приятно слышать!

  6. Здравствуйте, мой вопрос похож на вопрос Юрия. Но я столкнулся с ситуацией, когда мне необходимо управлять двумя кнопками по отдельности, подключёнными к одной! ножке микропроцессора. Т.е. они относятся к одному биту. Если я не ошибаюсь, то там требуются некоторые расчёты сопротивлений и напряжений, связанных с делителем. Помогите пожалуйста с вопросом, буду вам очень признателен.

    Даже схемку составил:
    VDD
    |
    | Btn1
    |—R1—-/ ——|
    | |
    R2 |
    | |
    |——————|
    | |
    R3 |
    | Btn2 |
    |—R4—-/ ——|—->P2.0
    |
    |
    DGND

  7. Схемка в прошлом комментарии пострадала, я заменил пробелы точками:

    VDD
    |
    | ……..Btn1
    |–R1—-/ ——|
    | ………………..|
    R2 …………….|
    | ………………..|
    |——————|
    | ………………..|
    R3 …………….|
    | ……..Btn2 ….|
    |–R4—-/ ——|—->P2.0
    |
    |
    DGND

    / — значок ключа

    • Извините, Turbo_enot, я ничего из Вашей схемы не понял. Вижу, что там используются четыре резистора, две кнопки, один вход МК, земля, питание. А что замыкают кнопки — не понятно.

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

      Таким образом, Вы не можете использовать ни систему прерываний МК для определения момента нажатия на кнопку, ни цифровые порты вообще.

      Чтобы определить нажата ли кнопка, Вам нужно использовать ADC. Вы периодически, допустим, через каждые 30 мс, оцифровываете вход. И как только напряжение на входе существенно измениться (скажем на одну четвертую часть от VDD), можно считать, что произошло нажатие на ту или другую кнопку.

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

      • Аноним

        Спасибо! Начал разбираться с ADC — отдельная, большая и очень важная схема. Постепенно стало получаться.

      • Могу подсказать. Чтобы определить изменение состояния кнопок Вам нужно создать в программе статическую переменную, где будете хранить предыдущее значение, считанное с АЦП.

        Например:
        static uint16_t prev_keys;

        На каждом цикле (один раз в 30 мс) считывайте с АЦП текущее значение и сверяйте его с предыдущим. Если изменений нет, то с предыдущего раза кнопки не нажимались и не отпускались. Если есть изменения, то определяете что изменилось.

        В конце цикла опроса кнопок заменяйте в этой переменной старое значение на текущее.

        Таким образом Вы сможете узнать не только нажатую кнопку, но и подавить нежелательный дребезг контактов. Только помните, что АЦП сам по себе шумит, и считанные с АЦП значения будут иметь небольшие отклонения от нормы (от ожидаемого значения). Можете просто перед сравнением тупо закрывать маской несколько (2..4) разрядов АЦП.

  8. Здравствуйте, zhevak. Вот, что у меня вышло:

    #define RED_LEDS  (BIT0) //красненький светодиодик
    #define GREEN_LED (BIT2) //зелёненький
    
    #define BUTTON    (BIT0) // Две кнопки (SB1 и SB2), подсоединённые к P2.0
    
    char flash = 0;
    
    void main(void)
    {
      WDTCTL = WDTPW + WDTHOLD; // Останавливаем сторожевой таймер
    
      ADC10CTL0 = SREF_1 + ADC10SHT_2 + REFON + ADC10ON;  // включаем АЦП
      ADC10CTL1 = INCH_0 + SHS_0 + ADC10SSEL_0 + ADC10DIV_0 + CONSEQ_0; 
      ADC10AE0 |= BUTTON;
      
      P1DIR |= GREEN_LED; 
      P1DIR |= RED_LEDS;
        
      //Создаём таймер для удобного моргания через прерывания
      TACCR0 = 15000;   // Задержка таймера для мигания, 15000 циклов
      TACCTL0 = CCIE;   // Разрешаем прерывание таймера по достижении CCR0
      TACTL = TASSEL_2 + ID_3 + MC_1 + TACLR; // SMCLK, делитель 8,
                                              // Прямой счёт, обнуление таймера       
     __bis_SR_register(GIE);; // Разрешаем прерывания
    
    
       while (1) {
       ADC10CTL0 |= ENC + ADC10SC; // Включаем преобразование
    
          if (0x03FC < ADC10MEM && ADC10MEM < 0x0400)  // Если нажата SB1
          {                               
          P1OUT &= ~RED_LEDS;
          flash  =  GREEN_LED;
          }      
          if (0x025A < ADC10MEM && ADC10MEM < 0x03FA) // Если кнопки не нажаты
          {
           P1OUT &= ~GREEN_LED; 
           P1OUT &= ~RED_LEDS;
          }
          if (0x0193 < ADC10MEM && ADC10MEM < 0x025A) // Если кнопки не нажаты
            {
            P1OUT &= ~GREEN_LED; 
            P1OUT &= ~RED_LEDS;
            } 
          if (0 < ADC10MEM && ADC10MEM < 0x0400)      // Если кнопки не нажаты
          {                               
          P1OUT &= ~RED_LEDS;
          P1OUT &= ~GREEN_LED;
          }      
          if (0x0246 < ADC10MEM && ADC10MEM < 0x025A) // Если обе кнопки нажаты
          {
           P1OUT &= ~GREEN_LED; 
           P1OUT |=  RED_LEDS;
          }
          if (0x017F < ADC10MEM && ADC10MEM < 0x0193) // Если нажата SB2
          {
           P1OUT &= ~RED_LEDS;
           flash  =  GREEN_LED;
          }
        }
      }
    
    #pragma vector = TIMER0_A0_VECTOR
    __interrupt void CCR0_ISR(void) 
    {
            P1OUT ^= flash;                 // если flash == 0, светодиод не горит
                                            // если flash == LED1, мигаем светодиодом
    }
    
  9. Всё работает, однако, наблюдаются проблемы. Когда кнопки не нажаты, зелёный диод слабенько мерцает. Причём начинается это только после первого нажатия на любую из кнопок. Не знаю, виновата ли плата, или всё же код. Во-вторых, не могу усыпить МК, даже через LPM0 — прога перестаёт работать.
    А самое главное, я так и не разобрался в прерываниях ADC. Чем сейчас старательно занимаюсь.
    Буду благодарен вам за любую помощь.

    • Замеченные проблемы в программе:

      1. Вы используете АЦП. В строке 28 Вы запускаете преобразование, которое выполняется какое-то время. Наверняка этот время намного больше, чем исполнение следующего оператора if. Поэтому в регистре ADC10MEM будет либо шум, либо предыдущее значение. В любом случае так не делается. Для исправления ситуации нужно использовать либо прерывание (по окончанию цикла преобразования), либо зациклить программу проверяя флаг окончания преобразования ADC10IFG в регистре ADC10CTL0.

      2. Переменная flash используется для определения какие светодиоды должны моргать. Поскольку эта переменная используется и в прерывании и в основном (главном) цикле программы, я бы на будущее все же определил ее как volatile. Хотя в данном случае оно не требуется, так как в прерывании эта переменная не изменяется. Но кто поручиться за то, что в следующей версии программы Вы не удумаете ее менять? Поэтому лучше обезопасится.

      3. Переменной flash Вы присваиваете значение GREEN_LED в двух строках программы — в 33-ей и в 58-ой. И более нигде в программе Вы не меняете значение этой переменной. Таким образом, стоит Вам нажать кнопку 1 или кнопку 2, переменная flash пример значение GREEN_LED. И это значение останется до выключения питания.

      4. По оптимизации программы я бы еще посоветовал Вам не делать таких страшных If-ов.

      Я немного изменю Ваши «пороги» значений АЦП, дабы донести суть. Допустим, Вы условно разбили весь диапазон значений на четыре поддиапазона. Вам нет никакой необходимости проверять все четыре поддиапазона по отдельности.

      Сначала проверьте первый поддиапазон (допустим, он имеет пороги от 0 до 100) с помощью оператора:

      if (ADC10MEM < 100)

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

      else if (ADC10MEM < 200)

      , если значение АЦП находится здесь, то нажата кнопка 1. (поддиапазон 0…100 мы только что проверяли, и там нет "попадания". Значит, если всё-таки "попадание" есть, то оно однозначно будет в поддиапазоне 100…200).

      Если "попадания" нет, то следует проверить попадание в первый, второй и третий диапазоны:

      else if (ADC10MEM < 300)

      , если значение АЦП находится здесь, то нажата кнопка 2.

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

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

  10. Премного благодарен вам за помощь.
    1. Буду разбираться с АЦП дальше. Как я уже говорил, знаю, что нужны прерывания, но ещё не научился ими владеть на АЦП.
    2. Учту, спасибо. Уже исправил.
    3. Похоже, что в этом основная загвоздка, и эта ерундовина заставляет мерцать светодиод когда ему не следует.
    4. Программу я писал, как вы однажды написали, «крайне брутально, по рабоче-крестьянской технологии». Постепенно совершенствуя её в силу новоприбывших знаний. Теперь, когда она заработала (хоть как-то, но заработала), я начну её «причёсывать».
    Долго мучился, как же правильно организовать опрос через if, спасибо за совет, сейчас буду приводить прогу в порядок, и проверять её работоспособность.
    P.S.: мне (да и не только мне, я думаю) очень повезло, что вы написали столько полезных уроков, и продолжаете помогать, отвечая на вопросы. Спасибо.

    • С прерываниями все очень и очень просто. Ничего там заумного нет.

      Вам нужно только описать в программе обработчик прерывания и разрешить бит ADC10IE в регистре ADC10CTL0.

      В момент загрузки значения, полученного в результате аналого-цифрового преобразования, в регистр ADC10MEM, в микроконтроллере возникнет прерывание. Это прерывание остановит основной цикл программы и автоматически вызовет Ваш обработчик прерывания.

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

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

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

      А Вы — программист, Вы — руководитель этой конторы. Ваша задача, организовать параллельные процессы.

      Дорогу осилит идущий! Удачи!

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s