Интересная особенность MSP430

В отличие от других процов у MSP430 при запуске собака (watchdog) включена. Обычно ее отрубают первой же командой.

В ассемблере:

    mov.w  #(WDTPW + WDTHOLD), &WDTCTL

В Си:

  WDTCTL = (WDTPW + WDTHOLD);

Потом, если это необходимо, включают повторно. Честно говоря, такой расклад меня несколько раздражает, особенно после ARM7/Cortex и AVR. Но что делать! Традиция MSP430 обязывает.

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

Засада ожидает вас вот в каком месте. Допустим, у вас есть проц MSP430F2617 и есть прога. (Пример проблемы взят c форума electronix.ru и немного изменен в сторону «учебного материала».)

#include <msp430x26x.h>

#define BUFSIZE 4000
#define INITVAL 1

uint8_t buf[BUFSIZE];

void main(void)
{
  unsigned int i;

  WDTCTL = WDTPW+WDTHOLD; // Stop WDT

  for (i = 0; i < 4000; i++)
   buf[i] = INITVAL;

  while (1)
    ; // Вечный цикл
}

Если его откомпилировать и залить в проц, то все будет хорошо. Массив buf[] проинициализируется единицами (INITVAL), и прога выйдет на бесконечный цикл, как мы и предполагаем.

Но если мы увеличим размерность массива (BUFSIZE) до 7000, то проц «сойдет с ума» — мы даже не сможем дойти до входа в функцию main. Что вы можете предположить?

Первое, что приходит в голову — не хватает оперативы. Ответ хороший, но в данном случае не верный. У проца MSP430F2617 оперативы 8 кБ, и нам ее вполне хватает.

Проблема зарыта достаточно глубоко и сразу ее не так-то просто выявить. Следите за руками.

Мы объявляем массив buf[], который располагается в области инициализируемой памати (то есть в сегменте .bss). Инициализизация памяти предполагает, что стартовый код процессора должен зарезервировать в оперативе место под наш массив и заполнить это пространство нулями. Этот код обычно находится в ассемблерном файле cstartup.S, который неявно подключается к любому Си-шному проекту.

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

Теперь разгадка становится легкой. Если мы берем небольшой массив (4000 элементов, например), то код из cstartup.S успевает обнулить массив и передать управление в main, где первая же команда вырубит собаку. Если же массив будет достаточно большой, то время на его обнуление будет превышать время срабатывания собаки — собака гавкнет раньше, чем управление доберется до main.

Проблема ясна. Второй вопрос — как лечиться?

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

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

Третий вариант предложил на форуме некто ld81. Он предлагает в Си-шном файле создать низкокоуровневую функцию __low_level_init. Эта функция вызывается из кода cstartup.S после инициализации стека (т.е. в этот момент уже можно вызывать функции — место, куда записывать адреса возвратов, уже определено), но до инициализаии переменных. Вот она:

int __low_level_init(void)
{
  WDTCTL = WDTPW + WDTHOLD;               // Stop WDT

  // place your code for hardware initialization here
  /*==================================*/
  /* Choose if segment initialization */
  /* should be done or not.           */
  /* Return: 0 to omit seg_init       */
  /*         1 to run seg_init        */
  /*==================================*/
  return (1);
}

Нам эта функция нужна только лишь для того чтобы вырубить собаку, что мы и делаем в первой строчке функции. После отработки, функция вернет управление обратно в cstartup.S. Потом, по окончании работы cstartup.S, как обычно будет вызвана функция main. Понятно, что в main собаку уже не имеет смысла не вырубать еще раз.

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

UPDATE (07.09.2012)

Эта тема странного поведения MSP430 еще раз поднялась на

http://electronix.ru/forum/index.php?showtopic=105866

, где Сергей Борщ предложил выполнять отключение WDT в секции .init3 в файле cstartup.s :

__attribute__((naked, section(".init3"))) void __low_level_init()
{
WDTCTL = WDTPW + WDTHOLD;
}
Advertisements

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s