Нужна помощь по генератору сдвоенных импульсов

Vlad1976
Offline
Зарегистрирован: 17.05.2016

Всем Доброго Времени Суток!

Стоит задача, сделать генератор сдвоенных и гальванически развязанных, прямоугольных импульсов и регулировкой частоты следования, длительностей и задержки между ними. Благодаря чтению форума и особенно авторам нужных тем ЕвгенияП и dimax почти готов нужный скетч. 

Программа работает только на  Arduino Mega2560  v.R3, т.к. только там нашлась пара свободных 16-ти битных таймеров (можно взять Mega2560 и другой ревизии, но тогда придется переопределить в программе выходные пины таймеров). Импульсы задаются таймерами 3 и 5, (пины 2 и 45 соответственно, можно переопределить в небольших пределах). Повторю программа работает, импульсы наблюдаю на осциллографе. Частоту можно переопределять через Serial. С длительностью импульсов тоже всё в порядке. Наблюдается что-то непонятное с задержкой между импульсами.
Проявляется это следующим образом: при переходе с частоты нескольких килогерц, на новое значение, например в 15 кГц, происходит скачок задержки с 70 мкс до 35 мкс, и при возврате частоты на исходную задержка остается такой же (35 мкс).  При изменении частоты в небольших пределах (например с 2 кГц на 3, 4 или 5 кГц) этого не происходит, всё работает правильно.  За эту задержку в программе отвечают строки 51 (в разделе setup) и 130 (в разделе loop ). Не могу понять почему так происходит и поэтому прошу помощи.
Заранее всем спасибо за участие.
 
/*
Генератор гальванически развязанных сдвоенных импульсов ( импульсы на разных пинах ).

Программа только для  Arduino Mega2560  v.R3, т.к. только там нашлась
пара свободных 16-ти битных таймеров (можно взять Mega2560 и другой ревизии, 
но тогда придется переопределить в программе выходные пины таймеров).
Импульсы задаются таймерами 3 и 5, 
(пины 2 и 45 соответственно, можно переопределить в небольших пределах).
При необходимости, можно использовать еще один таймер (4).

При необходимости, изменить в программе значения длительность импульсов и задержку между ними.
Изменение частоты реализовано через монитор монитор порта (serial)

При написании программы использовались идеи представленные в:
1.http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-blink-i-bez-delay-i-bez-millis
2.http://arduino.ru/forum/proekty/generator-s-reguliruemoei-chastotoi-na-arduino
3.библиотека Timer.h

Загружаете скетч в Мегу 2560 (R3) и на пинах 2 и 45 появляются импульсы. 

*/

#include <limits.h>

#define  setDuty() {OCR3B=dutyOnePulse;OCR5B=dutyTwoPulse;}

unsigned char clockSelectBits;   // переменная для записи в управляющий регистр таймера
unsigned long dutyOnePulse = 56; // 56 = 7 microSeconds, это длительность первого импульса
unsigned long dutyTwoPulse = 8;  // 8  = 1 microSeconds, это длительность второго импульса
int16_t       PulseDelay = 210;  // 210 это примерно 70 мкс задержки между импульсами

int16_t  prescaler[6] = {1,1,8,64,256,1024}; // 5 значений прескалера + оптимальное(нулевое для записи)
uint16_t counter[6]; // переменная для счетчика, можно обойтись без массива
double   frequency[6]; // переменная для частоты, можно обойтись без массива
double   difference[6]; // переменная для ошибки, можно обойтись без массива
bool     valid; // двоичная переменная..

double freq = 2000;  // начальное значение частоты следования импульсов в Герцах

void setup(){
 
    TCCR3A = 0;  // обнуление управляющего регистра А Таймера3
    TCCR5A = 0;  // обнуление управляющего регистра А Таймера5
      
    setPreScaler(freq);  // расчет и установка делителей Таймеров 3 и 5
    setDuty();           // установка длительности импульсов
  
    DDRE |= _BV(PORTE4); TCCR3A |= _BV(COM3B1); // Устанавливаем пин 2 как выход Таймера3
    TCCR3B |= clockSelectBits;                  // запуск режима PWM на Таймере3

    for ( int16_t i=0; i <= PulseDelay; i++){ asm("nop"); }  // задержка между импульсами

    DDRL |= _BV(PORTL4); TCCR5A |= _BV(COM3B1);  // Устанавливаем пин 45 как выход Таймера5
    TCCR5B |= clockSelectBits;                   // запуск режима PWM на Таймере5
 
    Serial.begin(115200);
    Serial.setTimeout(LONG_MAX);
}

void setPreScaler(double freq)
{ 
  difference[0] = 200;   // это значение "начальной" ошибки между заданной и генерируемой частотой
  
  for ( int8_t i=1; i<6; i++ ){  // в этом цикле производится перебор делителей и выбирается вариант с наименьшей ошибкой
    
    double f0 = floor( F_CPU / ( 2.0 * prescaler[i] * freq ) - 0.5 );
    
    if ( f0 < 0.0 || f0 > UINT_MAX ) { valid = false; }
    else { valid = true; }
    
    counter[i] = (uint16_t) f0;
    frequency[i] = F_CPU / ( 2.0 * prescaler[i] * ( 1.0 + counter[i] ) );
    difference[i] = fabs( frequency[i] - freq );
    
    if ( (difference[i] < difference[0] ) && ( valid == true ) ) { // вариант с наименьшей ошибкой запоминается в нулевом номере массива
      difference[0] = difference[i];
      prescaler[0]  = prescaler[i];
      counter[0]    = counter[i];
      frequency[0]  = frequency[i];         
      }    
  }
  
  TCCR3B = _BV(WGM13); // остановка Таймеров
  TCCR5B = _BV(WGM13);
  
  switch (prescaler[0]) // запись выбранного значения делителя в clockSelectBits
      {   
            case 1:  //выполняется когда  prescaler[0] равно 1
                     clockSelectBits = _BV(CS10);
            break;      
            case 8:  //выполняется когда  prescaler[0] равно 8
                     clockSelectBits = _BV(CS11);
            break;
            case 64: //выполняется когда  prescaler[0] равно 64
                     clockSelectBits = _BV(CS11) | _BV(CS10);
            break;      
            case 256: //выполняется когда  prescaler[0] равно 256                                                     
                     clockSelectBits = _BV(CS12);                                                
            break;
            case 1024: //выполняется когда  prescaler[0] равно 1024                                                    
                     clockSelectBits = _BV(CS12) | _BV(CS10);                                                
            break;    
       }
   ICR3 = counter[0]; //  запись выбранного значения счетчика в ICR3
   ICR5 = counter[0]; //  у нас импульсы с одинаковой частотой..

   TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
   TCCR5B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // обнуление в Таймере5 управляющего регистра TCCR5B  
   
}


void loop() {  
  
  Serial.println("\nЧастота следования импульсов " + String(frequency[0]) + "Гц");
  Serial.println("\nВведите новое значение частоты");
  
  freq = Serial.parseFloat();
  
  if (freq < 0) 
  {
    Serial.println("\nЧастота не может быть отрицательной\n");
  } else if (freq > 20000) 
    {
      Serial.println("\nЧастота не может быть слишком большой\n");
    } else {setPreScaler(freq);    // расчет и уставка новых значений делителей
            TCCR3B = _BV(WGM13);   // остановка Таймеров
            TCCR5B = _BV(WGM13); 
            TCCR3B |= clockSelectBits;
            for ( int16_t i=0; i <= PulseDelay; i++){ asm("nop"); }  // задержка между импульсами
            TCCR5B |= clockSelectBits;} 
  
  delay(1000);
  
}

 

 
asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

Так когда частоту перенастраиваишь надо же таймерам счетчики сбросить.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Vlad1976, а вы можете описать точно что вам нужно? Из того, что видно по программе -дельта между таймерами не делается ноп-ами, а делается загрузкой в счётный регистр CNT одного из таймеров некого смещения, от которого начнётся счёт. Потом таймеры синхронно запускаются регистром GTCCR

А на stm32 всё делается гораздо проще и удобнее :)

Vlad1976
Offline
Зарегистрирован: 17.05.2016

Отвечаю asam.

Так я считаю, что сбрасываю счетчик, это строки в программе № 107 и 108.

Для Dimax.

1. stm32 - еще едут ко мне из Китая.

2. Из того что от этой программы мне осталось добиться, это чтобы при перестройки частоты, задержка между импульсами не изменялась.

3. не понял из того, что вы написали, что задержка задается у меня не нопами!? в управляющие регистры счетчиков (TCCR3B, ICR3  TCCR5B, ICR5), я записываю одни и те же значения, для того чтобы импульсы шли синхронно, записываю разные значения в регистры (OCR3B и OCR5B), так как нужна разная длительность у этих импульсов, и считал, что задержку формирую нопами.. А почему тогда она меняется --- если менять значение PulseDelay? Не могли бы вы разъяснить мне подробнее, пожалуйста.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

два 16 битных таймера есть в 328PB

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Vlad1976,

void setup() {
pinMode(2,OUTPUT);
pinMode(45,OUTPUT);
GTCCR=(1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC);
TCCR3B=0;TCCR5B=0;
TCCR3A=1<<COM3B1; TCCR5A=1<<COM5B1;
TCCR3B=(1<<WGM33)|(1<<CS30); TCCR5B=(1<<WGM53)|(1<<CS50);
OCR3B=56; OCR5B=8;
ICR3=3999; ICR5=3999;
TCNT3=1120;  TCNT5=0;
GTCCR=0;
}

void loop() {}

Вот так надо задержки делать. Думаю разберётесь как это работает.

Vlad1976
Offline
Зарегистрирован: 17.05.2016

dimax - большое спасибо.

Доделал программу. Всё работает как надо. Выкладываю, вдруг кому-нибудь пригодиться. Коротко о схеме. Пины 2 и 45 - выходы генераторов. Дисплей двухстрочных (16х2) подключен через i2c, конкретно у моего адрес 0х3f, у вас может быть другой. Кнопка энкодера - земля и пин 3, меняет режим энкодера (М1 - изменение частоты по 100Гц, М2 - изменение частоты по 1 Гц, М3 - изменение длительности первого импульса, М4 - изменение задержки между импульсами.).  Средний контакт энкодера - на земле, два других подключены через триггер Шмитта 74HC14p (антидребезг контактов) к пинам 18,19 

/* 
Генератор гальванически развязанных сдвоенных импульсов ( импульсы на разных пинах ).

Программа только для  Arduino Mega2560  v.R3, т.к. только там нашлась
пара свободных 16-ти битных таймеров (можно взять Mega2560 и другой ревизии, 
но тогда придется переопределить в программе выходные пины таймеров).
Импульсы задаются таймерами 3 и 5, 
(пины 2 и 45 соответственно, можно переопределить в небольших пределах).
При необходимости, можно использовать еще один таймер (4).

При необходимости, изменить в программе значения длительность импульсов и задержку между ними.
Изменение частоты реализовано через монитор монитор порта (serial)

При написании программы использовались идеи представленные в:
1.<a href="http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-blink-i-bez-delay-i-bez-millis" rel="nofollow">http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-blink-i-bez-delay-i-bez-millis</a>
2.<a href="http://arduino.ru/forum/proekty/generator-s-reguliruemoei-chastotoi-na-arduino" rel="nofollow">http://arduino.ru/forum/proekty/generator-s-reguliruemoei-chastotoi-na-arduino</a>
3.библиотека Timer.h

отдельное спасибо Dimax.

Загружаете скетч в Мегу 2560 (R3) и на пинах 2 и 45 появляются импульсы. 
*/


#include <limits.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

#define  setDuty() {OCR3B=dutyOnePulse;OCR5B=dutyTwoPulse;}

LiquidCrystal_I2C lcd(0x3f,16,2);  // set the LCD address to 0x3f for a 16 chars and 2 line display

unsigned char clockSelectBits;    // переменная для записи в управляющий регистр таймера
unsigned long dutyOnePulse = 56;  // 56 = 7 microSeconds, это длительность первого импульса
unsigned long dutyTwoPulse = 8;   // 8  = 1 microSeconds, это длительность второго импульса
int16_t       PulseDelay = 64465; // -1070 это примерно 70 мкс задержки между импульсами
int8_t        mode = 1;           // режим работы энкодера

int16_t    prescaler[6] = {1,1,8,64,256,1024}; // 5 значений прескалера + оптимальное(нулевое для записи)
uint16_t   counter[6];    // переменная для счетчика, можно обойтись без массива
double     frequency[6];  // переменная для частоты, можно обойтись без массива
double     difference[6]; // переменная для ошибки, можно обойтись без массива
bool       valid;         // двоичная переменная..

double             freq = 2000;       // начальное значение частоты следования импульсов в Герцах
volatile int8_t    val = 0;           // переменная для энкодера
volatile boolean   flag = false;      // флаговый автомат

 
void setup() {
  
 pinMode(18,INPUT_PULLUP);      // другой пин энкодера на цифровом пине 18..
 pinMode(3,INPUT_PULLUP);           // кнопка на первом прерывании, пин 3
 attachInterrupt(1, set_mode, LOW); // привязываем 1-е прерывание к функции set_mode()
 pinMode(19,INPUT_PULLUP);          // Энкодер на 4-м прерывании, пин 19 
 attachInterrupt(4, set_val, FALLING);     // привязываем 4-е прерывание к функции set_val()
 
 pinMode(2,OUTPUT);  // выход генератора, импульс 1, таймер 3
 pinMode(45,OUTPUT); // выход генератора, импульс 2, таймер 5
  
 GTCCR=(1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC);
 TCCR3B=0;TCCR5B=0;
 TCCR3A=1<<COM3B1; TCCR5A=1<<COM5B1;
 TCCR3B=(1<<WGM33)|(1<<CS30); TCCR5B=(1<<WGM53)|(1<<CS50);
 setDuty();
 ICR3=3999; ICR5=3999;
 TCNT3=0;  TCNT5=PulseDelay;
 GTCCR=0;
  
 lcd.init();                      // инициализация экрана 
 lcd.backlight();
 set_display();
}

void setPreScaler(double freq)
{ // расчет прескалера, ошибки частоты и значения счетчика
  difference[0] = 200;   // это значение "начальной" ошибки между заданной и генерируемой частотой
  
  for ( int8_t i=1; i<6; i++ ){  // в этом цикле производится перебор делителей и выбирается вариант с наименьшей ошибкой
    
    double f0 = floor( F_CPU / ( 2.0 * prescaler[i] * freq ) - 0.5 );
    
    if ( f0 < 0.0 || f0 > UINT_MAX ) { valid = false; }
    else { valid = true; }
    
    counter[i] = (uint16_t) f0;
    frequency[i] = F_CPU / ( 2.0 * prescaler[i] * ( 1.0 + counter[i] ) );
    difference[i] = fabs( frequency[i] - freq );
    
    if ( (difference[i] < difference[0] ) && ( valid == true ) ) 
    { // вариант с наименьшей ошибкой запоминается в нулевом номере массива
      difference[0] = difference[i];
      prescaler[0]  = prescaler[i];
      counter[0]    = counter[i];
      frequency[0]  = frequency[i];         
     }    
  }
  
  TCCR3B = _BV(WGM13); // остановка Таймеров
  TCCR5B = _BV(WGM13);
  
  switch (prescaler[0]) // запись выбранного значения делителя в clockSelectBits
      {   
            case 1:  //выполняется когда  prescaler[0] равно 1
                     clockSelectBits = _BV(CS10);
            break;      
            case 8:  //выполняется когда  prescaler[0] равно 8
                     clockSelectBits = _BV(CS11);
            break;
            case 64: //выполняется когда  prescaler[0] равно 64
                     clockSelectBits = _BV(CS11) | _BV(CS10);
            break;      
            case 256: //выполняется когда  prescaler[0] равно 256                                                     
                     clockSelectBits = _BV(CS12);                                                
            break;
            case 1024: //выполняется когда  prescaler[0] равно 1024                                                    
                     clockSelectBits = _BV(CS12) | _BV(CS10);                                                
            break;    
       }
   ICR3 = counter[0]; //  запись выбранного значения счетчика в ICR3
   ICR5 = counter[0]; //  у нас импульсы с одинаковой частотой..

   TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
   TCCR5B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // обнуление в Таймере5 управляющего регистра TCCR5B  
   
}

void set_mode(void)
{ // по нажатию кнопки, изменяется режим работы энкодера
  detachInterrupt(1);
  flag = true;
}

void set_val(void)
{ // вращение энкодера с учетом направления
  if (digitalRead(18)) 
  { 
    val++; 
  }
  else 
  { 
    val--; 
  }
}

void set_display(void)
{ // вывод информации на дисплей
  lcd.setCursor(0,0);
  lcd.print("                ");
  lcd.setCursor(0,1);
  lcd.print("                ");
  
  lcd.setCursor(0,0);
  lcd.print("M");
  lcd.print(mode);
  lcd.print("  f = ");
  lcd.print(freq);
  
  lcd.setCursor(0,1);
  lcd.print("t=");
  lcd.print(dutyOnePulse);
  lcd.print(",");
  lcd.print(dutyTwoPulse);
  lcd.print(" dt=");
  lcd.print(PulseDelay);
}

void loop() {

if (flag==true) 
{ // обработка нажатия кнопки
  mode++;
  if (mode==5) {mode=1;}
  flag=false;
  attachInterrupt(1, set_mode, LOW);
  set_display();
}


if (val!=0) 
{
  switch (mode) {
    case 1:
      //выполняется, когда mode равно 1
      if (val>0)
      {
        if ((freq + val*100)<14000) {freq = freq + val*100;}        
      }
      else
      {
        if ((freq + val*100)>100) {freq = freq + val*100;}
      }      
      break;
      
    case 2:
      //выполняется когда  mode равно 2
      if (val>0)
      {
        if ((freq + val)<14000) {freq = freq + val;}         
      }
      else
      {
        if ((freq + val)>100) {freq = freq + val;} 
      }      
      break;

    case 3:
      //выполняется когда  mode равно 3
      if (val>0)
      {
        if (dutyOnePulse<70) {dutyOnePulse++;}               
      }
      else
      {
        if (dutyOnePulse>30) {dutyOnePulse--;}
      }      
      break; 

    case 4:
      //выполняется когда  var равно 3
      if (val>0)
      {
        if (PulseDelay<1350) {PulseDelay=PulseDelay+val;}               
      }
      else
      {
        if (PulseDelay>900) {PulseDelay=PulseDelay+val;}
      }      
      break;     
      
  }
  
val=0;

GTCCR=(1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC);
TCCR3B=0;TCCR5B=0;
setPreScaler(freq);
TCCR3B=(1<<WGM33)|(1<<CS30); TCCR5B=(1<<WGM53)|(1<<CS50);
setDuty();
TCNT3=0;  TCNT5=PulseDelay;
GTCCR=0; 

set_display();
  
}

delay(500);  

}