простой калькулятор на Arduino

Aleks_neofit
Aleks_neofit аватар
Offline
Зарегистрирован: 28.12.2016

Тема о том куда применить клавиатуру и дисплей от кассовых аппаратов.

Код взят с сайта http://radioprog.ru/post/133

Клавиатура с кассового аппарата ЭКР 2102 - верхняя часть кнопки снимается - в прозрачных можно самому рисовать символы.

#include <Keypad.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(7,8,9,10,11,12); // Выделить выводы для LCD дисплея

long num1,num2 ;
double total;
char operation,button;

const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  {'7','8','9','C6'},
  {'4','5','6','-'},
  {'1','2','3','+'},
  {'*','0','/','='}
};

byte rowPins[ROWS] = {A2,A3,A4,A5}; // подключение к строкам клавиатуры
byte colPins[COLS] = {2,3,4,5};     // подключение к столбцам клавиатуры
Keypad customKeypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// Этот код выполняется только один раз: при включении платы
void setup() 
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  lcd.begin(16,2);      // инициализация LCD
}

void loop()
{
  
  // Циклы удобны для чтения нажатых кнопок клавиатуры 
  while(1)
  {
    // Первый цикл. Здесь мы читаем клавиатуру и составляем наше первое число.
  // Он выполняется, пока мы не нажмем кнопку оператора, и цикл прервется,
  // или, если будет нажата кнопка 'C', всё начнется с начала. 
        
    button = customKeypad.getKey(); // Чтение кнопки
    if (button=='C') // Если пользователь хочет сбросить набор первого числа
    {
      num1=0;
      num2=0;
      total=0;
      operation=0;
      lcd.clear();
    }
        
    if (button >='0' && button <='9') // Если пользователь нажал на числовое значение, один символ за раз
    {
      num1 = num1*10 + (button -'0'); 
      // Наши числовые значения лежат в диапазоне от 0 до 9, что означает, что это единицы.
      // Когда мы умножаем на 10, мы, по сути, добавляем 0 после числа.
      // Затем мы добавляем новое введенное число на место нуля.
      // Что касается (button -'0'), это простой трюк с таблице ASCII. Цифры 0...9 в таблице ASCII
      // это 48...57 (в десятичном виде), поэтому, вычитание '0' из любого из этих символов дает
      // соответствующее значение в десятичном виде. Например, символ '5' = 53 в десятичной системе
      // минус 48 (символ нуля) дает нам значение 5.
      // Если наше предыдущее число было, например, 25, мы получили 250 умножением его на 10, 
      // а затем добавили 5, и получили 255, что и будет выведено на LCD.
      lcd.setCursor(0,0); // Выбор первой строки на LCD
      lcd.print(num1);    // Печать текущего значения числа num1
    }
    
  if (num1 !=0 && (button=='-' || button=='+' || button=='*' || button=='/')) 
    {
      // Если пользователь завершил ввод цифр
      operation = button;   // запоминаем, какое математическое действие пользователь хочет выполнить
      lcd.setCursor(0,1);   // установить курсор на строку 2
      lcd.print(operation); // напечатать наш оператор
      break;
    }

  }
    
  while(1) 
  {
    // Второй цикл, он выполняется пока пользователь не нажмет '=' или 'C'.
    // И тогда будет выведен результат или сброшена программа.
    if (button =='C')
      break; // Это обрабатывает случай, когда пользователь нажал кнопку оператора и захотел сбросить
    button = customKeypad.getKey();
    if (button=='C') // Еще раз проверяем, не хочет ли пользователь сбросить
    {
      num1=0;
      num2=0;
      total=0;
      operation=0;
      lcd.clear();
      break;
    }
    if (button >='0' && button <='9') // Получение символов от клавиатуры для числа 2
    {
      num2 = num2*10 + (button -'0');
      lcd.setCursor(1,1);
      lcd.print(num2);
    }
    if (button == '=' && num2 !=0)
    { // Если нажата кнопка '=', то это конец пути. Вызываем функцию domath(), выполняющую расчет
      // и выводим результат.      
      domath();             
      break;  
    }
  }
    
  while(1) 
  {
    // Когда всё выполнено, этот цикл ждет нажатия кнопки 'C', чтобы сбросить программу и начать с начала.
      
    // Это побочный эффект от предыдущего цикла, когда пользователь нажал 'C', предыдущий цикл прерывается
    // и переходит сюда. Поэтому мы должны также прервать и текущий цикл, иначе пользователю придется
    // нажимать 'C' дважды.      
    if (button =='C')
      break;

    button = customKeypad.getKey();
    if (button =='C') 
    {
      lcd.clear();
      lcd.setCursor(0,0);
      num1=0;
      num2=0;
      total=0;
      operation=0;
      break;
    }
  }

}


void domath()
{
  switch(operation)
  {
    case '+': // Сложение
      total = num1+num2;
      break;
      
    case '-': // Вычитание 
      total = num1-num2;
      break;
      
    case '/': // Деление
      // Может добавить ошибку деления на ноль, или измените строку во втором цикле,
      // где тот ожидает символа '=' на if (button == '=' && num2 != 0), это остановит программу
      // от дальнейших действий, пока num2 не будет отличаться от нуля.
      total = (float)num1/(float)num2;
      break;
      
    case '*': // Умножение
      total = num1*num2;
      break;
          
  }
    
  lcd.setCursor(0,1);
  lcd.print('=');
  lcd.setCursor(1,1);
  lcd.print(total);   
} 

Разьём клавиатуры: Столбцы: 7, 8, 9, 10, 11

Строки: 4, 3, 2, 1, 5, 6

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Aleks_neofit пишет:

Тема о том куда применить клавиатуру и дисплей от кассовых аппаратов.

Видимо, в ближайшем будущем следует ожидать темы о том, куда применить «простой калькулятор на Arduino»? :))))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Aleks_neofit,

а если серьёзно, то попробуйте операцию и второй операнд оставлять в одной строке. Тогда у Вас получится не

123 (уже исчезло с экрана)
+123
=246.00

а 

123+123
=246.00

(всё на экране)

По-моему, проект заметно выиграет.

lean_74
Offline
Зарегистрирован: 22.12.2015

У меня так просто два старых кассовых аппарата валяется целых без ЭКЛЗ, может кому надо?

Logik
Offline
Зарегистрирован: 05.08.2014

Калькулятор - отстой. делайте программируемый. Программу в EEPROM хранить.

valera1950
Offline
Зарегистрирован: 18.05.2017

 Я только приступил к изучению Arduino. Разбирался со сктчем. Оператор while()   { } В  круглых скобках согласно синтаксиса должно писаться условие выполнения инструкции, а в скетче проставлена цифра  while(1). Подскажите пожалуйста , что означает эта цифра, и где же условие. Если в {} , то это же инструкция. Часа 2 лазил по инету так и ни чего не нашел. Заранее спасибо, если кто ответит.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

valera1950 пишет:

 Я только приступил к изучению Arduino. Разбирался со сктчем. Оператор while()   { } В  круглых скобках согласно синтаксиса должно писаться условие выполнения инструкции, а в скетче проставлена цифра  while(1). Подскажите пожалуйста , что означает эта цифра, и где же условие. Если в {} , то это же инструкция. Часа 2 лазил по инету так и ни чего не нашел. Заранее спасибо, если кто ответит.

Число 1 и есть условие.

Вы знаете, что является результатом проверки любого условия? 1 - если истинно и 0 - если ложно

uncle_grin
Offline
Зарегистрирован: 26.04.2015

1 эквивалентно true,  в данном случае условие будет выполняться всегда. Лишняя команда в данном случае, скорее всего в оригинале при разработке что- то было, но автор упростил

valera1950
Offline
Зарегистрирован: 18.05.2017

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

kalapanga
Offline
Зарегистрирован: 23.10.2016

uncle_grin пишет:

1 эквивалентно true,  в данном случае условие будет выполняться всегда. Лишняя команда в данном случае, скорее всего в оригинале при разработке что- то было, но автор упростил

 

Что значит лишняя? Ничего другого там никогда не было и никто ничего не упрощал. while(1) {} это бесконечный цикл. Автор в нём ожидает нажатие определённых клавиш. Если нажата нужная клавиша, то выход из цикла по команде break. Насколько это красивое решение - мы не обсуждаем, но в самом этом цикле ничего криминального и ничего удивительного.

 

valera1950
Offline
Зарегистрирован: 18.05.2017

 Все стало понятно. Я то же не мог понять, зачем  команда break  нужна. Долго эксперементировал, подставляя эту команду в другие места, и за скобками и перед скбками. Теперь до конца все стало ясно. Просто я для для изучения Arduino поставил сам себе задачу сделать счетчик , что бы он считал общее количество и подавал сигнал  на предварительно устанавливаемые циклы. Допустим цикл 150, на счете 148 подается сигнал, на 150 он снимается, счетчик обнуляется и цикл начинается сначала. а общий счетчик не сбрасывается а продолжает суммарный подсчет.  Вот эту программу я использовал для ввода данных с клавиатуры. Такое устройство я делал в железе без контроллеров, а теперь пробую на современной базе. Большое спасибо за консультацию

uncle_grin
Offline
Зарегистрирован: 26.04.2015

kalapanga пишет:

uncle_grin пишет:

1 эквивалентно true,  в данном случае условие будет выполняться всегда. Лишняя команда в данном случае, скорее всего в оригинале при разработке что- то было, но автор упростил

 

Что значит лишняя? Ничего другого там никогда не было и никто ничего не упрощал. while(1) {} это бесконечный цикл. Автор в нём ожидает нажатие определённых клавиш. Если нажата нужная клавиша, то выход из цикла по команде break. Насколько это красивое решение - мы не обсуждаем, но в самом этом цикле ничего криминального и ничего удивительного.

 

Конечно ничего криминального в этом нет - каждый пишет как ему удобно, главное чтобы работало

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

valera1950 пишет:

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

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

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

valera1950
Offline
Зарегистрирован: 18.05.2017

 Я только взялся за освоение arduino, языка С++ не знаю, пользуюсь доступной литературой которая есть в интернете, ну и статьями на форумах.  Я приведу цитаты из двух книг, по которыми я пользуюсь как справочниками.

 Обшая форма цикла while имеет следующий вид:

while (условие) оператор;

Здесь оператор (тело цикла) может быть пустым оператором, единственным оператором или блоком. Условие (управляющее выражение) может быть любым допустимым в языке выражением. Условие считается истинным, если значение выражения не равно нулю, а оператор выполняется, если условие принимает значение ИСТИНА. Если условие принимает значение ЛОЖЬ, программа выходит из цикла и выполняется следующий за циклом оператор. ( это из книги Г. Шильд)

Цикл while — это цикл for, у которого удалены инициализирующая и  обновляющая части; в нем имеется только проверочное условие и тело:

while (проверочное_условие), тело ( Это из книги  Прата С.) 

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

valera1950, так всё правильно написано. Читайте внимательноее. Смотрите:

valera1950 пишет:

Условие (управляющее выражение) может быть любым допустимым в языке выражением. Условие считается истинным, если значение выражения не равно нулю, а оператор выполняется, если условие принимает значение ИСТИНА. Если условие принимает значение ЛОЖЬ, программа выходит из цикла и выполняется следующий за циклом оператор. ( это из книги Г. Шильд)

Все чётко. 1 - допустимое в языке выражение? Да. Оно равно 0? Нет. Значит это истина.

// Практически синонимы
while (1) ...
while (true) ...
while (2 > 1) ...
while (1+1) ...
while (2 == 2) ...
while (a = 2) ...

Для последнего нужно, чтобы a было ранее описано.

valera1950
Offline
Зарегистрирован: 18.05.2017

 Допустим

while  (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))

    если я правильно вас понял должно выполниться арифметическое выражение в скобках,  и затем будет выполняться вычисление или преобразование  до тех пор пока выражение в скобках не станет логическим значением  ЛОЖНО. Только тогда этот оператор заканчивает работу, и переходит к следующему оператору.  ???

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

valera1950 пишет:

 Допустим

while  (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))

    если я правильно вас понял должно выполниться арифметическое выражение в скобках,  и затем будет выполняться вычисление или преобразование  до тех пор пока выражение в скобках не станет логическим значением  ЛОЖНО. Только тогда этот оператор заканчивает работу, и переходит к следующему оператору.  ???

Нет, конечно. Баланс скобок-то соблюдать надо. После while должно идти выражение в скобках, а после оператор или блок. У Вас же после while выражение без скобок.

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

while (a) b;

Где а - любое допустимое в языке выражение, которое имеет результат и допускает преобразование этого результата к логическому типу. b - просто любая допустимая операция или блок.

Сначала вычисляется a, и если оно истина, выичсляется b, и процесс повторяется до тех пор, пока а не станет ложным.

А в Вашем выражении, что является а, и что - b?

-------------

А вообще, заведите полезную привычку, если есть вопрос, то прежде, чем лезть на форум, запустите и попробуйте сами. До тех пор, пока Вы не научитесь задавать вопросы компьютеру, Вы не научитесь ничему.

valera1950
Offline
Зарегистрирован: 18.05.2017

 извините, я просто не так написал, вот сейчас в соответствии с синтаксисом white.

                                          (a)                                               (b)

    while (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3)) { здесь инструкция, операция, блок...} 

в круглых скобках это то же арифметическое выражение, которое используется в языке. В результате вычисления здесь получается какой то  результат,  и чем оно отличается от       while (1+1)...?. 

Вы извините,я просто хочу все до конца понять.  пусть выражение в круглых скобках будет   (2 * к + 3), или это должно быть только логическим выражением. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

valera1950 пишет:

                                          (a)                                               (b)

    while (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3)) { здесь инструкция, операция, блок...} 

в круглых скобках это то же арифметическое выражение, которое используется в языке. В результате вычисления здесь получается какой то  результат,  и чем оно отличается от       while (1+1)...?. 

Ну, я же Вам писал, что баланс скобок надо соблюдать. Выражение "а" должно быть в скобках.

Посчитайте скобки в Вашем выражении и убедитесь, что "а" у Вас это "t * sin(x)-1.05e4". Оно в скобках и это правильно. Далее должно идти выражение b, а у Вас почему-то идёт знак / - а это синтиксически неверно.

Вы пробовали это запустить? Почему не пробовали?

valera1950 пишет:

пусть выражение в круглых скобках будет   (2 * к + 3), 

Ну, пусть будет, кто ему мешает? Совершенно нормальное выражение, если k предварительно было описано. Выражение будет вычислено. Если результат 0, то будет считаться ложью, если не 0, то истиной.

valera1950 пишет:

или это должно быть только логическим выражением. 

Оно не должно быть логическим, одно должно предполагать возможность преобразования к логическому. Т.е. попросту его должно быть возможно сравнить с 0. Если равно - ложь, не равно - истина. Вот и всё. Там даже переменные описывать можно. Например:

while (int k = n) { 
   // ...
}

вполне законная конструкция (если n раньше была описана, конечно). Область видимости переменной k - до конца цикла.

 

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

видимо так правильно?

while ((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) { здесь инструкция, операция, блок...}

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ua6em пишет:

видимо так правильно?

while ((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) { здесь инструкция, операция, блок...}

Формально правильно, но реально тут есть засада. Такая конструкция предполагает сравнение выражения

((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) 

с нулём (если равно - ложь, не равно - иститна).

Но это выражение даёт резальтат типа double. А точное сравнение чисел с плавающей точкой на равенство - игра в рулетку. Он может быть не равно 0 в каком-нибудь 10-ом знаке после запятой (а ещё лучше - в таком знаке, который присутствует, но никак не гарантируется точностью вычислений). Поэтому для плавающих чисел лучше сравнивать не на точное равенство, а с некоторой "допустимой погрешностью". Например, так будет более адекватно

while (fabs((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) < 1e-6)

например.

valera1950
Offline
Зарегистрирован: 18.05.2017

Большое спасибо, прктически понял все. Скобки я пропустил. Сейчас в уме разложу все по полочкам, тогда приду к окончательному решению, понял или остались еще непонятные вопросы. Хорошо, что откликнулись на  вопросы, так практически можно изучить все. Еще раз спасибо.!!!

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

valera1950 пишет:

Вот из этих книг, я понял, что обязательно должно быть условие. 

Из приведенной Вами цитаты видно, что:

1. Под "условием" автор подразумевает именно выражение.

2. Что это "условие" может принимать значения либо ИСТИНА либо ЛОЖЬ. "1" - это ИСТИНА, следовательно, "1" с точки зрения автора - это именно "условие".

Т.е. понятно, что под "условием" автор понимает нечто отличающееся от бытового представлении о том, что такое условие. И пояснил, что именно.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

valera1950 пишет:

Вы извините,я просто хочу все до конца понять.  пусть выражение в круглых скобках будет   (2 * к + 3), или это должно быть только логическим выражением. 

Особенностью языка Си является то, что в нем нет логического типа. Вместо него используется обычный целочисленный. При этом 0 трактуется как ЛОЖЬ, а любое другое значение как ИСТИНА.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

ЕвгенийП пишет:

ua6em пишет:

видимо так правильно?

while ((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) { здесь инструкция, операция, блок...}

Формально правильно, но реально тут есть засада. Такая конструкция предполагает сравнение выражения

((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) 

с нулём (если равно - ложь, не равно - иститна).

Но это выражение даёт резальтат типа double. А точное сравнение чисел с плавающей точкой на равенство - игра в рулетку. Он может быть не равно 0 в каком-нибудь 10-ом знаке после запятой (а ещё лучше - в таком знаке, который присутствует, но никак не гарантируется точностью вычислений). Поэтому для плавающих чисел лучше сравнивать не на точное равенство, а с некоторой "допустимой погрешностью". Например, так будет более адекватно

while (fabs((t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) < 1e-6)

например.

а так?
while (unsigned int a = (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) { здесь инструкция, операция, блок...}

valera1950
Offline
Зарегистрирован: 18.05.2017

 Да, теперь все понятно. Как только в теле инструкции, или оператора, что то меняется, то это приводит к изменению условия. ИСТИНА меняется на ЛОЖЬ.  В прведенной программе калькулятора в первом цикле  изменения могут быть вызваны только изменениями состояния кнопок, и ни чем  другим. Это проверяется оператором if  Если условие меняется на ЛОЖЬ, то цикл не выполняется. Если это делается так как я написал, значит вопросов у меня по этому поводу уже не возникнет. Если не, то буду разбираться еще. А с выражениями мне все ясно. Просто не все внимательно прочитал. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

andriano пишет:

Особенностью языка Си является то, что в нем нет логического типа. 

Беда только в том, в Ардуино используется не С, а С++ (в .ino файлах).

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ua6em пишет:

а так?

while (unsigned int a = (t * sin(x)-1.05e4)/((2 * к + 2) * (2 * к + 3))) { здесь инструкция, операция, блок...}

Ну, скобки мне влом считать, а так идейно - пожалуйста, если Вам не жалко, что дробная часть будет отброшена. Вы же знаете, что если присваиваете целому числу плавающее, дробная часть отбрасывается даже если она .9999.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

valera1950, смотрите, как изящно можно строки копировать 

while (*dst++=*src++) ;

и без единого оператора в цикле