УЦИ на сельсине

Аватара пользователя
Автор темы
kasss
Реальное имя: Владимир
Откуда: Суздаль

УЦИ на сельсине

Сообщение kasss » 09 янв 2020, 15:40

Здравствуйте, долго думал о выдаче этой информации так как сельсин устарел и везде сейчас проще поставить энкодер, а также моя работа любительская и может вызвать много нареканий со стороны профи, есть опасение что при повторение устройства из за разброса показателей радиодеталей может получится погрешность в измерениях отличающаяся от моих значений, а кому то и вовсе точность мала.
В итоге решил что информация не должна пропадать, иногда встречаются вопросы как можно запустить сельсин.
Коротко об истории, есть станок (6720вф1) на котором возможно когда то стояли сельсины а возможно индуктосины (но это не столь важно), прикупил сельсины БС 155 А и задумался как его оцифровать, сразу скажу что УЦИ с оптическими линейками у меня стоят, но на станке нет регулируемых упоров авто подач и было желание использовать сельсин как электронные настраиваемые упоры конца подачи,
а самая главная причина, это практика в изучение программирования.
В итоге родилось самостоятельное УЦИ на Ардуино с дискретностью 0.01 мм для винтов с шагом до 5 мм, шаг можно и больше применять но тогда дискретность на мой взгляд надо делать 0.1 мм., все это связано с искажением генерируемой синусоиды и снимаемого сигнала.
Шаг винта можно свободно менять в программе платы обработки сигнала в виде выбора количества соток на оборот (100,150,200-500)

IMG_0601.JPG
IMG_0607.JPG
IMG_0615.JPG


Принцип работы такой, с 3х фазного генератора сделанного на одной плате ардуино и усилителя сигнал
подается на сельсин , с сельсина снимаются показатели смещения фазы, полученный сигнал подаётся
на вторую плату ардуино где по сигналу синхронизации с генератора вычисляется сдвиг и переводится в линейный размер.
Генератор выдает синусоидальный сигнал частотой 110 гЦ. Так как ардуина не может вычислять быстрее чем 4 миллисекунды
( это всего 625 делений на оборот при 400 гц для БС 155 А) и стабильность генерации немного плавает то временные рамки частоты пришлось
сдвинут для увеличения точности и снижения девиации показаний, из за этого не 400 гц а 110. Для примера время периуда 400 гц 0.0025 сек.
на шаг винта 5 мм надо 5 мс на сотку, а при 110 гц 18 мс на сотку. Это не значит что я могу получить 2500 тиков на оборот, остаются все теже 600
но показания выводятся стабильней и при переходе через .0. снижается пропуск.
Схема

Схема УЦИ.JPG


Разработанную плату заказал в Китае, плата имеет не указанные на схеме элементы, так как я закладывал возможности под свои нужды.

плата.JPG


Устройство при проверке плитками и по показаниям лимба станка показало точность не хуже 0.03 мм на вал с шагом 5 мм. и при
переходе сельсина через .0. ардуина теряет показания 3 сотки так как не может их вычислить, по этому общая точность
составляет 0.03 мм.

Код генератора найден на просторах интернета и был допилен под свои нужды.
генератор выдает шим полуволн, после фильтрации и сложения полуволн на выходе усилителя получаем синусоидальный сигнал.
Наверно не лучшее решение, есть модули которые дают синус без искажений и управляются по I2C шине но не решился менять схему когда пол дела было сделано, нет уверенности что смог бы объединить 3 генератора по I2C, хотя в этом случае устройству хватило бы одной ардуино.

Код: Выделить всё


   //ШИМ 0, pins 6, 5
   
   //ШИМ 1, pins 9, 10
   
   //ШИМ 2, pins 11, 3

   //синхронизация pins 4 pins 7 pins 8
const byte PROGMEM sineP4[]  = { //Константа для первой четверти синуса. 256 значений.
   
0,1,3,4,6,7,9,10,12,14,15,17,18,20,21,23,25,26,28,29,31,32,34,36,37,39,40,42,43,
45,46,48,49,51,53,54,56,57,59,60,62,63,65,66,68,69,71,72,74,75,77,78,80,81,83,84,
86,87,89,90,92,93,95,96,97,99,100,102,103,105,106,108,109,110,112,113,115,116,117,
119,120,122,123,124,126,127,128,130,131,132,134,135,136,138,139,140,142,143,144,146,
147,148,149,151,152,153,154,156,157,158,159,161,162,163,164,165,167,168,169,170,171,
173,174,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,194,
195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,211,212,213,214,
215,216,217,217,218,219,220,221,221,222,223,224,225,225,226,227,227,228,229,230,230,
231,232,232,233,234,234,235,235,236,237,237,238,238,239,239,240,241,241,242,242,243,
243,244,244,244,245,245,246,246,247,247,247,248,248,249,249,249,250,250,250,251,251,
251,251,252,252,252,252,253,253,253,253,254,254,254,254,254,254,255,255,255,255,255,
255,255,255,255,255,255,255,255,255};


 


const unsigned int refclk = 21630;      /* Реальная внутренняя частота находится по замеру осцилографом 
 частоты на  пине 2 в моем случае это 10869000 г /510 = 21311764 в таймере для замера, 21420 скоректировал в ручную
чтобы заданная частота совпадала с реальной
PIND |= (1 << 2); строку раскоментировать*/

volatile unsigned long phaccu;   // аккумулирующая переменная, 32 бит
volatile float delta;     // переменная, расчитывающаяся на основе refclk и желаемой частоты работы
volatile unsigned int incr;       // переменная для вычисления итерации(10 бит = 2 бит + 8 бит)
volatile int dfreq=110;              //Частота 110 Гц



void setup()
{
   pinMode(12, INPUT);
   digitalWrite(12,LOW);
   pinMode(13, INPUT);
   digitalWrite(13,LOW);
   pinMode(14, INPUT);
   digitalWrite(14,LOW);
   pinMode(15, INPUT);
   digitalWrite(15,LOW);
   pinMode(16, INPUT);
   digitalWrite(16,LOW);
   pinMode(17, INPUT);
   digitalWrite(17,LOW);
   pinMode(18, INPUT);
   digitalWrite(18,LOW);
   pinMode(19, INPUT);
   digitalWrite(19,LOW);
   
   DDRD = 0xFC;  // весь порт D - выход, кроме RX TX
   DDRB = 0xFF;  // весь порт B - выход
   delta = 0x100000000 * dfreq / refclk;
   // TIMER0
   TCCR0A |=  (1<<COM0A1); // COM0A1, COM0A0: 1 0 - None-inverted mode для OC0A, для инверсного режима ШИМ нужно 1 1
   TCCR0A &= ~(1<<COM0A0);
   TCCR0A |=  (1<<COM0B1); // COM0B1, COM0B0: 1 0 - None-inverted mode для OC0B, для инверсного режима ШИМ нужно 1 1
   TCCR0A &= ~(1<<COM0B0);
   TCCR0A |=  (0<<WGM01);  // WGM02, WGM01, WGM00: 0 1 1 - Fast PWM
   TCCR0A |=  (1<<WGM00);

   TCCR0B &= ~(1<<CS02);   // CS02, CS01, CS00: 0 0 1 - тактовый генератор CLK
   TCCR0B &= ~(1<<CS01);
   TCCR0B |=  (1<<CS00);
   TCCR0B &= ~(1<<WGM02);

   // TIMER1
   TCCR1A |=  (1<<COM1A1); // COM1A1, COM1A0: 1 0 - None-inverted mode для OC1A, для инверсного режима ШИМ нужно 1 1
   TCCR1A &= ~(1<<COM1A0);
   TCCR1A |=  (1<<COM1B1); // COM1B1, COM1B0: 1 0 - None-inverted mode для OC1B, для инверсного режима ШИМ нужно 1 1
   TCCR1A &= ~(1<<COM1B0);
   TCCR1A &= ~(0<<WGM11);  // WGM13, WGM12, WGM11, WGM10: 0 1 0 1 - Fast PWM, 8bit
   TCCR1A |=  (1<<WGM10);

   TCCR1B &= ~(1<<CS12);   // CS12, CS11, CS10: 0 0 1 - тактовый генератор CLK
   TCCR1B &= ~(1<<CS11);
   TCCR1B |=  (1<<CS10);
   TCCR1B &= ~(1<<WGM13);
   TCCR1B |=  (1<<WGM12);

   // TIMER2
   TCCR2A |=  (1<<COM2A1); // COM2A1, COM2A0: 1 0 - None-inverted mode для OC2A, для инверсного режима ШИМ нужно 1 1
   TCCR2A &= ~(1<<COM2A0);
   TCCR2A |=  (1<<COM2B1); // COM2B1, COM2B0: 1 0 - None-inverted mode для OC2B, для инверсного режима ШИМ нужно 1 1
   TCCR2A &= ~(1<<COM2B0);
   TCCR2A |=  (1<<WGM21);  // WGM22, WGM21, WGM20: 0 1 1 - Fast PWM
   TCCR2A |=  (1<<WGM20);

   TCCR2B &= ~(1<<CS22);   // CS22, CS21, CS20: 0 0 1 - тактовый генератор CLK
   TCCR2B &= ~(1<<CS21);
   TCCR2B |=  (1<<CS20);
   TCCR2B &= ~(1<<WGM22);

   //Установить все PWM в 0
   OCR0A = 0;
   OCR0B = 0;
   OCR1A = 0;
   OCR1B = 0;
   OCR2A = 0;
   OCR2B = 0;
   //Разрешить/запретить прерывания по таймерам
   TIMSK0 &= ~(1<<TOIE0); //Запретить прерывания на таймер 0
   TIMSK1 &= ~(1<<TOIE1); //Запретить прерывания на таймер 1
   TIMSK2 |=  (1<<TOIE2); //Разрешить прерывания на таймер 2
}

//******************************************************************
//Прерывание по таймеру 2 (TIMER2)
ISR(TIMER2_OVF_vect) {
  // PIND |= (1 << 2); //Для замера реальной частоты ардуино на PIN 2  строку раскоментировать
   phaccu = phaccu + delta;   // Добавляем дельту(рассчитна по частоте) в аккумулирующую переменную 32 бит
   
   //Фаза 1
   incr = (phaccu >> 22); //10 бит. Старщие 2 бита(incr & 0x300) - номер четверти, младшие 8 бит(incr & 0xFF) - номер значения из таблицы констант
   
   switch (incr & 768) { //B1100000000=0x300
      case 0: //Первая четверть, выбрать константу из массива в пин A.
      {
   OCR0B = 0;
   PORTD|=(1<<4);// включаем пин используется для фиксации начала фазы второй ардуиной
   OCR0A = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 256: //Вторая четверть, выбрать обратнную константу из массива в пин A
      {
   OCR0B = 0;
   OCR0A = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      case 512: //Третья четверть, выбрать константу из массива в пин B
      {
   OCR0A = 0;
   PORTD &= ~(1<<4);// выключаем пин используется для фиксации начала фазы второй ардуиной
   OCR0B = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 768: //Четвертая четверть, выбрать обратнную константу из массива в пин B
      {
   OCR0A = 0;
   OCR0B = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      default:
      {
   OCR0A = 0;
   OCR0B = 0;
   break;
      }
   }
   
   //Фаза 2. Смещена относительно фазы 1 на 120. 1/2*Pi = 512; 120 = 2/3*Pi = 512*4/3 = 683. Сдвинем фазу на 683 деления
   incr = incr + 683;
   //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
   incr &= 1023;

   switch (incr & 768) { //B1100000000=0x300
      case 0: //Первая четверть, выбрать константу из массива в пин A.
      {
   PORTD|=(1<<7);     
   OCR1B = 0;
   OCR1A = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 256: //Вторая четверть, выбрать обратнную константу из массива в пин A
      {
   OCR1B = 0;
   OCR1A = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      case 512: //Третья четверть, выбрать константу из массива в пин B
      {
   PORTD &= ~(1<<7);     
   OCR1A = 0;
   OCR1B = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 768: //Четвертая четверть, выбрать обратнную константу из массива в пин B
      {
   OCR1A = 0;
   OCR1B = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      default:
      {
   OCR1A = 0;
   OCR1B = 0;
   break;
      }
   }
   
   //Фаза 3. Смещена относительно фазы 2 на 120. 1/2*Pi = 512; 120 = 2/3*Pi = 512*4/3 = 683. Сдвинем фазу на 683 деления
   incr = incr + 683;
   //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
   incr &= 1023;

   switch (incr & 768) { //B1100000000=0x300
      case 0: //Первая четверть, выбрать константу из массива в пин A.
      {
   PORTB|=(1<<0);     
   OCR2B = 0;
   OCR2A = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 256: //Вторая четверть, выбрать обратнную константу из массива в пин A
      {
   OCR2B = 0;
   OCR2A = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      case 512: //Третья четверть, выбрать константу из массива в пин B
      {
   PORTB &= ~(1<<0);     
   OCR2A = 0;
   OCR2B = pgm_read_byte_near(sineP4 + (incr & 255));
   break;
      }
      case 768: //Четвертая четверть, выбрать обратнную константу из массива в пин B
      {
   OCR2A = 0;
   OCR2B = pgm_read_byte_near(sineP4 + 255 - (incr & 255));
   break;
      }
      default:
      {
   OCR2A = 0;
   OCR2B = 0;
   break;
      }
   }
}
//******************************************************************
void loop()
{

}


ВАЖНО !!! Генератор программировался без загрузчика ардуины через программатор HEX файлом сгенерированным самой средой ардуины.
при желание можно и через ардуину но тогда частота на выходе немного поднимается и в программе платы обработки сигнала надо подобрать период новой частоты.

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


Код: Выделить всё

#include "LedControl.h"
//15-DIN  17-CLK  16-CS
LedControl lc = LedControl(15, 17, 16, 1);
float stepp = 500; // шаг резьбы вала на оборот в сотках ****
int pyeriud = 9000; //период снимаемый с сельсина за оборот макс значения ****
byte dig_0_x = 0;
byte dig_1_x = 0;
byte dig_2_x = 0;
byte dig_3_x = 0;
byte dig_4_x = 0;
char sign_x = ' ';
byte withholding = 0;
long totals_rew = 0;
long totals_led = 0;
long totals_led_1 = 0;
long AA = 0;
long AAA = 0;
int a = 0;

int cycle_time = 10; // время обновления показаний 100= 1 сек
float divider ;// 22.54 для вала 4 мм 9016/400=22.54
int pyeriud_1;//9016/3=3005
int pyeriud_2;//3005+3005=6010
byte rotation;
byte b;
volatile long taim; //время прихода импульса с сельсина
volatile long interval;
long interval_2; // для хранения не изменяемых показаний лед
long zerro = 0;

void setup()
{
  pyeriud_1 = pyeriud / 3; //раздел оборота сельсина на 3 сектора  часть 1
  pyeriud_2 = pyeriud_1 * 2; //раздел оборота сельсина на 3 сектора часть 2
  divider = pyeriud / stepp; //вычисляем делитель шага
  /*LOW вызывает прерывание, когда на порту LOW
    CHANGE прерывание вызывается при смене значения на порту, с LOW на HIGH и наоборот
    RISING прерывание вызывается только при смене значения на порту с LOW на HIGH
    FALLING прерывание вызывается только при смене значения на порту с HIGH на LOW */
  attachInterrupt(0, P1, RISING  ); // внешнее прерывание по pin-2
  attachInterrupt(1, P2, FALLING  );// внешнее прерывание по pin-3
  lc.shutdown(0, false);
  lc.setIntensity(0, 3); /* Установите яркость до средних значений 1-15 */
  lc.clearDisplay(0);
  pinMode(6, INPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
 // Serial.begin(9600);
}

void P1() {
  taim = micros();//подсчет разности периуда синхронизированного по  выводу
  a++;
}
void P2() {
  interval = micros() - taim; //импульс синхронизации
  //if (interval > pyeriud){interval = pyeriud;}
 if (interval >= 0        && interval <= pyeriud_1) {rotation=1;}
 if (interval > pyeriud_1 && interval <= pyeriud_2) {rotation=2;}
 if (interval > pyeriud_2 && interval <= pyeriud)   {rotation=3;}
 if(rotation==3&&b==4) {b=1;}
 if(rotation==1&&b==0) {b=2;}
 if(rotation==3&&b==0) {b=1;}
 if(rotation==1&&b==3) {b=1;}
 if(rotation==1&&b==1) {b=2;AA++;AAA=AA*stepp;}//  счет в + при каждом обороте
 if(rotation==3&&b==2) {b=3;AA--;AAA=AA*stepp;} //  счет в - при каждом обороте
 if(rotation==2&&b==3) {b=4;}
 if(rotation==2&&b==1) {b=2;}
 if(rotation==1&&b==4) {b=2;}
 if(rotation==2&&b==2) {b=0;}
 
}
 
void loop() {

 
 if (a > cycle_time) {
   a = 0;

    totals_led_1 = (interval / divider) + AAA - 1; //вычисление показаний для дисплея прямой счет
    // totals_led_1=((interval/divider)+AAA-1)*-1; //вычисление показаний для дисплея реверс счет
    totals_led = totals_led_1 - zerro; // ноль по кнопке
    if (withholding < 20) {
      withholding++;
      lc.setChar(0, 0, '-', 0); lc.setChar(0, 1, '-', 0); lc.setChar(0, 2, '-', 0); lc.setChar(0, 3, '-', 0);
      lc.setChar(0, 4, '-', 0); lc.setChar(0, 5, '-', 0); lc.setChar(0, 6, '-', 0); lc.setChar(0, 7, '-', 0);
    }  // во время старта
    //программы выдаем на табло "----" c задержкой

    // ----------печатаем показания сельсина---------------------

    if (withholding == 20) { //печатаем показания сельсина спустя задержку после  включения

      if (totals_led < 0) {
        sign_x = '-';  // знак для печати
      }
      else  {
        sign_x = ' ';
      }
      lc.setChar(0, 6, ' ', 0); lc.setChar(0, 7, ' ', 0);
      if (totals_led >= 0) {
        // раскладываем по знакам положительные значения
        dig_0_x = (totals_led / 1) % 10; //единици
        dig_1_x = (totals_led / 10) % 10;
        dig_2_x = (totals_led / 100) % 10;
        dig_3_x = (totals_led / 1000) % 10;
        dig_4_x = (totals_led / 10000);

        lc.shutdown(0, false);
        // 0-2 знаки печатаем всегда
        lc.setDigit(0, 0, dig_0_x, 0);
        lc.setDigit(0, 1, dig_1_x, 0);
        lc.setDigit(0, 2, dig_2_x, 1);

        // Определяем необходимость печати 3-4 знака
        if (totals_led > 999) {
          lc.setDigit(0, 3, dig_3_x, 0);
        }
        if (totals_led > 9999) {
          lc.setDigit(0, 4, dig_4_x, 0);
        }

        // определяем знакоместо и печатаем "+" "-"
        if ((totals_led >= 0) & (totals_led < 1000))     {
          lc.setChar(0, 3, sign_x, 0);
          lc.setChar(0, 4, ' ', 0);
          lc.setChar(0, 5, ' ', 0);
        }
        if ((totals_led > 999) & (totals_led < 10000))   {
          lc.setChar(0, 4, sign_x, 0);
          lc.setChar(0, 5, ' ', 0);
        }
        if ((totals_led > 9999) & (totals_led < 100000)) {
          lc.setChar(0, 5, sign_x, 0);
        }
      }

      if (totals_led < 0) {
        totals_rew = totals_led * -1;
        // раскладываем по знакам отрицательные значения
        dig_0_x = (totals_rew / 1) % 10; //единици
        dig_1_x = (totals_rew / 10) % 10;
        dig_2_x = (totals_rew / 100) % 10;
        dig_3_x = (totals_rew / 1000) % 10;
        dig_4_x = (totals_rew / 10000);

        lc.shutdown(0, false);
        // 0-2 знаки печатаем всегда
        lc.setDigit(0, 0, dig_0_x, 0);
        lc.setDigit(0, 1, dig_1_x, 0);
        lc.setDigit(0, 2, dig_2_x, 1);

        // Определяем необходимость печати 3-4 знака
        if (totals_rew > 999) {
          lc.setDigit(0, 3, dig_3_x, 0);
        }
        if (totals_rew > 9999) {
          lc.setDigit(0, 4, dig_4_x, 0);
        }

        // определяем знакоместо и печатаем "+" "-"
        if ((totals_rew >= 0) & (totals_rew < 1000))     {
          lc.setChar(0, 3, sign_x, 0);
          lc.setChar(0, 4, ' ', 0);
          lc.setChar(0, 5, ' ', 0);
        }
        if ((totals_rew > 999) & (totals_rew < 10000))   {
          lc.setChar(0, 4, sign_x, 0);
          lc.setChar(0, 5, ' ', 0);
        }
        if ((totals_rew > 9999) & (totals_rew < 100000)) {
          lc.setChar(0, 5, sign_x, 0);
        }
      }
    }
    //Serial.println(interval);
    Button();
  }
}

void Button()
{
  if (digitalRead(6) == LOW) {
    digitalWrite(13, LOW);
  }
  else {
    digitalWrite(13, HIGH);
    zerro = totals_led_1;
  }
}


Для программирования использовалась среда Arduino 1.8.10
В данном виде устройство позволяет отображать координаты с дискретностью 0.01 мм и точностью 0.03 мм. Дает возможность обнуления координат.
Устройство не сбивается при оборотах ротора сельсина 200 об/мин, показатели на сбой выше.
Есть возможность выбора необходимого шага винта.
В коде не представлена, но есть возможность вывода сигналов энкодера (на плате элементы опторазвязки), что может подружить устройство с любым китайским УЦИ и получить из проекта обычный адаптер, но система не обладает высокими скоростными характеристиками по выводу сигнала энкодера, по этому счет на внешнем УЦИ занимает какое то время в случае быстрых перемещений (ускоренный переезд) на большие расстояния (100-300 мм и выше). Этой функцией плотно не занимался и не знаю можно ли это исправить.

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


Работы по данному проекту пока прекратил, нет настроения им заниматься и ещё одна причина, на моем станке нет места для установки
сельсина на вертикальную подачу без танцев с бубном, а мне как раз и интересны электронные концевики вертикальной подачи
при расточных работах.

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

Аватара пользователя
anker33333
Реальное имя: владимир
Откуда: москва

УЦИ на сельсине

Сообщение anker33333 » 09 янв 2020, 20:10

kasss писал(а):Источник цитаты моя работа любительская и может вызвать много нареканий

однозначно RESPECT :good2:
даже если точность в реальных условиях будет ниже


Вернуться в «Измерительные инструменты»