Здравия всем!
Люди с опытом, подскажите как правильно организовать код, что бы программа не “тупила”.
Суть в следующем :
На борту шаговый мотор, подключенный к драйверу, получающий управляющие импульсы от Timer4 с изменяемой частотой от потенциометра.
Оптический энкодер , с 50 прерываний на один оборот вала шагового двигателя (зубчатое колесо).
В панели управления несколько кнопок, LCD I2C 20x4 для задания параметров работы устройства, выбор пунктов меню осуществляется механическим энкодером ( прикрутил библиотеку Гайвера). Так же в дополнении есть 7Segment на TM1637, отображающий в реальном времени количество импульсов оптического энкодера. Период импульсов оптического энкодера, при максимальной скорости шагового двигателя , порядка 4ms.
Скажем так, аппаратная часть, т.е. прерывания, Таймер, работают четко. Но траблы начинаются с отображением информации на 7Segment ( 6 знаков), появляются пропуски с увеличением оборотов шагового мотора, а так же жестко начинает тупить механический энкодер, для меню LCD. И как “вишенка на торте”, при максимальных оборотах шаговика, когда в функции void SegmentDisp() подключен вывод на 7Segment - display.showNumberDec(OptoStepCounter); начинает срываться шаговый мотор. Как только отключаю вывод на 7Segment , шаговик работает без сбоев.
Я так понимаю, что когда обработчик прерывания оптического энкодера, начинает слишком часто вызывать функцию библиотеки 7Segment, то библиотека не успевает обрабатывать переменную из обработчика . А что касаемо механического энкодера, то его библиотека вызывается в основном цикле loop(), код которого выполняется в промежутках между прерываниями и прочими аппаратными ресурсами, которые выполняются намного чаще, не оставляя “воздуха” для кода в цикле.
Как решить такую проблему согласования?
Отказаться от библиотек 7Segmen ( TM1637TinyDisplay6.h) и EncButton.h от Гайвера, и прописать код для дисплея и энкодера самому? Или есть какой то другой метод реализации многозадачности, без “тормозов” ? ( RTOS не предлагать).
Приведу основные участки кода, где возникают траблы :
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <TM1637TinyDisplay6.h>
#include <EncButton.h>
const byte Encoder16Bit_OPTO_A = 3; // Encoder wheel (for stacker sync microsteps) ++
const byte Encoder16Bit_OPTO_B = 4; // Encoder wheel (for stacker sync microsteps) --
const uint16_t PUL1 = 7; // Пин для задания скорости вращения Main Stepper
const byte DIR1 = 6; // Пин для задания направления вращения Main Stepper
const byte ENA1 = 5; // Пин для включения/отключения драйверва Main Stepper
const uint8_t potPin = A0; // Аналоговый пин для потенциометра скорости вращения Main Stepper
#define CLK 14
#define DIO 15
// Настройки для библиотеки Гайвера
#define EB_NO_FOR // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)
#define EB_FAST_TIME 30 // таймаут быстрого поворота
#define EB_DEB_TIME 50 // дебаунс кнопки
#define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка)
//End
#define A 52 // Пин для Энкодера A
#define B 53 // Пин для энкодера B
#define SW 51 // Пин для кнопки энкодера
#define LCD_ADDRESS 0x27
#define MENU_ROWS 4 // Количество строк для LCD
#define MENU_COLS 2 // Количество столбцов для LCD
//Disp1637_6 disp(DIO, CLK);
TM1637TinyDisplay6 display(CLK, DIO); // 6-Digit Display Class
//TM1637 TM;
LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); // Создание класса для LCD ( 20 x 4)
EncButton eb(A, B, SW, INPUT, INPUT); // Создание экземпляра класса EncButton с указанными пинами энкодера и кнопки
// Глобальные перменнные для пунктов меню
//.....
//End
// Счетчики оборотов - флаг
volatile bool dataUpdated = false;
// Определяем переменные для хранения состояний пинов
uint32_t OptoStepCounter = 0;
// Переменные для потенциометра
volatile uint8_t potValue = 0; // Переменная для хранения текущего значения потенциометра
// Настройка Timer2 на прерывание при переполнении для оптического энкодера
void setupTimer2() {
// Настраиваем Timer2 на прерывание при переполнении
TCCR2A = 0; // Сбрасываем регистр управления таймером
TCCR2B = 0; // Сбрасываем регистр управления таймером
TCNT2 = 0; // Сбрасываем счетчик таймера
OCR2A = 250; // Устанавливаем значение сравнения, чтобы вызывать прерывание каждые 1 мс
TIMSK2 |= (1 << TOIE2); // Включаем прерывание по переполнению таймера
TCCR2B |= (1 << CS22); // Устанавливаем делитель частоты 64, чтобы таймер 2 работал на частоте 1 МГц
}
// Функция настроек таймера шагового двигателя
void setupTimer4() {
cli(); // Отключение прерываний
TCCR4A = 0;
TCCR4B = 0;
TCNT4 = 0;
OCR4A = 0;
OCR4B = 0;
TCCR4A = (1 << COM4B1) | (1 << COM4B0) | (1 << WGM41) | (1 << WGM40);
TCCR4B = (1 << WGM43) | (1 << WGM42) | (1 << CS40);
sei(); // Включение прерываний
}
// Функция для настройки ADC
void setupADC() {
ADCSRA = 0; // Сбрасываем регистр ADCSRA
ADCSRB = 0; // Сбрасываем регистр ADCSRB
ADMUX = (1 << REFS0) | (0 & 0x07) | (1 << ADLAR); // Настройка регистра ADMUX для выбора опорного напряжения и мультиплексирования A0, разрешение 8 бит
ADCSRA |= (1 << ADEN); // Включаем АЦП
ADCSRA |= (1 << ADSC); // Запускаем преобразование
ADCSRA |= (1 << ADATE); // Непрерывный режим работы АЦП
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Предделитель ADC на 128
ADCSRA |= (1 << ADIE); // Разрешение прерывания ADC
}
/***************************************************************************************************************/
void setup() {
Serial.begin(115200);
Wire.begin(); // Инициализируем шину I2C
Wire.setClock(400000L); // Установка скорости передачи данных на шине I2C в 400 кГц
setupTimer2();
setupTimer4();
setupADC();
cli(); // Запрещаем все прерывания.
// Порт D,E пины 18 и 3
DDRD &= ~(1 << DDD3); // PD3 как вход
DDRE &= ~(1 << DDE5); // PE5 как вход
PORTD |= (1 << PORTD3); // PD2 с уровнем 1
PORTE |= (1 << PORTE5); // PE5 с уровнем 1
// Порт H,G пины 17 и 4
DDRH &= ~(1 << DDH0); // PH0 как вход
DDRG &= ~(1 << DDG5); // PG5 как вход
PORTH |= (1 << PORTH0); // PH0 с уровнем 1
PORTG |= (1 << PORTG5); // PG5 с уровнем 1
// Настройка пинов PH4, PH3, PE3, PL4, PL2, PL3 как выходы, с начальным уровнем 0 и без подтяжки к питанию
// Порт H (PH4, PH3)
DDRH |= (1 << DDH4) | (1 << DDH3); // Настройка пинов PH4 и PH3 как выходы
PORTH &= ~((1 << PORTH4) | (1 << PORTH3)); // Установка начального уровня 0 на пинах PH4 и PH3
// Порт E (PE3)
DDRE |= (1 << DDE3); // Настройка пина PE3 как выход
PORTE &= ~(1 << PORTE3); // Установка начального уровня 0 на пине PE3
// Порт L (PL4, PL2, PL3)
DDRL |= (1 << DDL4) | (1 << DDL2) | (1 << DDL3); // Настройка пинов PL4, PL2 и PL3 как выходы
PORTL &= ~((1 << PORTL4) | (1 << PORTL2) | (1 << PORTL3)); // Установка начального уровня 0 на пинах PL4, PL2 и PL3
// Настройка прерываний для пина 19 (INT2)
EICRA &= ~(1 << ISC30); // Прерывание по изменению состояния
EICRA |= (1 << ISC31);
// Настройка прерываний для пина 3 (INT5)
EICRB &= ~(1 << ISC50);
EICRB |= (1 << ISC51);
// Включаем прерывания
EIMSK |= (1 << INT3) | (1 << INT5); // Включаем прерывания INT4
sei(); // Разрешаем все прерывания.
eb.attach(callback_ENC);
lcd.clear(); //Очистка дисплея ( обновление)
printMenu(); // Вызов функции основного меню
// Initialize 7-segment display
display.begin();
display.clear();
display.setBrightness(7);
display.showNumberDec(OptoStepCounter);
}
/*******Блок Функций для вывода МЕНЮ на LCD*******/
void printMenu(){}
void myClick(){}
void myTurn(){}
//.....
/*******Конец Блока вывода МЕНЮ на LCD*******/
//Обработчик вызовов состояния энкодера
void callback_ENC() {
switch (eb.action()) { // Вызываем метод действия для пинов Энкодера
case EB_CLICK: // Если действие является кликом (нажатием кнопки)
myClick(); // Выполняем функцию обработки клика
break;
case EB_TURN: // Если действие является поворотом (вращением кнопки)
myTurn(); // Выполняем функцию обработки поворота
break;
}
}
// Вывод инфы на 7Segment
void SegmentDisp() {
if (dataUpdated) {
display.showNumberDec(OptoStepCounter);
dataUpdated = false;
}
}
//Обработчик преывания от Timer2 (Частота вызовов 1МГц, согласно настройкам таймера)
ISR(TIMER2_OVF_vect) {
SegmentDisp(); // Вызываем SegmentDsp() по прерыванию от Timer2
}
void loop() {
eb.tick(); // Выполняем опрос событий Энкодера
// Остальной код...
}
// Обработчик прерывания ADC
ISR(ADC_vect) {
potValue = ADCH; // Сохранение текущего значения потенциометра (для 8 бит разрешение)
OCR4A = map(potValue, 0, 255, 24000, 800); // Преобразование среднего значения и установка регистра сравнения
OCR4B = OCR4A - 48; // (48 - 3мкс)
}
ISR(INT5_vect) {
// Сбросить состояние прерывания INT2 в начале
EIFR |= (1 << INTF5);
bool OptoStep_B = (PING & (1 << PING5));
if (OptoStep_B) {
OptoStepCounter -= 1;
} else {
OptoStepCounter += 1;
}
dataUpdated = true;
}