В итоге решил что информация не должна пропадать, иногда встречаются вопросы как можно запустить сельсин.
Коротко об истории, есть станок (6720вф1) на котором возможно когда то стояли сельсины а возможно индуктосины (но это не столь важно), прикупил сельсины БС 155 А и задумался как его оцифровать, сразу скажу что УЦИ с оптическими линейками у меня стоят, но на станке нет регулируемых упоров авто подач и было желание использовать сельсин как электронные настраиваемые упоры конца подачи,
а самая главная причина, это практика в изучение программирования.
В итоге родилось самостоятельное УЦИ на Ардуино с дискретностью 0.01 мм для винтов с шагом до 5 мм, шаг можно и больше применять но тогда дискретность на мой взгляд надо делать 0.1 мм., все это связано с искажением генерируемой синусоиды и снимаемого сигнала.
Шаг винта можно свободно менять в программе платы обработки сигнала в виде выбора количества соток на оборот (100,150,200-500)
Принцип работы такой, с 3х фазного генератора сделанного на одной плате ардуино и усилителя сигнал
подается на сельсин , с сельсина снимаются показатели смещения фазы, полученный сигнал подаётся
на вторую плату ардуино где по сигналу синхронизации с генератора вычисляется сдвиг и переводится в линейный размер.
Генератор выдает синусоидальный сигнал частотой 110 гЦ. Так как ардуина не может вычислять быстрее чем 4 миллисекунды
( это всего 625 делений на оборот при 400 гц для БС 155 А) и стабильность генерации немного плавает то временные рамки частоты пришлось
сдвинут для увеличения точности и снижения девиации показаний, из за этого не 400 гц а 110. Для примера время периуда 400 гц 0.0025 сек.
на шаг винта 5 мм надо 5 мс на сотку, а при 110 гц 18 мс на сотку. Это не значит что я могу получить 2500 тиков на оборот, остаются все теже 600
но показания выводятся стабильней и при переходе через .0. снижается пропуск.
Схема
Разработанную плату заказал в Китае, плата имеет не указанные на схеме элементы, так как я закладывал возможности под свои нужды.
Устройство при проверке плитками и по показаниям лимба станка показало точность не хуже 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. Хочу сказать что устройство в практических условиях не тестировалось продолжительное время но за время проверки показало себя устойчивым в работе, все разрабатывалось дилетантом в программирование и проектирование схем по этому не исключаю что многое сделано не правильно и при продолжительной работе могут вылазить косяки. Данный материал на мой взгляд больше носит характер ознакомительного.