Использование прерывания внутреннего таймера для чтения аналогового датчика
Мне трудно использовать прерывание внутреннего таймера в Arduino Nano, чтобы правильно получить определенное значение от линейного потенциометра и затем мгновенно остановить привод.
По сути, у меня есть дворник линейного потенциометра, подключенный к концу линейного привода. Привод выдвигается/втягивается с помощью модуля L298N, управляемого Nano. В том месте, где он расположен, потенциометр показывает около «80». когда привод полностью выдвинут и около «25»; при полном втягивании. Я ввожу числа через последовательный монитор, чтобы заставить привод двигаться вверх или вниз, эти числа хранятся в EEPROM Nano.
Похоже, мое прерывание не работает, пока привод находится в движении. Например, если я установлю прерывание для остановки двигателя, когда потенциометр показывает «50», он просто пропустит это число, не останавливая привод. Но если я установлю прерывание для остановки двигателя, когда потенциометр показывает минимальное или максимальное значение (где привод находится в данный момент перед активацией), привод правильно не выдвигается и не втягивается. Я думаю, что мое прерывание достаточно быстрое на частоте 8 кГц, поэтому я чувствую, что есть что-то еще, запрещающее приводу останавливаться на определенном значении между максимальным и минимальным.
Мой текущий код приведен ниже, буду очень признателен за любую помощь.
//таймер2 прерывается на частоте 8 кГц
#include <EEPROM.h>
#define EEPROM_SIZE
long number;
long x;
int toggle=0;
int old_x=0;
const int linPot = A1;
int MotorSpeed=255;
int enA = 9;
int in1 = 8;
int in2 = 7;
int currentPosition;
enum {IdleState, UpState, DownState} State;
void setup(){
Serial.begin(9600);
number = EEPROM.read(0);
x=number;
pinMode(enA, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
State = IdleState;
//cli();//остановим прерывания
//устанавливаем прерывание таймера 2 на частоте 8 кГц
TCCR2A = 0;// устанавливаем весь регистр TCCR2A в 0
TCCR2B = 0;// то же самое для TCCR2B
TCNT2 = 0;//инициализируем значение счетчика равным 0
// устанавливаем регистр сравнения совпадений с шагом 8 кГц
OCR2A = 249;// = (16*10^6) / (8000*8) – 1 (должно быть < 256)
// включаем режим CTC
TCCR2A |= (1 << WGM21);
// Устанавливаем бит CS21 для 8 прескалера
TCCR2B |= (1 << CS21);
// включаем прерывание сравнения таймера
TIMSK2 |= (1 << OCIE2A);
sei();//разрешаем прерывания
}//завершаем настройку
ISR(TIMER2_COMPA_vect){//прерывание таймера1 8 кГц
//генерирует пульсовую волну частотой 8кГц/2 = 4кГц
if(currentPosition==50){
HALT();
}
}
void loop(){
if ((Serial.available()&&toggle==0)){
x=Serial.parseInt();
old_x=x;
toggle=1;
}
if((Serial.available())&&(toggle==1)&&(Serial.parseInt()!=old_x)){
toggle=0;
}
if((toggle==1)&&(Serial.parseInt()==0)){
toggle=0;
}
if(x>78){
x=78;
}
if(x<0){
x=0;
}
EEPROM.write(0, x);
Serial.print("X :");
Serial.println(x);
int data = analogRead(linPot);
currentPosition = map(data, 0, 1023, 0, 100);
Serial.print("Potentiometer at ");
Serial.print(currentPosition);
Serial.println("%");
long difference = currentPosition-x;
Serial.print("Difference :");
Serial.println(difference);
if(difference>0){
State = UpState;
}
if(difference>0){
State = DownState;
}
if(difference==0){
State = IdleState;
}
switch (State) {
case IdleState:
Serial.println("Idle State");
HALT();
if((currentPosition-number)<0){
State = UpState;
}
if((currentPosition-number)>0){
State = DownState;
}
break;
case UpState:
Serial.println("Up State");
//Нужно двигаться вниз
DOWN();
if((currentPosition-number)>0){
State = DownState;
}
if((currentPosition-number)==0){
State = IdleState;
}
break;
case DownState:
Serial.println("Down State");
//Нужно двигаться вверх
UP();
if((currentPosition-number)<0){
State = UpState;
}
if((currentPosition-number)==0){
State = IdleState;
}
break;
}
}
void DOWN(){
analogWrite(enA, MotorSpeed);
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
}
void UP() {
analogWrite(enA, MotorSpeed);
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
}
void HALT () {
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
}
@wickedhurricane, 👍2
Обсуждение2 ответа
Во-первых, currentPosition
не объявлен volutable
, поэтому компилятор может хранить это значение в регистре, к которому не обращаются внутри ISR.
Меня также беспокоит последовательная печать со скоростью 9600 бод (довольно медленно), из-за которой позиция может проскочить с 49 на 51, пока вы заняты печатью значений.
Сравнивать на равных, чтобы что-то сделать, всегда чревато.
x=Serial.parseInt();
old_x=x;
toggle=1;
}
if((Serial.available())&&(toggle==1)&&(Serial.parseInt()!=old_x)){
toggle=0;
}
if((toggle==1)&&(Serial.parseInt()==0)){
toggle=0;
}
Вы ожидаете от серийного номера трех целых чисел?
Во-первых, прочтите ответ Ника Гаммона: все его доводы совершенно верны. Он
отмечает, что серийная печать может быть довольно медленной. И действительно, как я посчитал
символов, я считаю, что отпечатки должны занимать не менее 54 мс.
за итерацию цикла. Это действительно очень медленно. В дополнение к этому,
вызовы parseInt()
могут быть намного медленнее, особенно если учесть
что один из них не зависит от Serial.available()
.
В настоящее время код, который считывает показания потенциометра и остановка привода выглядит так:
int currentPosition;
// Срабатывает каждые 125 мкс.
ISR(TIMER2_COMPA_vect) {
if (currentPosition == 50) HALT();
}
void loop() {
// Потенциально очень медленный код...
currentPosition = map(analogRead(linPot), 0, 1023, 0, 100);
// Определенно очень медленный код: занимает не менее 54 мс.
}
Это считывание показаний потенциометра в лучшем случае каждые 54 мс, и
потенциально гораздо медленнее. При каждом чтении currentPosition
обновлено. Параллельно проверяется значение currentPosition
.
каждые 125 мкс.
Обратите внимание, что на самом деле нет смысла тестировать currentPosition
, который
часто. После того как значение проверено, проверьте его еще раз, прежде чем оно будет
обновление не имеет никакой цели. Ваш код на самом деле будет немного быстрее
если вы проверите значение сразу после его обновления:
void loop() {
// Потенциально очень медленный код...
currentPosition = map(analogRead(linPot), 0, 1023, 0, 100);
if (currentPosition >= 50) HALT();
// Определенно очень медленный код: занимает не менее 54 мс.
}
Обратите внимание, что я заменил тест на равенство тестом на неравенство, причина объяснена в ответе Ника Гаммона.
У вас все еще остается проблема: цикл выполняется очень медленно. Если вы хотите потенциометр нужно проверять чаще, у вас есть несколько вариантов.
Плохой вариант: сделать это в обработчике прерывания:
// Запускается каждые 125 мкс. ISR(TIMER2_COMPA_vect) { currentPosition = map(analogRead(linPot), 0, 1023, 0, 100); if (currentPosition >= 50) HALT(); }
Это плохой вариант, поскольку
analogRead()
занимает как минимум 112 мкс, а это значит, что ISR съест 98,6% процессора циклы. Вы могли бы сделать эту работу более-менее разумной, если бы снизили частоту прерываний примерно до 1 кГц или ниже. Все еще, такой длинный ISR не является хорошей практикой.Лучший вариант: сделать цикл неблокирующим:
void loop() { // Неблокирующий, очень быстрый код. currentPosition = map(analogRead(linPot), 0, 1023, 0, 100); if (currentPosition >= 50) HALT(); // Еще очень быстрый код. }
Чтобы прочитать последовательный порт неблокирующим способом, см. Читаю сериал на Ардуино. Для записи в последовательный порт в неблокирующим способом, просто воздержитесь от частой печати.
Сложный вариант: настроить АЦП на непрерывное чтение.
Вы можете запрограммировать АЦП в «автономном режиме»: тогда он чтение каждые 104 мкс. Вы можете запрограммировать его на доставку прерываться каждый раз, когда доступно новое показание: вы можете протестировать чтение в этом обработчике прерываний. Это даст вам лучшее возможное время ответа:
void setup() { // Установите АЦП в режим автономной работы. // Включаем прерывание АЦП. } volatile int raw_adc_reading; // Срабатывает при каждом новом чтении АЦП. ISR(ADC_vect) { raw_adc_reading = ADC; if (raw_adc_reading >= RAW_THRESHOLD) STOP(); }
Обратите внимание, что я предпочитаю проверять необработанное чтение, а не преобразованное. один. Это связано с тем, что
map()
работает медленно (он включает в себя длинное деление). и в идеале ISR должен быть максимально быстрым.Вам придется покопаться в таблице данных MCU, чтобы узнать, как настроить АЦП в автономном режиме.
- Работа двигателя в течение 3 секунд непрерывно с прерыванием и без него
- Прерывания таймера Arduino для PID
- Проблема с прескалером таймера Elegoo Nano
- Arudino получает команду прерывания ДО перехода в спящий режим, из-за чего он не получает никаких команд прерывания для пробуждения.
- Как указать имя таймера в зависимости от чипа, в который он будет компилироваться?
- Не удалось получить показания для "Двигателя с энкодером"
- когда 3 компонента на Arduino nano работают вместе, один из них не работает
- Использование millis() и micros() внутри процедуры прерывания
как потенциометр подключен? ... пожалуйста, добавьте схему подключения к вашему вопросу, @jsotola
почему вы пытаетесь использовать прерывание в очень медленном событии? ... просто следите за потенциометром внутри блока
loop()
, @jsotolaУчитывая, что currentPosition обновляется в цикле(), возможно, очень медленно (поскольку Serial.parseInt() может блокироваться на целую секунду), нет смысла запускать прерывание только для проверки. его ценность., @Edgar Bonet
Сначала я пытался отслеживать положение внутри цикла void, и это тоже не сработало. Мне нужно, чтобы привод останавливался, когда потенциометр достигает определенного значения, которое я еще не определил (поэтому сейчас я просто пытаюсь заставить код остановиться на случайном значении, которое я выбираю). Я подключил кнопку к D2, чтобы иметь внешнее прерывание, которое останавливало привод при каждом нажатии кнопки. Итак, если у внешнего прерывания не было проблем с Serial.parseInt(), разве внутреннее прерывание не должно вести себя так же?, @wickedhurricane
Может быть, использовать Direct Port Manipulation для более быстрых операций GPIO? Это было бы быстрее, чем digitalWrite() и digitalRead()., @b1n3t