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

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

А поскольку, мы рассматриваем MSP430 как ногодрыгалку, то неизбежно мы обязательно столкнемся с такой проблемой как формирование заданных промежутков времени,  иначе говоря, нам нужно уметь формировать точные задержки. Если вы еще не сообразили, где это может потребоваться, то вот вам банальный пример — формирование импульсов заданной частоты и заданной длительности.

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

Предположим, что у нас процессорное ядро тактируется частотой 8МГц. Для этой частоты я написал (готовый для применения) код, который формирует задержку исполнения в диапазоне от нескольких микросекунд до 65 миллисекунд:

// Задержка в микросекундах
// Действительна для Fcpu = 8 МГц
void delay_us(uint16_t us)
{
  do {
    _NOP();
    _NOP();
    _NOP();
    _NOP();
    _NOP();
  } while (--us != 0);
}

Параметр us задает выдержку времени в микросекундах.
Приведенный кусок кода не является абсолютно точным и абсолютно повторяемым, но для компилятора msp430-gcc ver.4.6.3 с применением ключей компиляции -Os -Wall -c -mmcu=msp430g2452, дает примерно правильные результаты. Поясню.

Указанный С-шный код должен скомпилироваться в следующую последовательность машинных кодов:

0000e076 <delay_us>:
e076:    03 43           nop
e078:    03 43           nop
e07a:    03 43           nop
e07c:    03 43           nop
e07e:    03 43           nop
e080:    3f 53           add    #-1,    r15    ;r3 As==11
e082:    f9 23           jnz    $-12         ;abs 0xe076
e084:    30 41           ret

Этот фрагмент можно «подсмотреть», если дизассемблировать полученный elf-файл и сохранить его в файле my-proga.asm с помощью команды:

$ msp430-objdump -S my-proga.elf > my-proga.asm

Пять команд nop занимают у процессора пять тактов.
Команда add #-1, r15 занимает один такт.
И команда перехода jnz $-12 — еще два такта.

Таким образом, на каждый цикл процессор будет затрачивать:

5 * 1 + 1 + 2 = 8 тактов.

При частоте процессора 8 МГц один такт длиться 125 нс:

T = 1 / Fcpu = 1 / 8000000 Гц = 0.000000125 c

Восемь тактов по 125 нс каждый дадут потерю времени ровно в одну микросекунду:

dt = 8 * 125 нс = 1000 нс

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

Для того чтобы вызывать эту функцию и вернуться из нее, процессор также тратит свои такты. Но это нигде в функции не учитывается. Я вас предупредил!

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

С этим — всё! Переходим рассмотрению более длительных задержек. Если вы умеете угадывать, то скорее всего уже догадались, что следующая функция будет называться delay_ms.

Эта функция позволит формировать задержки от одной миллисекунды до 65 секунд. Задержка задается параметром ms.

// Задержка в миллисекундах
// Предполагается, что частота источника для таймера = 1 МГц
void delay_ms(uint16_t ms)
{
  uint16_t i;

  for (i = ms; i != 0; i--)
  {
    TACCTL0 &= ~CCIFG;      // Сбрасываем флаг прерывания
    TACTL   |= TACLR + MC_1; // Очищаем счетчик и начинаем отсчет периода времени в 1 мс

    while ((TACCTL0 & CCIFG) == 0)
      ;
  }

  TACTL &= ~MC_1;  // Останавливаем таймер
}

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

Заметьте, что таймер может тактироваться необязательно от того же самого источника, который используется для тактирования ядра процессора. Нам важно только подать на вход таймера частоту в 1 МГц. Откуда мы ее возьмем — не важно!

Я привел две проверенные и готовые к применению функции. Можете смело их копипастить в свои проекты.

Однако, давайте продолжим. Мне не хотелось бы останавливаться на половине пути. Давайте рассмотрим, как получить частоту в 1 МГЦ и подать ее на таймер.

Для этого я слегка отойду в сторону и скажу, что я обычно в своих проектах пишу отдельный файл для работы с аппаратной частью процессора. Точнее, два файла — hal.c  и hal.h.

hal — это сокращение от Hardware Abstraction Level (уровень абстрагирования от аппаратуры). Это часто применяемое сокращение в мире низкоуровневого программирования.

Вот, в файл hal.c мы и поместим эти две функции задержки. Сюда же имеет смысл прописать еще одну очень важную функцию — функцию инициализации железа. У этой функции нет какого-то устоявшегося названия, но я предлагаю назвать ее InitSystem.

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

Однако, много слов. Давайте ближе к делу. Вот, код этой функции:

// Инициализация системы
void InitSystem(void)
{
  uint8_t i;

  WDTCTL = WDTPW + WDTHOLD;   // Останавливаем собаку

  // Инициализируем MCLK
  DCOCTL  = CALDCO_8MHZ;
  BCSCTL1 = CALBC1_8MHZ;

  // Устанавливаем источник частоты для таймера Timer_A
  TACTL &= MC_0;  // Останавливаем таймер
  TACTL |= ID_3 + TASSEL_2;  //  SMCLK / 8 = 1 MHz
  TACCR0 = 1000;  // 1 MHz / 1000 = 1 kHz, T = 1 мc

  // Инициализируем порты
  ...
}

Здесь не так много комментариев. Но их вполне достаточно для тех, кто работал с микроконтроллерами MSP430. Я только поясню некоторые редко освещаемые в прессе, но полезные для повторения, моменты.

Для инициализации тактовой системы ядра я использовал пару констант — CALDCO_8MHZ и CALBC1_8MHZ. Эти константы «прописаны» в файле msp430g2452.h, который находится в директории

/usr/local/msp430/include

В этом файле со строки 737 имеется вот такой код:

/************************************************************
* Calibration Data in Info Mem
************************************************************/

#ifndef __DisableCalData

#define CALDCO_16MHZ_         0x10F8    /* DCOCTL  Calibration Data for 16MHz */
const_sfrb(CALDCO_16MHZ, CALDCO_16MHZ_);
#define CALBC1_16MHZ_         0x10F9    /* BCSCTL1 Calibration Data for 16MHz */
const_sfrb(CALBC1_16MHZ, CALBC1_16MHZ_);
#define CALDCO_12MHZ_         0x10FA    /* DCOCTL  Calibration Data for 12MHz */
const_sfrb(CALDCO_12MHZ, CALDCO_12MHZ_);
#define CALBC1_12MHZ_         0x10FB    /* BCSCTL1 Calibration Data for 12MHz */
const_sfrb(CALBC1_12MHZ, CALBC1_12MHZ_);
#define CALDCO_8MHZ_          0x10FC    /* DCOCTL  Calibration Data for 8MHz */
const_sfrb(CALDCO_8MHZ, CALDCO_8MHZ_);
#define CALBC1_8MHZ_          0x10FD    /* BCSCTL1 Calibration Data for 8MHz */
const_sfrb(CALBC1_8MHZ, CALBC1_8MHZ_);
#define CALDCO_1MHZ_          0x10FE    /* DCOCTL  Calibration Data for 1MHz */
const_sfrb(CALDCO_1MHZ, CALDCO_1MHZ_);
#define CALBC1_1MHZ_          0x10FF    /* BCSCTL1 Calibration Data for 1MHz */
const_sfrb(CALBC1_1MHZ, CALBC1_1MHZ_);

#endif /* #ifndef __DisableCalData */

Что это такое. Дело в том, что производитель МК MSP430 — фирма Texas Instruments — помимо контроля своих изделий производит еще запись калиброванных нескольких калиброванных значений для регистров тактового генератора DCO во flash информационную память. Для процессора MSP430G2452 имеются четыре пары констант для записи в регистры DCOCTL и BCSCTL1. Четыре пары констант для формирования тактовой частоты в 1, 8, 12 и 16 МГц.

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

0.9992, 7.89, 12.057 и 16.64 МГц.

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

1. Температурная стабильность генератора DCO не очень высокая. При изменении температуры частоты «поплывет» значительно сильнее, чем у кварцевого резонатора.

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

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

Ну вот, вроде как разобрались, откуда что взялось. Остальные наименования регистров и битов не поленитесь самостоятельно посмотреть в документации на процессор. Это очень полезно!

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

// hal.h

#ifndef __HAL_H__
#define __HAL_H__

// Задержка в микросекундах
// Действительна для Fcpu = 8 МГц
void delay_us(uint16_t us);

// Задержка в мииллисекундах
// Предполагается, что частота источника = 1 МГц
void delay_ms(uint16_t ms);

void InitSystem(void); // Инициализация системы

#endif
// hal.c

#include
#include
#include "hal.h"

// Задержка в микросекундах
// Действительна для Fcpu = 8 МГц
/*
0000e076 <delay_us>:
e076:    03 43           nop
e078:    03 43           nop
e07a:    03 43           nop
e07c:    03 43           nop
e07e:    03 43           nop
e080:    3f 53           add    #-1,    r15    ;r3 As==11
e082:    f9 23           jnz    $-12         ;abs 0xe076
e084:    30 41           ret
*/
void delay_us(uint16_t us)
{
  do {
    _NOP();
    _NOP();
    _NOP();
    _NOP();
    _NOP();
  } while (--us != 0);
}

// Задержка в мииллисекундах
// Предполагается, что частота источника = 1 МГц
void delay_ms(uint16_t ms)
{
  uint16_t i;

  for (i = ms; i != 0; i--)
  {
    TACCTL0 &= ~CCIFG;      // Сбрасываем флаг прерывания
    TACTL   |= TACLR + MC_1; // Очищаем счетчик и начинаем отсчет периода времени в 1 мс

    while ((TACCTL0 & CCIFG) == 0)
      ;
  }

  TACTL &= ~MC_1;  // Останавливаем таймер
}

// Инициализация системы
void InitSystem(void)
{
  uint8_t i;

  WDTCTL = WDTPW + WDTHOLD;  // Останавливаем собаку

  // Инициализируем MCLK
  DCOCTL  = CALDCO_8MHZ;
  BCSCTL1 = CALBC1_8MHZ;

  // Устанавливаем источник частоты для таймера Timer_A
  TACTL &= MC_0;  // Останавливаем таймер
  TACTL |= ID_3 + TASSEL_2;  //  SMCLK / 8 = 1 MHz
  TACCR0 = 1000;  // 1 MHz / 1000 = 1 kHz, T = 1 мc
}

Берите их и используйте их в качестве основы в своих проектах.

Тот, кто шустрее чем остальные, наверняка уже начал писать свой код точного моргания светодиодами. И, по правде говоря, — сделал очень даже правильно!

Реклама

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s