Перейти к содержанию
    

MSP430 + Timer + Interrupt

Читаю Student Guide and Lab Manual и Family User's Guide и стали мне непонятны некоторые строки из кода лабораторной.

В лабораторной 4 нашел пример кода.

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

 

 

#include <msp430g2553.h>

volatile unsigned int i;

void ConfigWDT(void);
void ConfigClocks(void);
void ConfigLEDs(void);
void ConfigTimerA2(void);

void main(void)
{
  ConfigWDT();
  ConfigClocks();
  ConfigLEDs();
  ConfigTimerA2();

  _BIS_SR(GIE);   // РАЗРЕШАЕМ ПРЕРЫВАНИЕ - ЭТО ПОНЯТНО

  while(1)
  {
   P1OUT |= BIT0;
   for (i = 100; i > 0; i--);
   P1OUT &= ~BIT0;
   for (i = 5000; i > 0; i--);
  }
}

void ConfigWDT(void)
{
WDTCTL = WDTPW + WDTHOLD;                     // Stop watchdog timer
}

void ConfigClocks(void)    // НЕПОНЯТНАЯ ЧАСТЬ,НО ОНА У ВСЕХ ПРИСУТСТВУЕТ,Я РЕШИЛ ОСТАВИТЬ
{
  BCSCTL1 = CALBC1_1MHZ;                     // Set range
  DCOCTL = CALDCO_1MHZ;                      // Set DCO step + modulation
}

void ConfigLEDs(void)
{
  P1DIR = BIT6 + BIT0;                      // P1.6 and P1.0 outputs
  P1OUT = 0;                                // LEDs off
}

void ConfigTimerA2(void)
  {
   TACCR0 = 62500 - 1;    // Период в 62,500 цикла, от 0 до 62,499.                    /// ПОЧЕМУ 62500 ОТСТЧЕТОВ ВСЕГО ????????
   TACCTL0 = CCIE;        // Разрешаем прерывание таймера по достижению значения CCR0.    // ЭТО ПОНЯТНО
   TACTL = TASSEL_2     // ПОНЯЛ ЧТО ЭТО ВЫБОР ИСТОЧНИКА ТАКТОВОГО СИГНАЛА, НО В ЧЕМ МЕЖДУ НИМИ РАЗНИЦА ? ГДЕ ПРОЧЕСТЬ ?
               + ID_3           // ЭТО ПОНЯТНО. НО КАКАЯ ИЗНАЧАЛЬНАЯ ЧАСТОТА КОТОРУЮ МЫ ДЕЛИМ ?
                + MC_1;     // ПОНЯТНО
  }

#pragma vector=TIMER0_A0_VECTOR          // НИ В ОДНОЙ ИЗ ВЫШЕ ОПИСАННЫХ КНИЖЕК НЕ НАПИСАНО ПОЧЕМУ ТАКОЙ СИНТАКСИС
__interrupt void Timer_A (void)                     // ПОЧЕМУ ТАКОЙ СИНТАКСИС ?
{
  P1OUT ^=BIT6;                                 
}

 

 

1. Если выше описано что в таймере 62500 отсчетов. А так же включен делитель частоты /8 - то изначальная частота была 8 *62500 = 500кГц ???

Т.е. SMCLK что в коде включен имеет такую частоту ?

 

2. Как мне реализовать 2 таймера ?

Мне нужно чтоб один обращался к прерыванию с частотой 48кГц , а второй 200 Гц.

Идея в том что 48кГц будет забираться данные с АЦП , потом еще и фильтроваться будет.

А 200гц - вывод на индикатор

 

 

Изменено пользователем Niketa

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1. Частота прерываний от TACCR0=SMCLK/ID3/TACCR0=1000000/8/62500=2Гц. Т.к. в прерывании выполняется инверсия вывода P1.6, то на нём будет меандр с частотой импульсов 1Гц.

TASSEL_2 - выбор тактирования таймера от источника SMCLK. SMCLK вы отдельно не настраивали в модуле тактовых сигналов. Значит тактирование идёт по умолчанию после сброса от DCO ( настройка частоты DCO - в ConfigClocks).

Посмотрите файл msp430g2553.h - в нём есть определения битов и векторов прерываний. Можно посмотреть книжки по описанию модулей MSP430 по русски для других вариантов этого контроллера - они похожи. Раньше книжки были на сайте ф.КОМПЭЛ.

 

2. Данные с АЦП нужно не забирать, а настроить АЦП на оцифровку какого либо канала (или группы каналов) с частотой 48кГц. При тактовой частоте 1МГц вряд ли успеете потом обсчитать. Нужно поднимать частоту тактироания процессора MCLK. Запуск АЦП можно сделать по разному: либо настроить на запуск преобразований от таймера, либо сам АЦП будет работать от внутреннего таймера выборки/хранения с этой частотой после однократного запуска. Читайте режимы работы АЦП. Формирование частоты 200Гц можно сделать путём подсчёта в обработчике прерываний таймера. Делаете в таймере частоту прерываний от TACCR0 48кГц и вводите в обработчике прерываний переменную с инкрементом. Переменную сравниваете с числом (240 т.к. 48000/240=200Гц). Если больше или равно 240 - установить флаг (или выполнить вывод на индикатор) и сбросить в ноль переменную. Если нужны точные частоты и временные интервалы, то лучше поставить кварцевый резонатор.

Изменено пользователем E.V.G.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот я написал Код.

С частотой 40кГц считывается АЦП.

На Индикатор выводится в основной программе под циклом While. Вывод на индикатор в данном случае не так важно.

 

#include <msp430g2553.h>

unsigned int digit; /// переменная для значения 1 разряда
unsigned int pos = 1;    /// номер разряда
unsigned int value;   // переменная для значения выводимого числа
unsigned int buffer;   // переменная для значения ADC

unsigned int i;   // счетчик

void init_led(void)    // инициализация индикатора (описание смотрите в даташите)
{
    P1REN &= ~(BIT1+BIT2+BIT3+BIT4+BIT5+BIT6);
    P2REN &= ~(BIT0+BIT1+BIT2+BIT3+BIT4+BIT5);
    P2REN &= ~(BIT3+BIT4+BIT5);
    P1REN &= ~BIT6;
    P2DIR |= (BIT0+BIT1+BIT2);
    P1DIR |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2OUT |= (BIT0+BIT1+BIT2);
    P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2DIR |=(BIT3+BIT4+BIT5);
    P1DIR |=BIT6;
    P2OUT |=(BIT3+BIT4+BIT5);
    P1OUT |=BIT6;
}

void clear_led(void) {            // обнуление. выключаем все регистры и цифры.
    P2OUT |= (BIT0+BIT1+BIT2);
    P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2OUT |= (BIT3+BIT4+BIT5);
    P1OUT |= BIT6;
}

void show_number(number) {     // сопоставление ножек установки числа
    switch (number) {
        case 0 :
            P1OUT &=~(BIT1+BIT2+BIT3+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 1:
            P1OUT &=~(BIT3+BIT5);
            break;
        case 2:
            P1OUT &=~(BIT1+BIT3+BIT4);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 3:
            P1OUT &=~(BIT1+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
        case 4:
            P1OUT &=~(BIT2+BIT3+BIT4+BIT5);
            break;
        case 5:
            P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
        case 6:
            P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 7:
            P1OUT &=~(BIT1+BIT3+BIT5);
            break;
        case 8:
            P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 9:
            P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
    }
}

void show_registr(registr) {   // сопоставление ножек управления регистра
    switch (registr) {
        case 4:
            P1OUT &=~BIT6;
            break;
        case 3:
            P2OUT &=~BIT5;
            break;
        case 2:
            P2OUT &=~BIT4;
            break;
        case 1:
            P2OUT &=~BIT3;
            break;
    }

}

void init_ADC(void) {
    ADC10CTL1 |= CONSEQ_0;        // single channel, single conversion
    ADC10CTL1 |= INCH_0
               + SHS_0            // use ADC10SC bit to trigger sampling
               + ADC10DIV_3    // clock divider = 4
               + ADC10SSEL_3;    // clock source = SMCLK

    ADC10CTL0 |= SREF_0        // reference voltages are Vss and Vcc
               + ADC10SHT_3    // 64 ADC10 clocks for sample and hold time (slowest)
               + ADC10ON
               + ENC;

    ADC10AE0 = BIT0;      // Разрешаем вход АЦП на порту P1.0
}

void ConfigClocks(void)
{
    BCSCTL1 = CALBC1_16MHZ;                     // Set range
    DCOCTL = CALDCO_16MHZ;                      // Set DCO step + modulation
}

void ConfigTimerA(void)
  {
    TACCR0 = 50;    // Обеспечиваем обращение к прерыванию 16MHZ/ 8 / 50 = 40kHz
    TACCTL0 = CCIE;        // Разрешаем прерывание таймера по достижению значения CCR0.
    TACTL = TASSEL_2 + ID_3 + MC_1;
  }


int main(void) {
    WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
    ConfigClocks();
    ConfigTimerA();
    init_led();
    init_ADC();

    _BIS_SR(GIE);

    while (1)
    {
        if (value == 0) {
            value=buffer;  // присваиваем результат к АЦП
            pos = 1;
        }
        digit = value % 10;    /// взятие остатка от деления
        clear_led();
        show_number(digit);    // выбор числа
        show_registr(pos);     // выбор сегмента
        value /= 10;           // деление без остатка. отбпрасываем число которое показали
        pos++;
    }
}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_CC0 (void)
{
/////////////////////////////////////C Частотой 40кГц работает ADC///////////////////////
    ADC10CTL0 |= ADC10SC; // включаем считвание с ацп
    while (ADC10CTL1 & ADC10BUSY); // ждем пока все считает
    buffer = ADC10MEM;
/////////////////////////////////////////////////////////////////////////////////////////

}

 

 

Вопросы:

 

1.Что делаю неправильно ?

 

2.В дальнейшем я планирую сделать фильтрацию частоты 15кГц. Т.е. счиитывается с частотой 40кГц а я ППФ спроектирую которsй будет вырезать где то 15кГц +-300Гц. И собственно вопрос. Сколько у меня запас по отсчетам есть микропроконтроллера до следующего считывания АЦП и так чтоб мигание вело себя корректно.

 

3.

Если нужны точные частоты и временные интервалы, то лучше поставить кварцевый резонатор.

Не совсем понял что это за резонатор. Исходя из мануала я вычитал что это какой то генератор. Но в чем он лучше ?

 

4.Как настроить второе прерывание с другой частотой ? По CC1 сравнивал чтоб в другом прерывании код выполнял. В описании g2553h есть такой пункт. Но что то он не работает.А в мануале не написано как настроить сравнение с CC1.

Сейчас while(1) выводит информацию на индикатор(с прерыванием на АЦП).

НО! Я хочу и не спрашивайте почему (для практики) сделать так чтоб :

-buffer - Значение АЦП меняется с частотой 40кГЦ

-value - то значение что выводится на индкатор (=buffer_2)

-buffer_2 - с частотой 20Гц(примерно) = buffer

 

пока реализовано это в том же прерывании

    if (i==40000/10) {
        i=0;
        buffer_2=buffer;
    }
    i++;

Но хочу чтоб было в отдельном прерыании, просто для практики

Изменено пользователем Niketa

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1. Смотрите внимательно юзер мануал

timer.png

Пример реализации тут в файле msp430g2x33_adc10_13.c.

Запускайте измерения по таймеру и не надо никаких прерываний таймера, а просто по прерыванию АЦП читайте.

2. Тут уж как реализуете. Сложно оценить.

3. Это внешний кварцевый резонатор, имеет очень стабильную частоту, заданную на заводе. Частоты бывают разные до десятков МГц. Наверное слышали выражение "кварцевые часы". Так называют часы, секунды которых задает кварцевый резонатор частотой 32768 Гц. Например без кварца не сделать часы, будильник и прочие критичные ко времени выполнения задачи, чтобы время отсчета таймера не плыло.

4. Пример для справки. Вместо TA0 - TA, т.к. для msp430g2553 таймеры зовутся иначе.

void Init_Timer0 (void){
    TA0CTL = TIMER0_SOURCE_CLK    |    /// Источник тактирования, подставьте своё
            TIMER0_SOURCE_DIV;            /// Предделитель частоты тактирования, подставьте своё
    /// Значение счетчика для сравнения, подставьте своё
    TA0CCR1 = (SMCLK_FREQUENCY_HZ /  TIMER0_DIV_VALUE) / TIMER0_CCR1_RATE_HZ;
    TA0CCR2 = (SMCLK_FREQUENCY_HZ /  TIMER0_DIV_VALUE) / TIMER0_CCR2_RATE_HZ;
    TA0CCTL1 = CCIE;            /// Разрешает прерывание от CCR1
    TA0CCTL2 = CCIE;            /// Разрешает прерывание от CCR2
    /// Сбрасывает флаг, задает режим бесконечного счета, т.е. запускает таймер
    TA0CTL |= TACLR | MC_2;// | TAIE; /// ВАЖНО, прерывание общее от таймера не включаем
}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR( void ) {

    switch(__even_in_range(TA0IV,14))
    {
        case  0:                                     // No interrupt
            break;
        case  0x02:                                 // CCR1
        
                /** Код **/

        case  0x04:                                    // CCR2

                /** Код **/

        case  0x06:                                    // CCR3
            break;
        case  0x08:                                    // CCR4
            break;
        case 0x0A:                                    // CCR5
            break;
        case 0x0C:                                    // CCR6
            break;
        case 0x0E:                                     // overflow
            break;
        default:
            break;
    }
}

 

Допустимые источники прерываний для своего МК смотреть в юзер мануале в разделе 12.3.5 TAIV, Timer_A Interrupt Vector Register.

Изменено пользователем Mihey_K

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1. Я знаю что что неправильно АЦП включать в прерывании.

Но это сделано для того чтоб я в нем мог математические операции по фильтрации сделать

Вот тут сразу вопрос.

Как посчитать правильно сколько тактов работы проца у меня между прерываниями и сколько тактов работы проца занимает один проход мигания

 

2. ясно

 

3. Частота маловата. в комплекте есть такой генератор.

 

4. Огромное спасибо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3. Частота маловата. в комплекте есть такой генератор.
Наверное плохо объяснил - кварцы бывают разных частот, е не только 32768.

Чтобы посчитать кол-во тактов, надо открыть ассемблерный листинг и считать, сколько команд МК будет исполнять. Время выполнения одной команды фиксировано - 1 такт, кроме команд условий и перехода. 1 такт - это 1/Fosc. Лучше написать и потом оптимизировать, чем считать. 16 МГц это большая скорость, думаю не заметите, если конечно не брать тысячи отсчетов

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Чтобы посчитать кол-во тактов, надо открыть ассемблерный листинг и считать, сколько команд МК будет исполнять. Время выполнения одной команды фиксировано - 1 такт, кроме команд условий и перехода. 1 такт - это 1/Fosc.

Не вводите в заблуждение начинающего! :1111493779: Как мне кажется, упоминая об условных командах и переходах, вы путаете (или смешиваете) длину команды (количество слов, занимаемое командой в памяти) и количество машинных циклов ядра, необходимых для исполнения команды. Одноцикловых команд у MSP430 весьма немного - в основном это те команды, которые работают исключительно с регистрами. Большинство же команд (даже некоторые из тех, которые занимают в памяти всего одно слово) выполняются за 2-6 циклов. Таблицы с описанием команд, их длиной и количеством циклов, есть в User's manual. Поскольку у MSP430 две разновидности ядра - CPU, поддерживающее команды с 16-битной адресацией, и CPUX, поддерживающее команды с 20-битной адресацией, то для ознакомления можно почитать, например, MSP430x2xx Family User's Guide (Rev. J), поскольку там описаны обе разновидности. Узнать время выполнения куска программы можно и в симуляторе. У IAR C-Cpy, например, в составе CPU Registers для этого есть CYCLECOUNTER.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Rezident, хорошее пояснение. Скажу честно, не заморачивался количеством тактов на команду у MSP :-) это же RISC архитектура, длина команды фиксирована. Другое дело конвейер приходится выгружать при исполнении условных переходов

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Другое дело конвейер приходится выгружать при исполнении условных переходов

Какой конвейер? Вы опять MSP430 с чем-то (с ARM/Cortex?) путаете?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

АЦП считывает с частотой 40кГц по таймеру

 

Так же замутил фильтр полосо пропускающий на частоте 13.5кГц +-500Гц

 

Запрогал алгоритм (предварительно смоделировал его в Матлабе)

 

Но как оказалось слабый очень msp430g2553

 

У меня из обработчика прерывания не выходит. Только в нем крутится и поэтому данные на Индикатор не выводятся.

 

Вроде 16МГц.Я думал успеет посчитать. Всего то 40кГц таймер....

 

#include <msp430g2553.h>

unsigned int digit; /// переменная для значения 1 разряда
unsigned int pos = 1;    /// номер разряда
unsigned int value;   // переменная для значения выводимого числа
unsigned int buffer;   // переменная для значения ADC
unsigned int buffer_2;   // переменная для значения ADC
unsigned int x1[3],x2[3],x3[3],x4[3],x5[3];
unsigned int y1[3],y2[3],y3[3],y4[3],y5[3];
unsigned int i;   // счетчик

void init_led(void)    // инициализация индикатора (описание смотрите в даташите)
{
    P1REN &= ~(BIT1+BIT2+BIT3+BIT4+BIT5+BIT6);
    P2REN &= ~(BIT0+BIT1+BIT2+BIT3+BIT4+BIT5);
    P2REN &= ~(BIT3+BIT4+BIT5);
    P1REN &= ~BIT6;
    P2DIR |= (BIT0+BIT1+BIT2);
    P1DIR |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2OUT |= (BIT0+BIT1+BIT2);
    P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2DIR |=(BIT3+BIT4+BIT5);
    P1DIR |=BIT6;
    P2OUT |=(BIT3+BIT4+BIT5);
    P1OUT |=BIT6;
}

void clear_led(void) {            // обнуление. выключаем все регистры и цифры.
    P2OUT |= (BIT0+BIT1+BIT2);
    P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
    P2OUT |= (BIT3+BIT4+BIT5);
    P1OUT |= BIT6;
}

void show_number(number) {     // сопоставление ножек установки числа
    switch (number) {
        case 0 :
            P1OUT &=~(BIT1+BIT2+BIT3+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 1:
            P1OUT &=~(BIT3+BIT5);
            break;
        case 2:
            P1OUT &=~(BIT1+BIT3+BIT4);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 3:
            P1OUT &=~(BIT1+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
        case 4:
            P1OUT &=~(BIT2+BIT3+BIT4+BIT5);
            break;
        case 5:
            P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
        case 6:
            P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 7:
            P1OUT &=~(BIT1+BIT3+BIT5);
            break;
        case 8:
            P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0+BIT2);
            break;
        case 9:
            P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
            P2OUT &=~(BIT0);
            break;
    }
}

void show_registr(registr) {   // сопоставление ножек управления регистра
    switch (registr) {
        case 4:
            P1OUT &=~BIT6;
            break;
        case 3:
            P2OUT &=~BIT5;
            break;
        case 2:
            P2OUT &=~BIT4;
            break;
        case 1:
            P2OUT &=~BIT3;
            break;
    }

}

void init_ADC(void) {
    ADC10CTL1 |= CONSEQ_0;        // single channel, single conversion
    ADC10CTL1 |= INCH_0
               + SHS_0            // use ADC10SC bit to trigger sampling
               + ADC10DIV_3    // clock divider = 4
               + ADC10SSEL_3;    // clock source = SMCLK

    ADC10CTL0 |= SREF_0        // reference voltages are Vss and Vcc
               + ADC10SHT_3    // 64 ADC10 clocks for sample and hold time (slowest)
               + ADC10ON
               + ENC;

    ADC10AE0 = BIT0;      // Разрешаем вход АЦП на порту P1.0
}

void ConfigClocks(void)
{
    BCSCTL1 = CALBC1_16MHZ;                     // Set range
    DCOCTL = CALDCO_16MHZ;                      // Set DCO step + modulation
}

void ConfigTimerA(void)
  {
    TACCR0 = 50;    // Обеспечиваем обращение к прерыванию 16MHZ/ 8 / 50 = 40kHz
    TACCTL0 = CCIE;        // Разрешаем прерывание таймера по достижению значения CCR0.
    TACTL = TASSEL_2 + ID_3 + MC_1;
  }


int main(void) {
    WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
    ConfigClocks();
    ConfigTimerA();
    init_led();
    init_ADC();

    _BIS_SR(GIE);

    while (1)
    {
        if (value == 0) {
            value=buffer_2;  // присваиваем результат к АЦП
            pos = 1;
        }
        digit = value % 10;    /// взятие остатка от деления
        clear_led();
        show_number(digit);    // выбор числа
        show_registr(pos);     // выбор сегмента
        value /= 10;           // деление без остатка. отбпрасываем число которое показали
        pos++;
    }
}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_CC0 (void)
{
/////////////////////////////////////C Частотой 40кГц работает ADC///////////////////////
    ADC10CTL0 |= ADC10SC; // включаем считвание с ацп
    while (ADC10CTL1 & ADC10BUSY); // ждем пока все считает
/////////////////////////////////////////////////////////////////////////////////////////

    x1[2]=x1[1];
    x1[1]=x1[0];
    x1[0]=ADC10MEM;

                   y1[0]=(5*x1[0]+6*x1[1]+5*x1[2]-10*y1[1]-8*y1[2])/8;
    x2[0]=y1[0];
    x2[1]=y1[1];
    x2[2]=y1[2];
    y1[2]=y1[1];
    y1[1]=y1[0];

                    y2[0]=(5*x2[0]+4*x2[1]+5*x2[2]-7*y2[1]-8*y2[2])/8;
    x3[0]=y2[0];
    x3[1]=y2[1];
    x3[2]=y2[2];
    y2[2]=y2[1];
    y2[1]=y2[0];


                    y3[0]=(3*x3[0]+5*x3[1]+3*x3[2]-9*y3[1]-7*y3[2])/8;
    x4[0]=y3[0];
    x4[1]=y3[1];
    x4[2]=y3[2];
    y3[2]=y3[1];
    y3[1]=y3[0];


                    y4[0]=(3*x4[0]+2*x4[1]+3*x4[2]-7*y4[1]-7*y4[2])/8;
    x5[0]=y4[0];
    x5[1]=y4[1];
    x5[2]=y4[2];
    y4[2]=y4[1];
    y4[1]=y4[0];

                    y5[0]=(1*x5[0]+0*x5[1]-1*x5[2]-8*y5[1]-6*y5[2])/8;
    y5[2]=y5[1];
    y5[1]=y5[0];

    buffer=y5[0];

    if (i==40000/10) {
        i=0;
        buffer_2=buffer;
    }
    i++;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

АЦП считывает с частотой 40кГц по таймеру

Вы используете программный запуск преобразования АЦП. Если хотите, чтобы был аппаратный запуск по таймеру, то измените настройки АЦП. Вам Mihey_K уже указывал на этот способ в сообщении выше.

Но как оказалось слабый очень msp430g2553

 

У меня из обработчика прерывания не выходит. Только в нем крутится и поэтому данные на Индикатор не выводятся.

 

Вроде 16МГц.Я думал успеет посчитать. Всего то 40кГц таймер....

Дык, а что вы ожидали? У вас же прямо в прерывании таймера ожидается готовность АЦП. Зачем??? Либо проверяйте готовность результата опросом флага ADC10IFG прямо в main-е, либо уберите проверку ADC10BUSY из обработчика прерывания таймера. Вообще функции обработки прерываний должны быть как можно короче. Вам весь функционал свободно можно перенести в main. А в прерывании от АЦП всего лишь считывать результат в буфер и устанавливать свой программый флаг о готовности этого результата. Раз нужна синхронизация от таймера, то настройте работу АЦП на запуск преобразования от этого таймера. Тогда результаты преобразований вы будете получать с частотой периода работы таймера, но в прерывании от АЦП.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ёшкин кот, зачем математику-то всю в прерывание запихали. Делайте проходом: завести буфер, в него в прерывании класть измерения до тех пор, пока переменная счетчик не достигнет нужной выборки, выставляете переменную флаг готовности данных в прерывании. В программе опрашиваете все время этот флаг. Как только он установлен, обсчитываете, выдаете на экран, сбрасываете флаг и по новой накапливаете данные в прерывании. На время обсчета можно АЦП остановить. Или завести 2 буфера, в один класть данные АЦП, второй обсчитывать, потом менять их местами.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так.Не думал что вернусь в тему,но увы проблемы все такие есть.

После длительной паузы опять сел дорабаывать прогу.

 

И так что я имею.

 

Таймер - 40кГц

АЦП по прерыванию счиывает данные.

В обработчике эти данные я забираю. И с меньше частотой вывожу на Индикатор.

Программа работает.

 

#include <msp430g2553.h>

unsigned int digit; /// переменная для значения 1 разряда
unsigned int pos = 1;    /// номер разряда
unsigned int value;   // переменная для значения выводимого числа
unsigned int buffer;   // переменная для значения ADC
unsigned int buffer_2;   // переменная для значения ADC
unsigned int i;   // счетчик
unsigned int x1[3]=0;
unsigned int y1[3]=0,y2[3]=0,y3[3]=0,y4[3]=0,y5[3]=0;

void init_led(void)    // инициализация индикатора (описание смотрите в даташите)
{
   P1REN &= ~(BIT1+BIT2+BIT3+BIT4+BIT5+BIT6);
   P2REN &= ~(BIT0+BIT1+BIT2+BIT3+BIT4+BIT5);
   P2REN &= ~(BIT3+BIT4+BIT5);
   P1REN &= ~BIT6;
   P2DIR |= (BIT0+BIT1+BIT2);
   P1DIR |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2DIR |=(BIT3+BIT4+BIT5);
   P1DIR |=BIT6;
   P2OUT |=(BIT3+BIT4+BIT5);
   P1OUT |=BIT6;
}

void clear_led(void) {            // обнуление. выключаем все регистры и цифры.
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT3+BIT4+BIT5);
   P1OUT |= BIT6;
}

void show_number(number) {     // сопоставление ножек установки числа
   switch (number) {
       case 0 :
           P1OUT &=~(BIT1+BIT2+BIT3+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 1:
           P1OUT &=~(BIT3+BIT5);
           break;
       case 2:
           P1OUT &=~(BIT1+BIT3+BIT4);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 3:
           P1OUT &=~(BIT1+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 4:
           P1OUT &=~(BIT2+BIT3+BIT4+BIT5);
           break;
       case 5:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 6:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 7:
           P1OUT &=~(BIT1+BIT3+BIT5);
           break;
       case 8:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 9:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
   }
}

void show_registr(registr) {   // сопоставление ножек управления регистра
   switch (registr) {
       case 4:
           P1OUT &=~BIT6;
           break;
       case 3:
           P2OUT &=~BIT5;
           break;
       case 2:
           P2OUT &=~BIT4;
           break;
       case 1:
           P2OUT &=~BIT3;
           break;
   }

}

void init_ADC(void) {
   ADC10CTL1 |= CONSEQ_2;        // Циклический,одноканальный
   ADC10CTL1 |= INCH_0				//A0
              + SHS_2;            // Модуль вывода 0 Таймера А

   ADC10CTL0 |= SREF_0        // Опорные Vcc Vss
              + ADC10SHT_3    // 64 такта ADC10CLK
              + ADC10ON       //Модуль ADC10 включён
              + ENC		//Работа ADC10 разрешена
              + ADC10IE;	//Прерывание разрешено

   ADC10AE0 = BIT0;      // Разрешаем вход АЦП на порту P1.0
}

void ConfigClocks(void)
{
   BCSCTL1 = CALBC1_16MHZ;                     // Set range
   DCOCTL = CALDCO_16MHZ;                      // Set DCO step + modulation
}

void ConfigTimerA(void)
 {
   TA0CCR0 = 400;    // Обеспечиваем обращение к прерыванию 16MHZ/ 400 = 40000 Hz
   TA0CCTL0 = OUTMOD_2;        // Режим работы модуля вывода. "Переключение"
   TA0CTL = TASSEL_2     //SMCLK
   		+ MC_1 		 //Прямой счёт (таймер считает от 0000h до TACCR0)
   		+ ID_0;     // и Делитель 1
 }


int main(void) {
   WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
   ConfigClocks();
   ConfigTimerA();
   init_led();
   init_ADC();

   _BIS_SR(GIE);  // Разрешаем прерывание

   while (1)
   {

   	//ТУТ ПОКА ПУСТО

   }
}

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{

buffer=ADC10MEM;   // с частотой 40000 гц берем значение с ацп

	//C с частотой 200Гц выводим на экран каждую цифру
	i++;
    if (i==40000/200) {
	i=0;
	if (value == 0) {
		value=buffer;  // присваиваем результат к АЦП
		pos = 1;
	}
	digit = value % 10;    /// взятие остатка от деления
	clear_led();
	show_number(digit);    // выбор числа
	show_registr(pos);     // выбор сегмента
	value /= 10;           // деление без остатка. отбпрасываем число которое показали
	pos++;
    }

}

 

 

Но вот куда добавить математику фильтрации?

 

Запихнул ее в While(1) в основную прогу с флагом.Если все правильно понял - как только данные считались - выполняется то что в цикле флага.

 

#include <msp430g2553.h>

unsigned int digit; /// переменная для значения 1 разряда
unsigned int pos = 1;    /// номер разряда
unsigned int value;   // переменная для значения выводимого числа
unsigned int buffer;   // переменная для значения ADC
unsigned int buffer_2;   // переменная для значения ADC
unsigned int i;   // счетчик
unsigned int x1[3]=0;
unsigned int y1[3]=0,y2[3]=0,y3[3]=0,y4[3]=0,y5[3]=0;

void init_led(void)    // инициализация индикатора (описание смотрите в даташите)
{
   P1REN &= ~(BIT1+BIT2+BIT3+BIT4+BIT5+BIT6);
   P2REN &= ~(BIT0+BIT1+BIT2+BIT3+BIT4+BIT5);
   P2REN &= ~(BIT3+BIT4+BIT5);
   P1REN &= ~BIT6;
   P2DIR |= (BIT0+BIT1+BIT2);
   P1DIR |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2DIR |=(BIT3+BIT4+BIT5);
   P1DIR |=BIT6;
   P2OUT |=(BIT3+BIT4+BIT5);
   P1OUT |=BIT6;
}

void clear_led(void) {            // обнуление. выключаем все регистры и цифры.
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT3+BIT4+BIT5);
   P1OUT |= BIT6;
}

void show_number(number) {     // сопоставление ножек установки числа
   switch (number) {
       case 0 :
           P1OUT &=~(BIT1+BIT2+BIT3+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 1:
           P1OUT &=~(BIT3+BIT5);
           break;
       case 2:
           P1OUT &=~(BIT1+BIT3+BIT4);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 3:
           P1OUT &=~(BIT1+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 4:
           P1OUT &=~(BIT2+BIT3+BIT4+BIT5);
           break;
       case 5:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 6:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 7:
           P1OUT &=~(BIT1+BIT3+BIT5);
           break;
       case 8:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 9:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
   }
}

void show_registr(registr) {   // сопоставление ножек управления регистра
   switch (registr) {
       case 4:
           P1OUT &=~BIT6;
           break;
       case 3:
           P2OUT &=~BIT5;
           break;
       case 2:
           P2OUT &=~BIT4;
           break;
       case 1:
           P2OUT &=~BIT3;
           break;
   }

}

void init_ADC(void) {
   ADC10CTL1 |= CONSEQ_2;        // Циклический,одноканальный
   ADC10CTL1 |= INCH_0				//A0
              + SHS_2;            // Модуль вывода 0 Таймера А

   ADC10CTL0 |= SREF_0        // Опорные Vcc Vss
              + ADC10SHT_3    // 64 такта ADC10CLK
              + ADC10ON       //Модуль ADC10 включён
              + ENC		//Работа ADC10 разрешена
              + ADC10IE;	//Прерывание разрешено

   ADC10AE0 = BIT0;      // Разрешаем вход АЦП на порту P1.0
}

void ConfigClocks(void)
{
   BCSCTL1 = CALBC1_16MHZ;                     // Set range
   DCOCTL = CALDCO_16MHZ;                      // Set DCO step + modulation
}

void ConfigTimerA(void)
 {
   TA0CCR0 = 400;    // Обеспечиваем обращение к прерыванию 16MHZ/ 400 = 40000 Hz
   TA0CCTL0 = OUTMOD_2;        // Режим работы модуля вывода. "Переключение"
   TA0CTL = TASSEL_2     //SMCLK
   		+ MC_1 		 //Прямой счёт (таймер считает от 0000h до TACCR0)
   		+ ID_0;     // и Делитель 1
 }


int main(void) {
   WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
   ConfigClocks();
   ConfigTimerA();
   init_led();
   init_ADC();

   _BIS_SR(GIE);  // Разрешаем прерывание

   while (1)
   {
   	while ((ADC10CTL0 & ADC10IFG))
   	{
   	    x1[0]=buffer; // входные данные с АЦП

   	    y1[0]=(9*x1[0]+13*x1[1]+9*x1[2]-20*y1[1]-15*y1[2])/16;
   	    y2[0]=(9*y1[0]+8*y1[1]+9*y1[2]-15*y2[1]-15*y2[2])/16;
   	    y3[0]=(6*y2[0]+9*y2[1]+6*y2[2]-18*y3[1]-14*y3[2])/16;
   	    y4[0]=(6*y3[0]+4*y3[1]+6*y3[2]-15*y4[1]-14*y4[2])/16;
   	    y5[0]=(2*y4[0]+0*y4[1]-2*y4[2]-16*y5[1]-13*y5[2])/16;

   	    buffer_2=y5[0];//

   	    x1[2]=x1[1];
   	    x1[1]=x1[0];
   	    y1[2]=y1[1];
   	    y1[1]=y1[0];
   	    y2[2]=y2[1];
   	    y2[1]=y2[0];
   	    y3[2]=y3[1];
   	    y3[1]=y3[0];
   	    y4[2]=y4[1];
   	    y4[1]=y4[0];
   	    y5[2]=y5[1];
   	    y5[1]=y5[0];

   	}

   }
}

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{

buffer=ADC10MEM;   // с частотой 40000 гц берем значение с ацп

	//C с частотой 200Гц выводим на экран каждую цифру
	i++;
    if (i==40000/200) {
	i=0;
	if (value == 0) {
		value=buffer_2;  // присваиваем результат к АЦП
		pos = 1;
	}
	digit = value % 10;    /// взятие остатка от деления
	clear_led();
	show_number(digit);    // выбор числа
	show_registr(pos);     // выбор сегмента
	value /= 10;           // деление без остатка. отбпрасываем число которое показали
	pos++;
    }

}

 

 

Но вот загвостка.

Я подаю на АЦП постоянный сигнал. АЧХ постоянного сигнала очевидна.Если я фильтрую частоту 13700 - то мне на выходе должен падать 0.

А вместо этого я получаю число 1456 +- небольшие помехи из за того что АЦП сам снимает с небольшими флуктуациями...

 

Фильтр в МАТЛАБЕ проектировал и там все тип топ.

 

 

Может с типами проблема. Или с тем что беззнаковые переменные использую....

А то мне кажется у меня в минус значения уходят....или нет....

 

Если меня с UNSIGNET INT на INT - то вроде похоже на правду , но проходит цикла 5-6 и больше в цикл математики не заходит...

В дебагере вижу что ацп считывает, но в математику не заходит.

 

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

Короче решил математику в обработчик запихнуть.вроде успевает считать.все тип топ.

 

ВОПРОС - Когда выполняется код из обоаботчика - таймер пересает считать ? нет ли из за этого сбивки по частоте ?

 

#include <msp430g2553.h>

int digit; /// переменная для значения 1 разряда
unsigned int pos = 1;    /// номер разряда
int value;   // переменная для значения выводимого числа
int buffer;   // переменная для значения ADC
int buffer_2;   // переменная для значения ADC
unsigned int i;   // счетчик
int x1[3]=0;
int y1[3]=0,y2[3]=0,y3[3]=0,y4[3]=0,y5[3]=0;

void init_led(void)    // инициализация индикатора (описание смотрите в даташите)
{
   P1REN &= ~(BIT1+BIT2+BIT3+BIT4+BIT5+BIT6);
   P2REN &= ~(BIT0+BIT1+BIT2+BIT3+BIT4+BIT5);
   P2REN &= ~(BIT3+BIT4+BIT5);
   P1REN &= ~BIT6;
   P2DIR |= (BIT0+BIT1+BIT2);
   P1DIR |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2DIR |=(BIT3+BIT4+BIT5);
   P1DIR |=BIT6;
   P2OUT |=(BIT3+BIT4+BIT5);
   P1OUT |=BIT6;
}

void clear_led(void) {            // обнуление. выключаем все регистры и цифры.
   P2OUT |= (BIT0+BIT1+BIT2);
   P1OUT |= (BIT1+BIT2+BIT3+BIT4+BIT5);
   P2OUT |= (BIT3+BIT4+BIT5);
   P1OUT |= BIT6;
}

void show_number(number) {     // сопоставление ножек установки числа
   switch (number) {
       case 0 :
           P1OUT &=~(BIT1+BIT2+BIT3+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 1:
           P1OUT &=~(BIT3+BIT5);
           break;
       case 2:
           P1OUT &=~(BIT1+BIT3+BIT4);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 3:
           P1OUT &=~(BIT1+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 4:
           P1OUT &=~(BIT2+BIT3+BIT4+BIT5);
           break;
       case 5:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
       case 6:
           P1OUT &=~(BIT1+BIT2+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 7:
           P1OUT &=~(BIT1+BIT3+BIT5);
           break;
       case 8:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0+BIT2);
           break;
       case 9:
           P1OUT &=~(BIT1+BIT2+BIT3+BIT4+BIT5);
           P2OUT &=~(BIT0);
           break;
   }
}

void show_registr(registr) {   // сопоставление ножек управления регистра
   switch (registr) {
       case 4:
           P1OUT &=~BIT6;
           break;
       case 3:
           P2OUT &=~BIT5;
           break;
       case 2:
           P2OUT &=~BIT4;
           break;
       case 1:
           P2OUT &=~BIT3;
           break;
   }

}

void init_ADC(void) {
   ADC10CTL1 |= CONSEQ_2;        // Циклический,одноканальный
   ADC10CTL1 |= INCH_0				//A0
              + SHS_2;            // Модуль вывода 0 Таймера А

   ADC10CTL0 |= SREF_0        // Опорные Vcc Vss
              + ADC10SHT_0    // 64 такта ADC10CLK
              + ADC10ON       //Модуль ADC10 включён
              + ENC		//Работа ADC10 разрешена
              + ADC10IE;	//Прерывание разрешено

   ADC10AE0 = BIT0;      // Разрешаем вход АЦП на порту P1.0
}

void ConfigClocks(void)
{
   BCSCTL1 = CALBC1_16MHZ;                     // Set range
   DCOCTL = CALDCO_16MHZ;                      // Set DCO step + modulation
}

void ConfigTimerA(void)
 {
   TA0CCR0 = 400;    // Обеспечиваем обращение к прерыванию 16MHZ/ 400 = 40000 Hz
   TA0CCTL0 = OUTMOD_2;        // Режим работы модуля вывода. "Переключение"
   TA0CTL = TASSEL_2     //SMCLK
   		+ MC_1 		 //Прямой счёт (таймер считает от 0000h до TACCR0)
   		+ ID_0;     // и Делитель 1
 }


int main(void) {
   WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
   ConfigClocks();
   ConfigTimerA();
   init_led();
   init_ADC();

   _BIS_SR(GIE);  // Разрешаем прерывание

   while (1)
   {
   	//while (!(ADC10CTL0 & ADC10IFG));
   }
}

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{

buffer=ADC10MEM;   // с частотой 40000 гц берем значение с ацп

x1[0]=buffer; // входные данные с АЦП

y1[0]=(5*x1[0]+6*x1[1]+5*x1[2]-10*y1[1]-8*y1[2])/8;
y2[0]=(5*y1[0]+4*y1[1]+5*y1[2]-7*y2[1]-8*y2[2])/8;
y3[0]=(3*y2[0]+5*y2[1]+3*y2[2]-9*y3[1]-7*y3[2])/8;
y4[0]=(3*y3[0]+2*y3[1]+3*y3[2]-7*y4[1]-7*y4[2])/8;
y5[0]=(1*y4[0]+0*y4[1]-1*y4[2]-8*y5[1]-6*y5[2])/8;

buffer_2=y5[0];//

x1[2]=x1[1];
x1[1]=x1[0];
y1[2]=y1[1];
y1[1]=y1[0];
y2[2]=y2[1];
y2[1]=y2[0];
y3[2]=y3[1];
y3[1]=y3[0];
y4[2]=y4[1];
y4[1]=y4[0];
y5[2]=y5[1];
y5[1]=y5[0];

//C с частотой 200Гц выводим на экран каждую цифру
	i++;
    if (i==40000/200) {
	i=0;
	if (value == 0) {
		value=abs(buffer_2);  // присваиваем результат к АЦП
		pos = 1;
	}
	digit = value % 10;    /// взятие остатка от деления
	clear_led();
	show_number(digit);    // выбор числа
	show_registr(pos);     // выбор сегмента
	value /= 10;           // деление без остатка. отбпрасываем число которое показали
	pos++;
    }

}

Изменено пользователем Niketa

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

///////////////////////////////////////////////////////////

еще как сюда уарт прикрутить ? чтоб на комп еще слать значения

В уже существующее прерывание от АЦП засунуть ?

или новое прерывание написать для уарта ?

Есть хоть один урок или пример нормальный ?

Изменено пользователем Niketa

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Куча примеров на сайте TI. Прям под твой МК. И уарт настраивать весьма просто.

А по работа с ним: есть 2 прерывания. Одно на прием, срабатывает когда пришел байт, считать можно в RXBUF. Другое на передачу, срабатывает когда передающий буфер TXBUF пустой.

И да, один момент, если по приему все просто, то передача организована след. образом: Есть сдвиговый регистр, откуда твой байт выдается на ножки МК, а в этот сдвиговый регистр байт попадает из TXBUF. Т.е. ты просто кидаешь байт в TXBUF, а дальше он сам переправляет его в сдвиговый регистр и выдает в UART.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...