Очень быстрый умножитель импульсов (умножитель частоты)

tmr
Offline
Зарегистрирован: 19.05.2014

Коллеги - требуется очень быстро (вход макс частота ~10кГц, ширина имульса ~5мкс) увеличить частоту импульсов на выходе, и посколько это импульсы шагов для ШД, то требуется произвести умножение очень пропорционально, при этом частота входящих импульсов категорически нелинейна.

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

Прошу помощи зала в ускорении кода и поиске ошибок, я как мог подробно прокомментировал код, но если что непонятно - постараюсь объяснить в комментариях.

#define LED 17
#define DIV 4 // Кол-во микроимпульсов
#define INP PINC // Порт входящих импульсов
#define OUT PORTF // Порт исходящих импульсов
#define MAX 12000 // Максимальное время ожидания импульса
  
void setup(){
  /* Инициализация портов */
  DDRF = B11111111;
  PORTF = B11111111;
  DDRC = B10111111;

  /* Таймер1 для подсчета временных интервалов между входящими импульсами */
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TCCR1B |= (1 << CS10); 
  TCCR1B |= (1 << CS11);
  TCCR1B |= (0 << CS12);
  
  /* Таймер3 для исходящих импульсов */
  TCCR3A = 0;
  TCCR3B = 0;
  TCNT3 = 0;
  TCCR3B |= (1 << CS10);
  TCCR3B |= (1 << CS11);
  TCCR3B |= (0 << CS12);
  
  Serial.begin(9600);
}

void loop(){
  uint8_t inpValPre = 0b11111111; // Для хранения текущего состояние порта
  uint8_t inpValCur = 0b11111111; // Для хранения предыдущего состояние порта

  uint8_t inpRun = false; // Индикатор состояния серии импульсов 
  uint8_t inpBufCnt = 0; // Входящий индекс буфера
  uint16_t inpBuf[256] = {0}; // Буфер для хранения временни импульсов
  
  uint8_t outRun = 0; // Индикатор состояния выхода
  uint8_t outDiv = 0; // Счетчик выполненных дроблений
  uint8_t outCnt = 0; // Исходящий индекс буфера
  
  uint8_t ovrCnt = 0; // Счетчик переполнения буфера

  /* 
  Для ровного хода импульсов прерывания лучше выключить,
  однако теперь плата будет доступна только в момент загрузчика,
  сразу после загрузчика контроллер уйдетт в автономку и в этом 
  состояниии его по юсб не прошить, т.е. придется ловить загрузчик
  */
  //noInterrupts();
/* Лучше из loop не выходить, большие накладные расходы */ 
run:
  //PORTD = 0b00000000; // Для оценки времени исполнения по осцилу

  /* 
  Считываем состояние входа, если пришел первый импульс - обнуляем таймер1 и продолжаем,
  если пришел следующий импульс - делим полученное время таймера1 и сохраняем значение в буфер
  Я пробовал реализовать этот кусок на прерываниях но это ничего по времени не дает
  */
  inpValCur = INP;
  if(inpValCur == 0b00000000 && inpValPre == 0b01000000){
    if(inpRun){
      inpBuf[inpBufCnt++] = TCNT1 / DIV; // Используем переполнения для организации циклического буфера
      TCNT1 = 0;
      if(ovrCnt++ == 255) goto dbg; // Увеличим счетчик переполнения и если перепонение достигнуто - выпадем в дебаг
      // if(ovrCnt++ == 255) goto rst; // или сбросим счетчики и продолжим 
    }else{
      TCNT1 = 0;
      inpRun = true;
    }
  }
  inpValPre = inpValCur;
  
  /*
  Проверяем закончилась ли серия импульсов, если время импульса превышено - записываем
  значение в буфер, если нет - продолжаем
  */
  if(inpRun){
    if(TCNT1 >= MAX){
      inpBuf[inpBufCnt++] = MAX / DIV;
      if(ovrCnt++ == 255) goto dbg; // goto rst; 
      inpBuf[inpBufCnt] = 0; // Обнуляем значение следующего элемента 
      inpRun = false;
    }
  }

  /* 
  Формируем начало импульса, если в буфере не нулевое значение - устанаваливаем
  уровень на выходе, обнуляем таймер и устанаваливаем признак импульса 
  */
  if(inpBuf[outCnt]){
    /*
    Если имеется признак импульса - устанавливаем уровень на выходе, и если время импульса 
    истекло - формируем окончание импульса
    */
    if(outRun){
      //if(TCNT3 >= 2) OUT = 0b11111111; // Pulse width ~10us
      OUT = 0b11111111;
      if(TCNT3 >= inpBuf[outCnt]){
        if(outDiv == DIV){
          outDiv = 0;
          outCnt++;
          ovrCnt--; // Уменьшаем счетчик переполнения
        }else{
          outDiv++;
        }
        outRun = false;
      }
    }else{
      OUT = 0b00000000;
      TCNT3 = 0;
      outRun = true;
    }
  }

  //PORTD = 0b11111111;
  goto run;

rst:
  outCnt = inpBufCnt;
  outDiv = 0;
  goto run;
  
dbg:
  Serial.println("DBG");
  Serial.print("Inp count: "); Serial.println(inpBufCnt, DEC);
  Serial.print("Out count: "); Serial.println(outCnt, DEC);
  Serial.print("Ovr count: "); Serial.println(ovrCnt, DEC);
  for(byte b = 0; b < 255; b++){
    Serial.print(b, DEC); Serial.print("\t"); Serial.println(inpBuf[b], DEC);
  }
  while(1){
    digitalWrite(LED, HIGH);
    delay(250);
    digitalWrite(LED, LOW);
    delay(250);
  };
} 

 

art100
Offline
Зарегистрирован: 09.03.2014

по моемому соосно с шаговиком енкодер от мышки одевали где-то в принтерах - это подешевле

то есть ты хочешь знать точное положение руки робота?

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

может не стоит бороться с ней

черт с ней

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

в мышках шарик проскальзывал и его заменили на енкодеры и мы все успокоились 

tmr
Offline
Зарегистрирован: 19.05.2014

art100, импульсы идут с компа на драйвер ШД, соответственно это управляющие импульсы step (не импульсы с энкодера).

Поскольку обычный комп не может нормально генерировать импульсы быстрее 65кГц (да что там, он и >25кГц генерирует кое-как), а я бы хотел 5000мм\мин при 5000шаг\об, получилась такая идея - увеличить частоту ардуинкой. Но что-то с алгоритмом не выходит никак.

art100
Offline
Зарегистрирован: 09.03.2014

интересно интересно 5000 шагов на оборот

а если по тупому отладить вылизать код на родных 16мгц а потом по тупому выдирать кварци допустим ровно в два раза 32 втыкать

может атмел теперь процы подразогнал по чуток

я так на мегах 8 делал только они больше 20 уже не хотели трястись

кнопку турбо никто не отменял

но на шаговиках нужно четко ровно кратно это делать

этож сколько механики не надо делать будет

вствки в кажды мотор по 20$ и станок в два раза шустрей детали выдает - мечта

Lisec это делает механикой за ооочень большие деньги

tmr
Offline
Зарегистрирован: 19.05.2014

Ну вот если на 16МГц ничего не придумается, буду на дуе на 84 пробовать.

У Геко есть такие умножители по 35$, только они на плиске или на фпга, возможно там какой-то вариант фапч реализован.

art100
Offline
Зарегистрирован: 09.03.2014

tmr пишет:

Ну вот если на 16МГц ничего не придумается, буду на дуе на 84 пробовать.

У Геко есть такие умножители по 35$, только они на плиске или на фпга, возможно там какой-то вариант фапч реализован.

а сылочку можно

я совсем не в теме

tmr
Offline
Зарегистрирован: 19.05.2014

Gecko G901X

Вот еще отечественный вариант на 4 оси на двух кортексах

И мне не столько скорость важна, хотя ее хотелось бы не потерять, а именно большой микрошаг, т.е. громкость и плавность перемещения.

art100
Offline
Зарегистрирован: 09.03.2014

tmr пишет:
..хотелось бы не потерять... плавность перемещения.

а у тебя C:\Mach3\ ?

tmr
Offline
Зарегистрирован: 19.05.2014

Mach3 да и linuxcnc тоже

art100
Offline
Зарегистрирован: 09.03.2014

tmr пишет:

Mach3 да и linuxcnc тоже

я его так и не попробовал

дальше светодиодов за 2 месяца не прошел так и светят под столом

 

меня что-то на g-code торкнуло

а mach3 какой-нибудь g-code поинмает или у него свое что-то

о открыл понимает

tmr
Offline
Зарегистрирован: 19.05.2014

Мач3 - да, ж-код понимает. Может даже на 100кГц импульсы выдавать, если железо позволит (у меня на >65 рандомно вешается)

art100
Offline
Зарегистрирован: 09.03.2014

тебе заказали или для себя умножители

а за 24$ подходят?

Она работает путем синтеза полным шагом, половиной шага, 5-microstep или 10-microstep пульс на основе перемычки; это означает, что диск подключен еще microstep плавность, с возможностью выбора разрешения.

переводы веселые

Gecko G213V вампир Шаговым приводом

а вот и енкоды 

 

а вот и то что искал

Mach3 Licence File

Retail Price: US$149.90

 

tmr
Offline
Зарегистрирован: 19.05.2014

Для себя. Геко скорее всего подойдет, тут я только по их описанию предположить могу. Но это только на одну ось, а нужно 4, ну и готовый моушн-контроллер у меня в пути уже, а интереснее свое написать.

art100
Offline
Зарегистрирован: 09.03.2014

интересно

у меня два станка

деталь обьемом со спичечный коробок это 2 часа времени

контроллеры на них g-code разжовывают эти

софта в принципе гиги

но ближе всего мне этот оказался D:\Printrun\

понимаю это совсем другая религия

но твои умножители думаю тоже вполне могут разогнать так сказать аппаратным решением но без механики

станки по 1500$ и 1300$

вкинуть 80$ чудо-коробченок чтобы чтобы чуток пошустрей были вполне реально

собственно только по этому здесь тусуюсь

щас глянул ужастнулся под теметику станка ЧПУ за 100 гигабайт информации

жуть

 

нашел чудо которое реально g-code понимает

смотри кино у меня только два дрйвера свободных оказалось

щас кино со стола сделаю и ссылку слеплю на кино

 

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

 

StrangerM
Offline
Зарегистрирован: 02.11.2013

В железе (помню без подробностей) работало ГУН, счетчик-делитель и ФД.

tmr
Offline
Зарегистрирован: 19.05.2014

Первоначально я этот вариант отбросил как слишком накладный, но видимо ошибался. Вот такой вариант работает гораздо стабильнее - расчитать и записать умноженные шаги сразу в буфер, это значительно увеличивает размер буфера, однако на скорости алгоритма практически не отражается - 555кГц на холостом ходу и 333кГц при входящем сигнале, что впринципе укладывается в ширину импульса 5мкс и пропусков быть не должно.

Тестовый прогон на 10 метров в дебаг не выпал и генерировал шаги довольно ровно. Блин, не может быть что все так просто, может я где ошибку не вижу?

#define SUB(i) {i; i; i; i; i; i; i; i;}

#define LED 17
#define DIV 8
#define BUF 1000
#define INP PINC
#define OUT PORTF
#define MAX 12000

void setup(){
  DDRF = B11111111;
  PORTF = B11111111;
  DDRC = B10111111;
  DDRE = B11111111;

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS11);
  TCCR1B |= (0 << CS12);
  
  TCCR3A = 0;
  TCCR3B = 0;
  TCNT3 = 0;

  TCCR3B |= (1 << CS10);
  TCCR3B |= (1 << CS11);
  TCCR3B |= (0 << CS12);
  
  Serial.begin(115200);
}

void loop(){  
uint8_t inpRun = false;
uint16_t inpCnt = 0;
uint16_t inpBuf[BUF] = {0};

uint8_t outRun = false;
uint8_t ovrCnt = 0;
uint16_t outCnt = 0;

uint16_t inpDiv = 0;

uint8_t inpValPre = 0b11111111;
uint8_t inpValCur = 0b11111111;

run:
  PORTE = 0b00000000;
  
  inpValCur = INP;
  if(inpValCur == 0b00000000 && inpValPre == 0b01000000){
    if(inpRun){
      inpDiv = TCNT1 / DIV;
      SUB(
        inpBuf[inpCnt] = inpDiv;
        if(++inpCnt == BUF) inpCnt = 0;
        if(++ovrCnt == BUF) goto dbg;
      );
    }else{
      inpRun = true;
    }
    TCNT1 = 0;
  }
  inpValPre = inpValCur;

  // Set timer value
  if(inpRun){
    if(TCNT1 >= MAX){
      inpBuf[inpCnt] = MAX / DIV;
      if(++inpCnt == BUF) inpCnt = 0;
      if(++ovrCnt == BUF) goto dbg;
      inpBuf[inpCnt] = 0;
      inpRun = false;
    }
  }

  // Send pulse
  if(inpBuf[outCnt]){
    if(outRun){
      OUT = 0b11111111;
      if(TCNT3 >= inpBuf[outCnt]){
        if(++outCnt == BUF) outCnt = 0;
        ovrCnt--;
        outRun = false;
      }
    }else{
      OUT = 0b00000000;
      TCNT3 = 0;
      outRun = true;
    }
  }

  PORTE = 0b11111111;
  goto run;

rst: 
  outCnt = inpCnt;
  goto run;

dbg:
  Serial.println("DBG");
  Serial.print("Inp count: "); Serial.println(inpCnt, DEC);
  Serial.print("Out count: "); Serial.println(outCnt, DEC);
  Serial.print("Ovr count: "); Serial.println(ovrCnt, DEC);
  for(uint16_t b = 0; b < BUF; b++){
    Serial.print(b, DEC); Serial.print("\t"); Serial.println(inpBuf[b], DEC);
  }
  goto die;
  
die:
  digitalWrite(LED, HIGH);
  delay(250);
  digitalWrite(LED, LOW);
  delay(250);
  goto die;
}