Реверсивный счётчик

Рабочие проекты Ардуино
Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Реверсивный счётчик

Сообщение Михаил_Я » 13 дек 2017, 23:32

Имею неплохой опыт в электронике, но к освоению программирования только приступаю.
Встала задача изготовить реверсивный 6-разрядный десятичный счётчик. На дискретных элементах сделать можно, но будет громоздко.
Программный метод предпочтительнее, но проблема в том, что я не знаю, с какой стороны подступиться.

Вот предварительные прикидки:

( период счётных импульсов ок. 3,5mS)

1. Назначаем:
переменную unsigned long x=0;
PinA -счётные импульсы;
PinB — направление счёта.

2. PinA запускает прерывание (1mS?) по RISING.
При изменении состояния счётчика прерываний:
если PinB=HIGH,то х=х+1
если PinB=LOW, то х=х-1

3. Отображение х на дисплее.

Пока буду использовать LCD1602.


Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 14 дек 2017, 01:15

Здравствуйте!
Я не совсем понял. Вам надо считать положительные фронты на выводе? Т.е. если вывод изменил свое состояние из 0 в 1, то надо прибавить или вычесть 1 в зависимости от состояния другого вывода.
Минимальный период этих импульсов 3,5 мс.
Откуда поступают эти импульсы? Есть ли дребезг?

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 14 дек 2017, 09:47

Я перенесу эту тему в раздел "Проекты Ардуино". Речь идет о рабочем проекте, а не о принципах программирования.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 14 дек 2017, 09:49

Здравствуйте!
Вы всё поняли правильно. Период импульсов приблизительно 3,5 мс, при необходимости его можно в некоторых пределах изменить. Эти импульсы управляют работой шагового мотора, по их количеству определяется положение его ротора. Подаются от генератора на NE555, дребезга нет.
PS: мотор работает не постоянно, поэтому импульсы поступают пачками.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 14 дек 2017, 09:53

С переносом темы согласен, проект действительно практический.

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 14 дек 2017, 10:36

У меня нет возможности проверить программу. Поэтому могут быть ошибки. Программу буду выдавать последовательно, этапами. Проверяйте тщательно каждый этап.
Первый вариант просто считает импульсы в положительном направлении и выводит состояние счетчика на компьютер. Так как в программу планируется добавить функции с достаточно большим временем выполнения, например, вывод на LCD дисплей, то подсчет импульсов должен быть в прерывании. Проверяйте. Может, дальше сами допишите.

Код: Выделить всё

#include <TimerOne.h>

#define PIN_IN  2 // вход счетчика импульсов ( _- )

long  counter=0;  // счетчик импульсов
boolean prevInState= false;    // предыдущее состояние входа
boolean state;

void setup() {
  pinMode(PIN_IN, INPUT);  // вход счетчика импульсов
  Serial.begin(9600); // инициализируем порт, скорость 9600
  Timer1.initialize(500);  // инициализация таймера 1, период 500 мкс
  Timer1.attachInterrupt(timerInterrupt, 500);  // задаем обработчик прерываний}
}

void loop() {

  // вывод счетчика на компьютер
  Serial.println(counter);
  delay(500);

}

// обработчик прерывания 500 мкс
void  timerInterrupt() {

  // выделение положительного фронта на входе
  state= digitalRead(PIN_IN);
  if ( (state == HIGH) && (prevInState == LOW) ) {
    // если сейчас состояние высокое, а было низкое
    counter++;  // +1 к счетчику импульсов
  }
  prevInState= state;
     
}


Остается добавить реверсивный счет, кнопку сброса счетчика и вывод на дисплей.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 14 дек 2017, 11:32

Спасибо, Эдуард!
Сейчас буду пробовать. В данный момент я нахожусь на работе, поэтому потребуется некоторое время.

Что меня смутило в скетче - использование delay. Поскольку время прихода пачки импульсов заранее неизвестно, часть из них может быть пропущена.

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 14 дек 2017, 12:37

delay() только для отладки. Чтобы на экране компьютера данные слишком часто не мелькали. Как раз это проверка того, что счет ведется параллельным процессом. Ничего пропускаться не должно.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 14 дек 2017, 22:41

Не понял - как счёт может вестись параллельным процессом? По моим представлениям delay - это пауза в работе контроллера.
Но вот практические результаты:
сначала я считал одиночные импульсы длительностью 1мс. Формировал их от кнопки с помощью RS-триггера и одновибратора. Считались не все импульсы. Чтобы определить, когда они не считаются, увеличил время delay(5000), период прерывания менял до 5000мкс.Некоторые импульсы считались во время delay, некоторые (вероятно) в момент прерывания. "Вероятно" - потому, что трудно попасть в интервал 5мс. Но когда я делал несколько нажатий на кнопку в первые 3 сек. после смены показаний на мониторе,количество сосчитанных импульсов было меньше, чем при том же количестве нажатий в течении большего времени - до смены показаний включительно.
Тогда я вернул исходные настройки (500мс и 500мкс) и подал на вход тактовую частоту от генератора, управляющего ШД ( Т~3,5мс).
Результат меня шокировал. При периоде импульсов 3,5 мс за 5 минут их подаётся 85714, а счётчик отобразил только 7534. Разница - больше, чем на порядок! Не исключаю, что это следствие большой скважности импульсов, пауза между ними гораздо меньше их длительности. Для работы моей схемы не принципиально, просто так получилось. Не понимаю как, но, возможно, это влияет на работу счётчика. Завтра изменю скважность и сообщу результат.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 14 дек 2017, 23:32

Эдуард, Вы пишете:" Так как в программу планируется добавить функции с достаточно большим временем выполнения, например, вывод на LCD дисплей, то подсчет импульсов должен быть в прерывании." Может, в этом и есть "корень зла"? Импульсы могут приходить в любое время.
Мне (может, по незнанию) в голову пришли 2 варианта решения этой проблемы:
1. Использовать 2 "Ардуинки" - одна только считает и передаёт данные на вторую, а вторая выводит их на дисплей. Но я не знаю, не помешает ли передача счёту ВСЕХ импульсов? Мне в данном случае важно не пропустить ни одного, поскольку счётчик будет использоваться в высокоточном приборе. Возможно, в этом случае прерывания вообще не понадобятся - достаточно считать фронты или спады. Или они понадобятся только для передачи данных и будут короче 3,5 мс.
2. Использовать 16-тиразрядный двоичный счётчик на дискретных ИМС, а контроллер использовать только для вывода данных на дисплей в десятичном виде.

Это, конечно, получится более громоздко, но цель - точность - в данном случае важнее. И, конечно, первый из двух вариантов мне интересней - меньше "железа", меньше габариты, а главное - позволит хоть на шаг продвинуться в программировании.

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 14 дек 2017, 23:51

Добрый вечер!
Что-то у вас не так с импульсами. Я проверил программу. Я соединил 2 и 3 выводы платы. 2й вывод используется как вход счетчика, а 3й я использую для имитации импульсов. Я добавил в программу в прерывание по таймеру блок, который вырабатывает импульсы с периодом 4 мс и длительностью 1 мс. Вот код.

Код: Выделить всё

#include <TimerOne.h>

#define PIN_IN  2 // вход счетчика импульсов ( _- )

long  counter=0;  // счетчик импульсов
boolean prevInState= false;    // предыдущее состояние входа
boolean state;

byte x=0;

void setup() {
  pinMode(PIN_IN, INPUT);  // вход счетчика импульсов
  pinMode(3, OUTPUT);  // вход счетчика импульсов
  Serial.begin(9600); // инициализируем порт, скорость 9600
  Timer1.initialize(500);  // инициализация таймера 1, период 500 мкс
  Timer1.attachInterrupt(timerInterrupt, 500);  // задаем обработчик прерываний}
}

void loop() {

  // вывод счетчика на компьютер
  Serial.println(counter);
  delay(500);

}

// обработчик прерывания 500 мкс
void  timerInterrupt() {

  // выделение положительного фронта на входе
  state= digitalRead(PIN_IN);
  if ( (state == HIGH) && (prevInState == LOW) ) {
    // если сейчас состояние высокое, а было низкое
    counter++;  // +1 к счетчику импульсов
  }
  prevInState= state;

  // имитация импульсов (_-___) 4 мс период, 1 мс длительность
  x++;
  x &= 7;
  if(x == 0) digitalWrite(3, HIGH);
  if(x == 2) digitalWrite(3, LOW);   
}


Запустил монитор последовательного порта. Каждая строчка увеличивается на число 125. Это 500 мс / 4 мс. Все работает правильно.
Программа написана так, что при любых задержках в основном цикле программы импульсы пропускаться не должны. В том числе и при работе с LCD дисплеем. Никаких вторых плат не надо. Единственное условие - нельзя запрещать прерывания.
И еще. Длительность входных импульсов должна быть более периода прерывания. Вы можете уменьшить период прерывания.

Timer1.initialize(250); // инициализация таймера 1, период 250 мкс
Timer1.attachInterrupt(timerInterrupt, 250); // задаем обработчик прерываний

Еще раз повторю - у вас что-то не так с импульсами. Может они короче 1 мс. Если возможно, посмотрите осциллографом.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 00:35

Эдуард, спасибо!
"Железо" всё на работе, поэтому пробовать буду завтра. Импульсы, естественно, смотрел осциллографом. Возможный "косяк" не в длительности импульса (он занимает почти все 3,5 мс), а в длительности паузы. Она очень мала, и спад одного импульса находится почти вплотную с фронтом следующего. Я даже замерить не смог: уменьшаю время развёртки - вторая пауза не захватывается. Грубо - около 0,2мс.

Небольшое сомнение (если я неправ ,то поправьте меня):
delay приостанавливает ВСЕ процессы в контроллере, генерацию сигнала - в том числе. Может, каждая строчка увеличивается на 125 именно поэтому? Счёта нет тогда же, когда нет и импульса?

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 15 дек 2017, 00:51

Да. При такой паузе работать не будет. Пауза должна быть не менее времени периода прерывания. Уменьшите период прерывания. Если надо, то до 100-150 мкс.
Что касается delay. Функция приостанавливает выполнение основного цикла программы, но не останавливает прерывания. То, что за каждый вывод информации в последовательный порт число увеличивается на 125 показывает, что за 500 мс было подсчитано 125 импульсов. Так и должно быть.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 01:01

Правильно ли я понял, что время прерывания не входит в период измерения? Что измерения проходят без паузы - 500мс+500мс+500мс и т.д.?

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 01:04

Спасибо, завтра поиграю со скважностью.
А сейчас - извините, я - спать!

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 15 дек 2017, 01:32

Да, конечно. Происходит сканирование сигнала каждые 500 мкс (период прерывания). И надо, чтобы период сканирования был меньше, чем длительность импульса или паузы.

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 15 дек 2017, 09:41

Здравствуйте!
Можете попробовать с внешним прерыванием.

Код: Выделить всё

#define PIN_IN  2 // вход счетчика импульсов ( _- )

volatile long  counter=0;  // счетчик импульсов

void setup() {
  attachInterrupt(0, inInterrupt, RISING);
  Serial.begin(9600); // инициализируем порт, скорость 9600
}

void loop() {

  // вывод счетчика на компьютер
  Serial.println(counter);
  delay(500);

}

// обработчик прерывания
void  inInterrupt() {
    counter++;  // +1 к счетчику импульсов     
}


Программа будет не такой гибкой, асинхронность появится. Но в вашем случае вполне допустимо, программа небольшая. Пропускать импульсы не будет даже при коротких паузах.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 16:34

Здравствуйте!
Поменял скважность (~1,5) - счётчик заработал. После этого подключил LCD, не с первой попытки, но удалось.
Потом попытался сделать реверс. Получилось что-то очень странное. Счётчик вроде работает, но при переходе через "0" откуда-то появляется ещё один разряд с правой стороны. Причём этот разряд до какого-то момента не действует, висит балластом, а показания отражаются слева от него. Потом вдруг начинает работать. Короче, полные непонятки! Вот получившийся скетч:

Код: Выделить всё

#include <TimerOne.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define PIN_IN  2 // вход счетчика импульсов ( _- )
#define PIN_DIRECT  7 //вход направления счёта
LiquidCrystal_I2C lcd(0x3F,16,2); // назначаем адрес LCD 0x3F и его размер

long  counter=0;  // счетчик импульсов
boolean prevInState= false;    // предыдущее состояние входа
boolean state;
boolean dir;
void  timerInterrupt() {

  state= digitalRead(PIN_IN);
  dir= digitalRead(PIN_DIRECT);
 
   // выделение положительного фронта на входе
  if ( (state == HIGH) && (prevInState == LOW)&&(dir == HIGH  )) {
    // если сейчас состояние высокое, а было низкое
    counter++;  // +1 к счетчику импульсов
  }
else if ( (state == HIGH) && (prevInState == LOW)&&(dir == LOW )){
      counter--;
  }
  prevInState= state;
}
void setup() {
  pinMode(PIN_IN, INPUT); // вход счетчика импульсов
  pinMode(PIN_DIRECT, INPUT); //вход направления счёта
  lcd.init();                      // инициируем lcd
  lcd.backlight();
 
  lcd.print("Hello!");
  delay(1500);
 
  Timer1.initialize(500);  // инициализация таймера 1, период 500 мкс
  Timer1.attachInterrupt(timerInterrupt, 500);  // задаем обработчик прерываний}
  }

void loop() {
   lcd.setCursor(0,0);
   lcd.print("Weight");
   
  lcd.setCursor(7,1);
     lcd.print(counter);
     
    delay(1000);
   }


Помогите, пожалуйста, разобраться!

PS:Ваш сегодняшний скетч буду пробовать завтра - рабочий день заканчивается, а таскать "железо" туда-обратно неудобно.

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 15 дек 2017, 22:47

Здравствуйте!
Оператор lcd.print(counter); может выводить на дисплей числа разной длины в зависимости от значения числа. Например, 50 и 5000. Если у вас выводилось длинное число, а потом стало короткое, то лишние цифры будут оставаться на дисплее. Надо стереть все поле перед тем как выводить число.

Код: Выделить всё

lcd.setCursor(7,1);
lcd.print("        ");  // стереть нужное количество знакомест
lcd.setCursor(7,1);
lcd.print(counter); 

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 23:04

Эдуард, спасибо огромное! В принципе, ясно. Только уточните, пожалуйста - стирать всё поле от(7,1) до (15,1) или только "лишние" знакоместа?
И ещё: на Ваш опытный взгляд - в скетче всё более-менее или нагромождено что-то лишнее?

PS: Насчёт "буду пробовать завтра" я погорячился, забыл, что впереди выходные))).

Эдуард
Администратор
Сообщения: 365
Зарегистрирован: 30 окт 2016, 20:53

Re: Реверсивный счётчик

Сообщение Эдуард » 15 дек 2017, 23:09

Лучше стирать все поле, которое вы отвели на число. А с программой, может, попробуете последний код. И его будете дорабатывать. Если у вас могут быть такие короткие состояния сигнала, лучше использовать внешнее прерывание.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 15 дек 2017, 23:22

Понятно!
Последний код я, в любом случае, попробую, но короткие состояния (я уже писал об этом) получились стихийно. Работе моего устройства они не мешали, поэтому оставил всё как есть. Теперь изменил скважность и их нет. Но варианты счётчика хочу попробовать разные. Как обычно, во время еды пришёл аппетит - захотелось добавить в счётчик дополнительные функции.

Михаил_Я
Сообщения: 36
Зарегистрирован: 13 дек 2017, 23:18

Re: Реверсивный счётчик

Сообщение Михаил_Я » 18 дек 2017, 21:38

Эдуард,огромное спасибо за помощь! Благодаря вашим подсказкам мне удалось написать полностью рабочий скетч, решающий мою задачу. Самую последнюю функцию счётчика (подсчёт отклонения) мне помогли написать коллеги, но основной скелет программы и направление задали Вы.
На случай, если вдруг кому-то пригодится, выкладываю код полностью. Его функционал:
1. Подсчёт импульсов в прямом и обратном направлениях.
2. Сброс счётчика на "0".
3. Фиксация показаний в определённый момент и подсчёт отклонения от этой величины.
4. Отображение всех результатов счёта на дисплее.


Код: Выделить всё

#include <TimerOne.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define PIN_IN  2 // вход счетчика импульсов ( _- )
#define PIN_DIRECT  7 //вход направления счёта
#define PIN_RES  4 // вход "сброс"
#define PIN_DIFF 8 //вход "отклонение"
LiquidCrystal_I2C lcd(0x3F,16,2); // назначаем адрес LCD 0x3F и его разрядность




long  diffcoun;  //счётчик отклонений
long  counter=0;  // счетчик импульсов
boolean prevInState = false;    // предыдущее состояние счётного входа
boolean state;  // текущее состояние счётного входа
boolean res;    // состояние входа "сброс"
boolean dir;    //  состояние входа "направление счёта"
boolean diff;   //  состояние входа "отклонение"


void  timerInterrupt() {
  state= digitalRead(PIN_IN);
  dir= digitalRead(PIN_DIRECT);
  res= digitalRead(PIN_RES);
  diff=digitalRead(PIN_DIFF);

   // выделение положительного фронта на входе и выбор направления счёта
   if ( (state == HIGH) && (prevInState == LOW)&&(dir == HIGH  )) {
    // если сейчас состояние высокое, а было низкое
    counter++;  // +1 к счетчику импульсов
  }
else if ( (state == HIGH) && (prevInState == LOW)&&(dir == LOW )){
      counter--;  //-1 от счётчика импульсов
  }
  prevInState = state;

   // сброс счётчика
  if (res == LOW)
{
    counter=0;
  }}


void setup() {
  // активация встроенных подтягивающих резисторов
  pinMode(PIN_DIRECT,INPUT_PULLUP);
  pinMode(PIN_RES,INPUT_PULLUP);
  pinMode(PIN_DIFF,INPUT_PULLUP);



  lcd.init();                      // инициируем lcd
  lcd.backlight();
 
  lcd.print("Hello!");
  delay(1000);
 
  Timer1.initialize(500);  // инициализация таймера 1, период 500 мкс
  Timer1.attachInterrupt(timerInterrupt, 500);  // задаем обработчик прерываний}
 
 
}

long current=0;

bool trigger=true;

void loop() {

   lcd.setCursor(0,0);
   lcd.print("Weight");
   lcd.setCursor(0,1);
   lcd.print("Deviation");
 
  if(!digitalRead(PIN_DIFF)==true&&trigger==true)
  {
current=counter;
counter=0;
  trigger=false; 
  }
  if(!digitalRead(PIN_DIFF)==false)
  {
  trigger=true; 
  }
 
 
    lcd.setCursor(10,0);
     lcd.print("          ");
     lcd.setCursor(10,0);
     lcd.print(current);
     lcd.setCursor(10,1);
     lcd.print("      ");
     lcd.setCursor(10,1);
     lcd.print(counter);
     
    delay(1000);
   }


PS: счётчик с внешним прерыванием сегодня опробовать не успел, завтра обязательно займусь. Возможно, скетч будет проще, меньше и надёжнее (хотя пока что и у этого проблем с надёжностью не выявилось).


Вернуться в «Проекты Ардуино»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 0 гостей