Добрый день, люди добрые. Такая незадача случилась с программированием проекта на Digispark, не могу подружить энкодер и ШИМ библиотеку (TinySoftPwm.h). Спойлер: Code Код (C++): // digispark не понимает 'byte' и 'boolean' #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <TinySoftPwm.h> // PWM // Энкодер #define pin_A 3 // (D-) #define pin_B 5 // (RESET) #define APOT 2 #define PWM_IRL540 1 // 4 и 0 - I2c // Частота обновления экрана #define TIME_LCD 200 unsigned long int time_lcd; unsigned long int time_pwm_motor; #define TIME_ENCODER 15 unsigned long time_encoder; unsigned char encoder_A; unsigned char encoder_B; unsigned char encoder_old; int val; float VOLTS; float VOLTS_SUMM; float R1 = 51000.0; // сопротивление R1 float R2 = 9100.0; // сопротивление R2 LiquidCrystal_I2C lcd(0x3F, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display void setup() { pinMode(APOT, INPUT); pinMode(pin_A, INPUT_PULLUP); pinMode(pin_B, INPUT_PULLUP); pinMode(PWM_IRL540, OUTPUT); lcd.init(); lcd.backlight(); lcd.setCursor(1, 0); lcd.print("Hello, world!"); lcd.setCursor(0, 1); lcd.print("Ywrobot Arduino!"); // Инициализация, как я понял, портов ШИМ, 0 - задействовать все возможные ШИМ-пины // Первое число, видимо, задержка (в мкс) между сменой стостояния лог.уровня, т.е. частота // TinySoftPwm_begin(3, 0); // 20kHz/defolt - 128 x TinySoftPwm_process() calls before overlap (Frequency tuning), 0 = PWM init for all declared pins delay(1000); lcd.clear(); } void loop() { // Че то там нужное для библиотеки ШИМ :) static uint32_t StartUs = micros(); if ((micros() - StartUs) >= 60) { StartUs = micros(); TinySoftPwm_process(); } // Опрашиваем энкодер ENCODER(); // управляем полевичком ШИМ TinySoftPwm_analogWrite(PWM_IRL540, val); /* for (int i = 0; i < 128; i++) { VOLTS = ((analogRead(APOT) * 5.0) / 1024) / (R2 / (R1 + R2)); VOLTS_SUMM = VOLTS_SUMM + VOLTS; } if (millis() - time_lcd > TIME_LCD) { lcd.setCursor(5, 1); lcd.print(VOLTS_SUMM / 127); lcd.print("V "); time_lcd = millis(); } VOLTS_SUMM = 0.0; */ } void ENCODER() { // Если прошло больше 15 мс if (millis() - time_encoder > TIME_ENCODER) { time_encoder = millis(); encoder_A = digitalRead(pin_A); // считываем состояние выхода А энкодера encoder_B = digitalRead(pin_B); // считываем состояние выхода B энкодера if (encoder_A == LOW && encoder_old == HIGH) { if (encoder_B == HIGH) { val = constrain(val - 5, 0, 255); // TinySoftPwm_analogWrite(PWM_IRL540, val); lcd.setCursor(6, 0); lcd.print(val); lcd.print(" "); } else { val = constrain(val + 5, 0, 255); // TinySoftPwm_analogWrite(PWM_IRL540, val); lcd.setCursor(6, 0); lcd.print(val); lcd.print(" "); } } encoder_old = encoder_A; } } Вместе не работает энкодер, буд-то его пины не опрашиваются, может библиотека ШИМ переконфигурирует эти пины, не знаю. Без строчки Код (C++): TinySoftPwm_begin(3, 0); // 20kHz/defolt - 128 x TinySoftPwm_process() calls before overlap (Frequency tuning), 0 = PWM init for all declared pins Энкодер начинает работать, но перестает ШИМ Пробовал в строчке TinySoftPwm_begin(3, 0); Значение 0 менять на другие (1,2,3,4,5), думал может нужно включить ШИМ только для одного вывода (1 используется у меня), но ничего не менялось. Как можно исправить, что предпринять и почему так происходит, как думаете? Спасибо.
Тогда помогите написать код для программного ШИМ, вообще не могу разобраться, что я делаю не так, пожалуйста. Вот, что у меня получилось, точнее не получилось. Принцип: если увеличиваем скважность, то уменьшаем время паузы и увеличиваем время импульса от 0-50% и наоборот от 50-100%. Но чтото совсем запутался. Код (C++): // 25 мкс время между периодами, должна ровняться примерно около 15 кГц void PWM() { if ((micros() - time_pwm) > value_pwm + 25) { digitalWrite(PWM_IRL540, HIGH); if (c != 1) { time_pwm = micros(); c = 1; } // меняя значение value_pwm, меняем скважность if ((micros() - time_pwm) > 25 - value_pwm) { c = 0; digitalWrite(PWM_IRL540, LOW); time_pwm = micros(); } } }
При таком подходе процессор будет только PWM обслуживать. Больше ни чего он делать не сможет, в противном случае погрешность частоты и скважности очень быстро начнет стремится к бесконечности (это именно касательно 15 KHz). Тогда проще два delayMicroseconds поставить вместо всего кода, и все сразу понятно станет. Кстати в Вашей реализации базовая погрешность будет начинаться примерно с 30%, но это конечно не точно, на вскидку. На мой взгляд использования Arduino для одного PWM это мягко говоря не оптимально. Чем Вам аппаратные PWM не угодили? напишите может лучше в этом направлении поправить. Они то же могут 15 KHz выдовать, и даже много больше. А функция micros(); я так понимаю весьма примерная.
Digispark используется для измерения напряжения на моторе и управления им же. ШИМ как раз используется для управления полевиком, управляющий мотором. Стандартная частота ШИМ Digispark не выше 500 Гц (490 заявлено). При такой частоте обмотка мотора начинает "пищать" т.к. частота попадает в звуковой диапазон. Знаю, что на обычных Atmega, есть пред делители, с помощью которых можно выставлять частоты от ~300 Гц до 31 или 62 кГц, в зависимости от таймера. Но что-то подобное на Attiny85 не смог найти. Есть библиотека, в 1 посту, ШИМа на Digispark, но она видимо переконфигурирует порты использующиеся у меня для энкодера. Замкнутый круг какой-то. Хотелось бы конечно, чтобы не только на ШИМ работал МК.
У Attiny85 один восьми битный таймер. Думаю он задействован ядром Arduino на частоте 1 KHz. Если хотите использовать Arduino IDE то можно извратится с прерываниями "Compare Match A Interrupt" или "Compare Match B Interrupt" и двигать значение регистра сравнения прямо внутри прерывания. Но я бы этот процессор в Arduino IDE не шил. Уж очень много от его скудных ресурсов/возможностей она отъедает. Если у Вас только шим и измерение напряжения (ADC) то легко реализуется в штатной среде Atmega Studio. Знаючи, работы на пол дня с тестированием. Не знаючи с подсказками 3-4 дня пока в DataSheet научитесь разбираться.
Вот запуск двух шимов на Atmega328 Код (C++): 91 void init_motors(){ // Отмечаем выводы как OUTPUT 92 DDRD |= _BV(DDD6) | // PWM двигателя A 93 _BV(DDD7) | // Направление двигателя A 94 _BV(DDD5) | // PWM двигателя B 95 _BV(DDD4) ; // Направление двигателя B 96 PORTD = 0; 97 TCCR0B |= _BV(CS00);//Делитель таймера 1 98 TCCR0A = _BV(WGM01) | _BV(WGM00) | // fast mode PWM верхнее значение 0xFF 99 _BV(COM0A1) | // Спадающий фронт вывода OC0A при OCR0A compare match 100 _BV(COM0B1) ; // Спадающий фронт вывода OC0B при OCR0B compare match 101 OCR0A = 0; // Скважность 1..255 102 OCR0B = 0; // Скважность 1..255 103 } timer1 на tinny85 делитель 16 надо ставить Код (Text): PWM Frequency Clock Selection CS1[3:0] OCR1C RESOLUTION 20 kHz PCK/16 0101 199 7.6 Если не сможешь портировать пиши завтра возьму с собой tinny85.
Спасибо, огромное. Но, да, трудности возникли, ардуинщик, что сказать compare match - это прерывание?
Без Вас не разберусь. Спойлер: код Код (C++): #define PWM_MOTOR 1 // пин для ШИМ void setup() { // конфигуриурем пин ШИМ на выход "DDRD |= _BV(DDD5) | // PWM двигателя" pinMode(PWM_MOTOR, OUTPUT); // устанавливаем LOW уровень "PORTD = 0;" digitalWrite(PWM_MOTOR, LOW); TCCR0B |= _BV(0101); // Делитель таймера 16 // Включаем фаст ШИМ режим TCCR0A = _BV(WGM01) | _BV(WGM00) | // fast mode PWM верхнее значение 0xFF _BV(COM0A1); // Спадающий фронт вывода OC0A при OCR0A compare match OCR0A = 0; // Скважность 1..255 } void loop() { } Сказать, что я что-то понимаю, что написал, ничего не сказать. compare match - прерывания по совпадению, можете объяснить принцип его работы и помочь с написанием кода программного ШИМ. Спасибо.
Вот у меня скважность регулируется. Код (C++): OCR1A = 230; По частоте не скажу не мерил. Код (C++): #define PWM_MOTOR 1 // пин для ШИМ void setup() { // конфигуриурем пин ШИМ на выход pinMode(PWM_MOTOR, OUTPUT); TCCR1 |= _BV(CS12) | _BV(CS10) | // Делитель таймера 16 _BV(COM1A1 ) | // Спадающий фронт вывода OC0A при OCR0A compare match _BV(PWM1A) ; //Запустить генерацию импульсов OCR1A = 230; } void loop() { } Если поставить максимальный делитель Код (C++): TCCR1 |= _BV(CS13) |_BV(CS12) | _BV(CS11) | _BV(CS10) | То скважность можно увидеть визуально. Частота что-то около одного герца. Ну и максимальная частота: Код (C++): TCCR1 |= _BV(CS10) |
Если частота не достаточно высокая добавьте в setup() Код (C++): PLLCSR |= _BV(PLLE); PLLCSR |= _BV(PCKE); Тогда ШИМ будет тактироваться от генератора 64MHz вместо тактовой частоты процессора
Безумное вам спасибо. На работе есть осциллограф, померю фактическую частоту. Вроде должно получиться!
Хм, померил сейчас дома обычным мультиметром с возможностью замера частоты. При таком коде: Код (C++): #define PWM_MOTOR 1 // пин для ШИМ void setup() { // конфигуриурем пин ШИМ на выход pinMode(PWM_MOTOR, OUTPUT); TCCR1 |= _BV(CS12) | _BV(CS10) | // Делитель таймера 16 _BV(COM1A1 ) | // Спадающий фронт вывода OC0A при OCR0A compare match _BV(PWM1A) ; //Запустить генерацию импульсов PLLCSR |= _BV(PLLE); PLLCSR |= _BV(PCKE); OCR1A = 50; } void loop() { } Выдает 4 кГц. Без строчек Код (C++): PLLCSR |= _BV(PLLE); PLLCSR |= _BV(PCKE); вовсе 1 кГц. Хотя может мультик неправильно считает, но вроде не должен (лимит у него 20 кГц). При "TCCR1 |= _BV(CS10)| _BV(CS10)|" тоже разницы в частоте нет. А скважность меняется, все хорошо. Надо будет протестировать на 4 кГц, может мотор не так сильно пищать будет, в любом случае - СПАСИБО!
Забавная проблема! Видимо среда Arduino инициализирует регистры сама поэтому частота не соответствует заданной Добавьте в начало setup() строчку: Код (C++): TCCR1 = 0; При этом минимальный делитель будет _BV(CS10), частота примерно 250KHz|