Ви не увійшли.
![]()
ну в принципі так і у мене - теж таблиця станів автомату ![]()
А упарюватись в особливості конкретного контроллера в мене не було необхідності. Arduino воно досить кросплатформене, але не найефективніше. Нас це влаштовує ![]()
По перше, енкодер хоч і оптичний, але тригерів Шмідта всередині немає.
Тригери Шмітта є на GPIO пінах атмеги. Гістерезис десь 100 мВ при 5 В Vcc.
Для компенсації кривих фронтів є таке рішення
При правильній реалізації ніякої "компенсації кривих фронтів" не потрібно.
Ось приклад для 4-квадрантного енкодера, конечний автомат - LUT з 32 елементів:
class encoder
{
uint8_t state { 0 };
static inline const uint8_t table[] = {
// in: ab aB Ab AB
0b000'01, 0b001'01, 0b010'01, 0b011'01, // 000 ab
0b000'01, 0b001'01, 0b010'01, 0b110'01, // 001 aB
0b000'01, 0b001'01, 0b010'01, 0b111'01, // 010 Ab
0b000'01, 0b001'01, 0b010'01, 0b011'01, // 011 AB
0b000'00, 0b001'01, 0b100'01, 0b110'01, // 100 Ab_b
0b000'10, 0b101'01, 0b010'01, 0b111'01, // 101 aB_a
0b000'01, 0b001'01, 0b100'01, 0b110'01, // 110 AB_b
0b000'01, 0b101'01, 0b010'01, 0b111'01, // 111 AB_a
};
public:
int8_t update(uint8_t in)
{
uint8_t act = table[state | in];
state = act & 0b111'00;
return 1 - (act & 0b000'11);
}
};На вході update() два молодших біта - стани ліній A та B. На виході: -1, 0 або +1.
Лічильник клацає по фронту і спаду кожної фази, тобто в 4 рази швидше - це треба врахувати.
t = 1 / (rpm_max * 60 * ppr * 4)
Це дасть мінімальний час між фронтами. А там уже дивіться, чи забезпечить потрібний таймінг та чи інша реалізація.
Всі зовнішні interrupts, доступні для Atmega328, зайняті.
Не до будь-якої ноги можна підключити енкодер.
У атмеги є Pin Change Interrupt на всі GPIO ноги.
У деяких інших контролерів (STM32, CH32) у таймера-лічильника є спеціальний режим для енкодера.
Довелось вимірювати швидкість обертання за допомогою такої штуки E6B2-CWZ6C
хттпс://www.rcscomponents.kiev.ua/datasheets/e6b2-c-datasheet.pdf
Зіткнувся з неочевидними не перший погляд речами, може комусь буде корисно.
По перше, енкодер хоч і оптичний, але тригерів Шмідта всередині немає. На малих швидкостях фронти імпульсів виглядають так:
І в зв"язку з цим, по 2 імпульсах рахувати швидкість не варто, бажано по хоча б 1 повному оберту.
По друге, результат вимірювання бажано отримувати актуальний зразу, а не після повного оберту.
Для компенсації кривих фронтів є таке рішення: по переходу 0-1 - лічильник інкрементується, по переходу 1-0 - декрементується. Після закінчення перехідного процесу залишиться +1 (чи -1, в залежності від напрямку обертання). Лічильник клацає по фронту і спаду кожної фази, тобто в 4 рази швидше - це треба врахувати.
Для підрахунку середньої швидкості обертання - інформацію про кожен імпульс (timestamp і покази лічильника) писати у кільцевий буфер. Тоді швидкість буде просто розраховуватись з різниці між початком і кінцем буфера. Якщо в початку буфері ці покази вже є - просто переписати поверх, щоб не зберігати непотрібний брязкіт.
Недоліки такого рішення:
Треба досить багато тактів процесора і пам"яті. Великі оберти Atmega328 не витягне.
Всі зовнішні interrupts, доступні для Atmega328, зайняті.
Не до будь-якої ноги можна підключити енкодер.
#define OMRON_PIN_A 2
#define OMRON_PIN_B 3
#define OMRON_PULSES_2_ROTATE 10
#define COUNT_OF_MEASURES 20
struct MEASURE_S {
unsigned long ts;
int ticks;
};
volatile MEASURE_S measures[COUNT_OF_MEASURES];
volatile int measures_head=COUNT_OF_MEASURES-1;
volatile int measures_tail=0;
long int ticks = 0;
const int increment[16] = {0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0};
volatile char ABprev = 0;
void isr_omron()
{
char AB = (PIND >> 2) & 3; // PORT D bits 2,3
ticks += increment[AB+ABprev*4];
unsigned long now = micros();
if (measures[measures_head].ticks == ticks
|| measures[(measures_head-1)% COUNT_OF_MEASURES].ticks == ticks
|| measures[(measures_head-2)% COUNT_OF_MEASURES].ticks == ticks
|| measures[(measures_head-3)% COUNT_OF_MEASURES].ticks == ticks) // bounce
{
measures[measures_head].ts = now;
measures[measures_head].ticks = ticks;
} else {
measures_head = (measures_head+1) % COUNT_OF_MEASURES;
measures[measures_head].ts = now;
measures[measures_head].ticks = ticks;
measures_tail = (measures_head+1) % COUNT_OF_MEASURES;
};
ABprev = AB;
}
int calc_rpm() //оберти за хвилину
{
int res = 0;
unsigned long dt;
int d_cont;
int head, tail;
int t;
noInterrupts();
t = ticks;
head = measures_head; tail = measures_tail;
if (measures_head != measures_tail)
{
dt = measures[measures_head].ts - measures[measures_tail].ts;
d_cont = abs(measures[measures_head].ticks - measures[measures_tail].ticks);
if (micros() > measures[measures_head].ts + dt)
{
dt = micros() - measures[measures_tail].ts;
};
};
;
interrupts();
if (dt == 0)
{
res = 0;
}
else
{
res = 60 * 1000000 * d_cont / dt / OMRON_PULSES_2_ROTATE / 4;
};
return res;
}
void setup() {
pinMode(OMRON_PIN_A, INPUT_PULLUP);
pinMode(OMRON_PIN_B, INPUT_PULLUP);
ABprev = (PIND >> 2) & 3; // PORT D bits 2,3
int _interrupt = digitalPinToInterrupt(OMRON_PIN_A);
attachInterrupt(_interrupt, isr_omron, CHANGE);
Serial.print(F("Use interrupt A ")); Serial.println(_interrupt);
_interrupt = digitalPinToInterrupt(OMRON_PIN_B);
attachInterrupt(_interrupt, isr_omron, CHANGE);
}