• I






      
           

Научно-популярный образовательный ресурс для юных и начинающих радиолюбителей - Popular science educational resource for young and novice hams

Основы электричества, учебные материалы и опыт профессионалов - Basics of electricity, educational materials and professional experience

КОНКУРС
language
 
Поиск junradio

Радиодетали
ОК
Сервисы

Stock Images
Покупка - продажа
Фото и изображений


 
Выгодный обмен
электронных валют

Друзья JR



JUNIOR RADIO

 

Как получить числовое значение ширины ШИМ-сигнала?





 

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

Задача:
Имеется приемник системы радиоуправления. Хотим узнать ширину импульса в микросекундах для канала Х.

Использованное оборудование:
МК - клон Arduino на базе ATmega168(или любой другой клон).
Шилд - Плата расширения для подключения дополнительных датчиков к Arduino. Можете обойтись и без него, пожертвовав удобством.
Система р/у - Турнига 9х или любая другая.
Экран - для контроля значения. Опять же использовал для удобства.
Серва - просто первая подвернувшаяся под руку.

ПО:
Arduino IDE 1.0.2
Библиотека PinChangeInt для удобства работы с прерываниями. Я пробовал и без нее - результат тот же, а с ней нагляднее. Потеря памяти меня устроила.
Библиотека для работы с экраном в архиве с исходниками. Уже не помню где нашел, но остальные память кушали совершенно нищадно и были отложены.
Библиотека RCArduinoFastLib для управления сервой.
Подключение:
1. Питание МК берем с USB.
2. Соединяем экран посредством стандартного шлейфа с колодкой 3х2 через последовательный порт. На шилде разъем помечен как LCD Serial.
3. Приемник соединяем с пятым пином МК при помощи обычного 3х-шнура (как у сервы). Я не долго думая воткнул в первый канал. Опять же удобство шилда - без каких бы то ни было проблем приемник получает питание с МК.
4. На восьмой пин вешаем серву.
Все провода стандартные и ничего паять и обжимать не надо - спасибо шилду.

Алгоритм:
1. Вешаем на входной пин прерывание на изменение уровня.
2. В прерывании проверяем пин. Если состояние 1, то запускаем таймер. Если состояние 0, то останавливаем таймер и вычисляем значение ширины импульса.
3. Вешаем на таймер прерывание на совпадение с некоторым калибровочным значением. Пробовал прерывание по переполнению, но не устроила точность. В перывании увеличиваем на 1 некоторую переменную.

Таймеры 0 и 1 мне нужны для других целей и я решил использовать таймер 2. Но если первые - 16 бит, то этот - 8 бит. Именно поэтому и нужен пункт 3.

Скачать можно тут
Код

#include <PinChangeInt.h>
#include "LCD12864RSPI.h"
#include <RCArduinoFastLib.h>
// Сюда подаем сигнал
#define PWM_PIN 5
// Сюда цепляем серву
#define SERVO_PIN 8
//калибровка таймера
#define TIMER_COMP_VAL 0xfa

volatile uint16_t ovf_cnt;
uint16_t val;
uint16_t val2;
volatile uint16_t temp;
volatile uint16_t temp_cnt;
String temp_str;
char cstr[16];

uint16_t val_prev;


void stop_timer()
{
  TCCR2B = 0;
}

void restart_timer()
{
  // обнуляем
  TCNT2 = 0;
  ovf_cnt = 0;
  // запускаем
  TCCR2B = 0<<CS22 | 1<<CS21 | 1<<CS20;
}

// Прерывание таймера как досчитает до 255
ISR(TIMER2_COMPA_vect) {
  // сброс счетчика
  TCNT2 = 0;
  // увеличение счетчика верхних разрядов
  ovf_cnt++;
}

void setup()
{
  // Подключаем экран
  LCDA.Initialise(); // INIT SCREEN
  delay(100);
  LCDA.CLEAR();//Clear screen
  // Инициализируем библиотеку управления сервой
  CRCArduinoFastServos::setFrameSpaceA(3,7*2000);
  CRCArduinoFastServos::begin();
  // Подключаем серву
  CRCArduinoFastServos::attach(0, SERVO_PIN);

  // Настройка таймера 2
  stop_timer();
  val = 0;
  val_prev = 0;
  TCCR2A = 0;
  OCR2A = TIMER_COMP_VAL;
  TIMSK2 = (1 << OCIE2A);

  //Подключение прерывания по изменению сигнала на входе
  PCintPort::attachInterrupt(PWM_PIN, calc, CHANGE);
  //Запуск таймера
  restart_timer();
}

void loop()
{
  val = (temp_cnt*TIMER_COMP_VAL + temp);
  val = map(val,0,TIMER_COMP_VAL*4,0,2000);
  if (val != val_prev) {
    val_prev = val;
    // вывод значения на экран
    temp_str = String(val_prev, DEC);
    temp_str.toCharArray(cstr, temp_str.length()+1);
    LCDA.DisplayString(0,0,(unsigned char*)cstr, temp_str.length());//
    // установка позиции сервы
    CRCArduinoFastServos::writeMicroseconds(0,val_prev);
  } 
}
void calc()
{
  stop_timer();
  if(PCintPort::pinState)
  {
    // начало импульса - стартуем таймер
    restart_timer();
  }
  else
  {
    // Конец импульса - считаем что получилось
    temp = TCNT2;
    temp_cnt = ovf_cnt;
  }
}

 

Считаем ширину импульса ШИМ (вариант 2)


Оборудование:
Пульт р/у - Turnigy 9XR
ВЧ-модуль - FrSky DJT
Приемник - FrSky V8FR-II
Микроконтроллер - Arduino Nano v3 втыкаем в шилд для удобства подключения. Скетч подойдет для любого AVR микроконтроллера от 168 до 2560. В принципе влезет и на 48-ю мегу. Но места там больше почти ни на что не останется.

Задача:
Хотим получить значения ширины канальных импульсов для первых шести каналов.

Решение:
AVR микроконтроллеры умеют генерить программные прерывания по изменению состояния входа. Именно этим и стоит воспользоваться. Казалось бы все достаточно просто:
- Вешаем обработчик прерывания на каждый интересующий нас вход.
- Запоминаем время перехода из 0 в 1.
- При переходе из 1 в 0 из текущего времени вычитаем запомненное и так получаем результат.
Вот только есть один подвох - микроконтроллер генерит прерывания не на каждый пин, а в целом на порт.

Для теста была взята 328-я мега. Все ее входы объединены в 3 порта: PORTB - пины 8-13 (+2 используются для подключения внешнего кварцевого резонатора), PORTC пины 14-19 (два последних пина на ардуине не распаяны для совместимости мк в разных корпусах), PORTD - пины 0-7. Мне как-то больше приглянулся PORTD(хотя по сути это не имеет значения).Для каждого порта есть соответствующий бит в регистре PCICR, разрешающий прерывания по изменению уровня на входе. Для порта D это PCIE2. Регистр DDRD задает конфигурацию пинов: 1 в соответствующем разряде задает пин как выход, а 0 - как вход. Так же есть три регистра PCMSK0:2(по одному для каждого порта), которые определяют маску - по изменению состояния каких пинов генерить прерывание.

С учетом всего выше изложенного наш алгоритм придется видоизменить следующим образом:
- Устанавливаем нужные нам пины как входы
- Вешаем обработчик прерывания на интересующий нас порт
- Определяем маску пинов для прерывания
в прерывании:
- Получаем текущее время по таймеру
- Получаем состояние интересующих нас пинов
- Получаем пины, изменившие состояние
- В цикле для каждого пина
  - если он изменил состояние
    - если переход с 0 на 1, то запоминаем текущее время для канала
    - иначе получаем ширину импульса как разность текущего и ранее запомненного времени для канала
Так же стоит учесть, что при переполнении таймера вы получите ошибку. Поэтому стоит добавить контроль на то, что ширина импульса укладывается в разумные пределы. А ошибочные значения таким образом будут игнорироваться.

Собственно ниже код тестового скетча:

void setup()

{

  // Все пины порта D кроме нулевого(TX) устанавливаем как входы

  DDRD = 1;

  // Настраиваем таймер 1 на отсчет 0.5 мкс

  TCCR1B   =   0;  

  TCCR1A   =   0;

  TCNT1   =    0;  

  TCCR1A   =   0;  

  TCCR1B   =   0<<CS12 | 1<<CS11 | 0<<CS10;//0x1A; //start timer with 1/8

  // Разрешаем прерывания порта D по изменению уровня

  PCICR |= (1 << PCIE2);

  // настраиваем маску прерывания - пины 2-7

  PCMSK2 = 0xFC;

  // открываем UART для вывода результата

  Serial.begin(9600);

}

 

typedef struct {

  unsigned long riseTime; // время перехода 0->1

  unsigned int  lastWidth; // ширина импульса

} tPinTimingData;

volatile static tPinTimingData pinData[6]; //массив каналов

volatile static uint8_t PCintLast;

 

uint8_t bit;

uint8_t curr;

uint8_t mask;

uint16_t currentTime;

uint16_t time;

 

// обработчик прерывания порта D

ISR(PCINT2_vect)

{

  // текущее время по таймеру 1 в мкс

  currentTime = TCNT1>>1;

  // Получаем состояние интересующих нас пинов

  curr = PIND & 0xFC;

  // пины, изменившие состояние

  mask = curr ^ PCintLast;

  PCintLast = curr;

  // в цикле просматриваем каждый пин

  for (uint8_t i=0; i < 6; i++) {

    // маска текущего пина цыкла

    bit = 0x04 << i;

    if (bit & mask) {

      // если 0->1

      if (bit & PCintLast)

        // сохраняем текущее время для канала

        pinData[i].riseTime = currentTime;

      else {

        // рассчитываем ширину импульса

        time = currentTime - pinData[i].riseTime;

        // и сохраняем ее, если таймер не наврал

        if ((time >= 800) && (time <= 2200)) {

          pinData[i].lastWidth = time;

        }

      }

    }

  }

}

 

void loop()

{

  // Вывод результата в UART каждые 0.5 сек.

  Serial.println();

  for (byte i = 0; i < 6; i++) {

    Serial.print("Ch");

    Serial.print((int)i + 1);

    Serial.print(": ");

    Serial.print(pinData[i].lastWidth);

    Serial.print(", ");

  }

  delay(500);

}

 

По материалам rc-master,  автор Зайчиков Александр

 

В начало обзора



Купить радиодетали для ремонта




Необходимо добавить материалы...
Результат опроса Результаты Все опросы нашего сайта Архив опросов
Всего голосовало: 371



          

Радио для всех© 2024