Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Подружить ADC12 и TimerB
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > MSP430
Salazar
Здравствуйте. Я пишу программу, которая должна периодически просыпаться и передавать по UART значения с ADC с метками времени от TimerB.

По отдельности всё работает, но как только всё собирается воедино...
Сразу после включения ADC12 у меня перестаёт прерываться таймерБ.
Как быть?
VAI
Цитата
Как быть?

Понять, что здесь не общество телепатов. Код выкладывать надо.
Если будете вставлять в сообщение, не забывайте соответственно обрамлять его, чтоб сохранилось форматирование. (кнопка "Код" в расширеной форме ответа)
jorikdima
меня тоже это все очень интересует biggrin.gif Как раз сейчас тоже мучаюсь с запуском конвертациии по таймеру. Приведите код плиз
Salazar
Вот так я инициализирую АЦП:
Код
...
ADC12CTL0 = SHT0_0 + MSC + ADC12ON + ADC12OVIE;
ADC12CTL1 = SHS_0 + SHP + ADC12DIV_0 + ADC12SSEL_0 + CONSEQ_2;
ADC12MCTL0 = INCH_0 + SREF_6;    // ref+=AVcc, channel = A0
ADC12MCTL1 = INCH_1 + SREF_1 + EOS;   // ref+=AVcc, channel = A1
...


Меня интересует, почему параметры ADC12DIV_x и ADC12SSEL_x никак не изменяют частоту срабатывания прерывания АЦП.
И вообще может кто знает как задавать период срабатывания прерывания АДЦ?
jorikdima
Цитата(Salazar @ Jul 12 2007, 10:29) *
Вот так я инициализирую АЦП:
Код
...
ADC12CTL0 = SHT0_0 + MSC + ADC12ON + ADC12OVIE;
ADC12CTL1 = SHS_0 + SHP + ADC12DIV_0 + ADC12SSEL_0 + CONSEQ_2;
ADC12MCTL0 = INCH_0 + SREF_6;    // ref+=AVcc, channel = A0
ADC12MCTL1 = INCH_1 + SREF_1 + EOS;   // ref+=AVcc, channel = A1
...


Меня интересует, почему параметры ADC12DIV_x и ADC12SSEL_x никак не изменяют частоту срабатывания прерывания АЦП.
И вообще может кто знает как задавать период срабатывания прерывания АДЦ?


Период стабатывания можно определять по выходу OUT таймераА или Б (пункт 17.2.5). Либо вручную, по ADC12SC (тот же пункт). Поэтому покажите как вы таймер инитите.

Вообще я не понял, вам таймерБ нужен зачем? Запускать АЦП от него или передавать его значения по УАРТ?
Salazar
Например:
(ACLK = 32768)

TimerB срабатывает 100 раз в секунду:
Код
  TBCTL = CNTL_0 + TBSSEL_1 + MC_1;
  TBCCR0 = 328;
  TBCCTL0 = CCIE;


АЦП инициализирую на TimerB:
Код
ADC12CTL0 = SHT0_0 + MSC + ADC12ON + ADC12OVIE;
ADC12CTL1 = SHS_2 + SHP + ADC12DIV_0 + ADC12SSEL_1 + CONSEQ_2;


Модуль тактирования настроен так:
Код
BCSCTL2 = SELM_3 + DIVM_3 + DIVS_3 + DCOR;
BCSCTL1 = XT2OFF + DIVA_0;


В итоге, таймерБ работает как надо (в его обработчике прерывания инкрементируется RTC) а прерывание от адц не вызывается вообще.

А если работать через ADC12SC, то в какой регистр установить период? (в 17.2.5 не нашёл)
jorikdima
Если через ADC12SC то периода нету как такового - это одноразовый запуск сэмплирования. Есть вариант (наскока я понимаю), поставить ADC12SC в обработчик прерывания таймера (там где у вас часы инкрементятся ), но это не по-человечески, ибо все равно придется обрабатывать прерывания от АЦП, когда данные будут готовы (через несколько тактов после ADC12SC)

Попробуйте так.

Код
//TimerB.OUT0, АЦП тактируется от встроенного осциллятора, только один канал и один раз (в том смысле, что начинать преобразоввание мы будем САМИ по прерыванию в таймере)
ADC12CTL1 = SHS_0 + SHP + ADC12DIV_0 + ADC12SSEL_0 + CONSEQ_0;

ADC12CTL0 = SHT0_0 + ENC + ADC12ON;


//И это не забываем
ADC12IE = BIT0;


//В обработчик таймера ставим
ADC12CTL0 |= ADC12SC;



Не проверял. Не гарантирую.
VAI
Вот так я вызываю Преобразования с определенным периодом по таймеру.
Если нужна еще и передача оцифрованых значений по UART, то можно еще и это добавить, важно, чтоб успело передаться до следующего вызова.
Код
// --- Определения ---------------------------------------------------------------------------------------- **
#define Start_conversion()        { ADC12CTL0 |= ADC12SC; }
#define Enable_conversions()      { ADC12CTL0 |= ENC; }
#define Disable_conversions()     { ADC12CTL0 &= ~ENC; }
#define ADC_SUM                   4     // сколько преобразований суммируем и принимаем за 1 отсчет
#define ADC_MAX                   ( 4095 * ADC_SUM )
#define ADC_NORMA                 ( 3500 * ADC_SUM )


/* --- adc_ini() ------------------------------------------------------------------------------------------ **
*  Инициализация каналов АЦП
* -------------------------------------------------------------------------------------------------------- */
static void adc_ini( void )
{
  P6SEL = BINARY( 11111111 );                                     // весь порт используется, как входы АЦП
  P6DIR = BINARY( 00000000 );

  ADC12CTL0 = ADC12ON                                             // АЦП включено
            | SHT0_2                                              // время установленияния для ADC12MEM0…ADC12MEM7, tsample >= 2 us, сделаем 16/4us
            | SHT1_3                                              // время установленияния для ADC12MEM8…ADC12MEM15 сделаем 32/4us (tsample > 5us)
            | MSC                                                 // обрабатываем последовательность каналов
            | REFON + REF2_5V                                     // опорное напряжение 2.5В включено
          ;

  ADC12CTL1 = CSTARTADD_0                                         // данные будем помещать начиная с ADC12MEM0
            | SHP                                                 // Сигнал SAMPCON берется с таймера защёлки. Передний фронт сигнала Sample-input запускает таймер.
            | ADC12SSEL_2                                         // тактовая частота для ядра АЦП (ADC12CLK) - MCLK
            | ADC12DIV_1                                          // деленая на 2 - итого 4 МГц, время преобразования (13 * 1/4 + tsample)us
            | CONSEQ_1                                            // один цикл измерения последовательности каналов
          ;

// При перестановке каналов при инициализации АЦП не забыть переставить enum variable_resistor
  ADC12MCTL0 = INCH_2  + SREF_1;                                  // канал = A2,               Vr+=Vref+, Vr-=AVSS
  ADC12MCTL1 = INCH_0  + SREF_1;                                  // канал = A0,               Vr+=Vref+, Vr-=AVSS
  ADC12MCTL2 = INCH_3  + SREF_1;                                  // канал = A3,              Vr+=Vref+, Vr-=AVSS
  ADC12MCTL3 = INCH_1  + SREF_1;                                  // канал = A1,              Vr+=Vref+, Vr-=AVSS

  ADC12MCTL4 = INCH_4  + SREF_1;                                  // канал = A4,               Vr+=Vref+, Vr-=AVSS
  ADC12MCTL5 = INCH_6  + SREF_1;                                  // канал = A6,              Vr+=Vref+, Vr-=AVSS
  ADC12MCTL6 = INCH_5  + SREF_1;                                  // канал = A5,              Vr+=Vref+, Vr-=AVSS
  ADC12MCTL7 = INCH_7  + SREF_1 + EOS;                        // канал = A7, Vr+=Vref+, Vr-=AVSS + EOS

// -----------------------------------------------------------------------------------------------------------------
  ADC12IE = 1 << ( adc_cnt - 1 );                                 // Enable ADC12IFG.x - разрешим прерывание от последнего оцифровываемого канала
  Enable_conversions();
}

/* --- Timer_A_isr() -------------------------------------------------------------------------------------- **
*
* -------------------------------------------------------------------------------------------------------- */
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A_isr( void )
{

  tick_ms++;
  Start_conversion();
......
......
}

/* --- ADC12_isr() ---------------------------------------------------------------------------------------- **
*  Конец преобразования АЦП, вызывается по таймеру А с частотой 1000 Гц
* Накапливаем сигнал за ADC_SUM преобразований
* -------------------------------------------------------------------------------------------------------- */
#pragma vector=ADC_VECTOR
__interrupt void ADC12_isr( void )
{
unsigned short *adc_mem_ptr;
static int a_cnt;
int a;

  adc_mem_ptr = (unsigned short *)&ADC12MEM0;
  for ( a = 0; a < adc_cnt; a++ )                                 // остальные из ADC_SUM
    x_adc_t[a] += *adc_mem_ptr++;                                 // сохраним результаты АЦП во временный массив
  if ( ++a_cnt < ADC_SUM )
    return;
// Сюда попадает с частотой ( 1000 / ADC_SUM ) = 250 Гц
  for ( a_cnt = a = 0; a < adc_cnt; a++ )                         // сумма ADC_SUM отсчетов готова, перенесем "куда нада"
  { x_adc[a] = x_adc_t[a];
    x_adc_t[a] = 0;
  }
// Дальше не критично ко времени, лишь бы до следующего прерывания выполнилось, поэтому разрешим прерывания
  __enable_interrupt();
  if ( flag.channel_REF == OFF )                                  // Опорнай канал игнорируется
    x_adc[X_REF] = ADC_NORMA;
  ADC12_isr_add();  // Дальнейшая обработка, должна успеть закончится до следующего вызова
}


первый пробел съедается при форматировании....
Salazar
Всем спасибо.
То что я хотел решилось стартом прерывания АЦП из прерываия таймера.
aag
2 Salazar:

Напишите, плиз, итоговый код прерывания

Сейчас с аналогичной проблемой борюсь, на примере всеж легче учиться будет..
VAI
2 aag

А чем мой код не нравится?
aag
Ваш код нравится.

только я не все в нем понимаю: зачем нужно сохранять результат ацп во временный массив? почему нельзя сразу нельзя в "куда надо" кинуть? (а нельзя действительно - выкинул часть кода, работать перестало, только не понимаю почему)
jorikdima
Цитата(aag @ Jul 17 2007, 15:47) *
Ваш код нравится.

только я не все в нем понимаю: зачем нужно сохранять результат ацп во временный массив? почему нельзя сразу нельзя в "куда надо" кинуть? (а нельзя действительно - выкинул часть кода, работать перестало, только не понимаю почему)

это усреднение у него просто по нескольким выборкам


Проблема, на которую я нарвался заключалась в следующем.

Я хотел по прирыванию от таймера начинать оцифровку. Но при этом не хотел делать SC в обработчике, а хотел, чтоб оно автоматом запускалось по сигналу out от таймера. Но запускался АЦП только однажды и причиной тому был режим CONSEQ_0 а не CONSEQ_2.

Вывод. Если вручную хотите запускать то SC в прерывании таймера и CONSEQ_0. Если автоматом, то настройка таймера на SET/RESET, необрабатывать его прерывания и CONSEQ_2.

Я несколько неправильно понял доку поначалу.

Ясно выразился?
aag
В принципе, да.

У мне как раз и хотелось по таймеру А (или В, или просто по нажатию кнопки) однин раз считать значение напряжения на входе АЦП (пока только на одном пине, а не на всех сразу) и передать его куда-нибудь. но вот то что я написал не работает:
Код
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A_isr( void )
{
   if (!read)
   {
      ADC12CTL0 |= ADC12SC;
      read = true;
   }
  
   .....
}

#pragma vector=ADC_VECTOR
__interrupt void ADC12_isr( void )
{
   adc_value = ADC12MEM0 + (ADC12MEM1 << 8);
   read = false;
}


adc_value - глобальная переменная типа unsigned int, откуда потом можно будет считать данные о том, что было на входе АЦП
read - глобальная переменная, необходимая для того чтобы таймер знал, что данные еще не получены и не запускал АЦП заново

p.s. функция ADC12_isr_add() в коде VAI для его нужна? и где ее код взять?
jorikdima
ADC12_isr_add() его "личная" функция и совершенно не важно что она там делает.

1. Я не понял, что вы хотите сделать сторокой

Код
adc_value = ADC12MEM0 + (ADC12MEM1 << 8);


Если вы хотите в adc_value передать значения с 2 входов, то тут как минимум проблемы с разрядностью.
unsigned int - 16 бит
ADC12MEMx - 16 бит

В 16 битах 32 не передать smile.gif

Кроме того (ADC12MEM1 << 8): в 16 битовом числе вы младшие 12 бит сдвигаете на 8 влево. Что получите?

2.
Цитата
но вот то что я написал не работает


Что не работает то конкретно? Приведите обязательно все то что касается настройки таймера и АЦП.
aag
считывание значения заменил такой строкой:
Код
adc_value = ADC12MEM0;

(изначально я думал, что ADC12MEMx 8-битные, а раз ацп 12 разрядов, то соответственно первые 8 лежат в ADC12MEM(x), а оставшиеся 4 - в ADC12MEM(x+1) )

установки такие:
Код
ADC12CTL0 = ADC12ON | MSC | ENC | REF2_5V;
ADC12CTL1 = SHP;
P6SEL = 0x01;
ADC12IE = 0x01;


функция ADC12CTL0 |= ADC12SC; срабатывает по нажжатию кнопки.

вроде все должно нормально работать, только вот значения ADC12MEM0 не зависят от приложенного к пину напряжения почему-то. значение всегда равно 0x04D8
jorikdima
Цитата(aag @ Jul 18 2007, 07:23) *
считывание значения заменил такой строкой:
Код
adc_value = ADC12MEM0;

(изначально я думал, что ADC12MEMx 8-битные, а раз ацп 12 разрядов, то соответственно первые 8 лежат в ADC12MEM(x), а оставшиеся 4 - в ADC12MEM(x+1) )

установки такие:
Код
ADC12CTL0 = ADC12ON | MSC | ENC | REF2_5V;
ADC12CTL1 = SHP;
P6SEL = 0x01;
ADC12IE = 0x01;


функция ADC12CTL0 |= ADC12SC; срабатывает по нажжатию кнопки.

вроде все должно нормально работать, только вот значения ADC12MEM0 не зависят от приложенного к пину напряжения почему-то. значение всегда равно 0x04D8


По моему вы невнимательно читаете документацию. Доки от ТИ заслуживают уважения к себе biggrin.gif a14.gif

1. Я не вижу где вы настраиваете непосредственно канал (регистр ADC12MCTL0).

2. Читайте в самом верху на регистры ADC12CTL1, ADC12CTL0, ADC12MCTL0 : Modifiable only when ENC = 0
Это значит, что
Код
ADC12CTL0 |= ENC;
вы должны сделать в самом конце. Вообще полезно после инициализации всех регистров проверить, а приинитились ли они как надо.

3. Прерывания от таимера и от АЦП происходят??? Точку останова поставте в обработчиках.

4. Если прерывания есть, в дебуггере сбросте значение ADC12MEM0 в 0 и посмотрите на следующем прерывании оно 0x04D8? Если нет, то это значит, что значение не меняется, а не неправильное.
aag
Цитата
2. Читайте в самом верху на регистры ADC12CTL1, ADC12CTL0, ADC12MCTL0 : Modifiable only when ENC = 0
Это значит, что


Блин..

после того как сделал так:
Код
ADC12CTL0 = ADC12ON | MSC | REF2_5V;
ADC12CTL1 = SHP;
P6SEL = 0x01;
ADC12IE = 0x01;

ADC12CTL0 |= ENC;

все заработало. теперь при изменении напряжения, ADC12MEM0 принимает значения от 0x00 до 0x0F39
smile.gif
больше не хочет принимать, видимо недостаточно напряжения.

Вобщем, проблема решена. Спасибо огромное
jorikdima
Цитата(aag @ Jul 18 2007, 10:21) *
теперь при изменении напряжения, ADC12MEM0 принимает значения от 0x00 до 0x0F39
smile.gif
больше не хочет принимать, видимо недостаточно напряжения.



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