Правильное выполнение арифметических операций?

Обсуждаем контроллеры компании Atmel.
Ответить
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1455
Зарегистрирован: Ср сен 03, 2008 21:42:17
Откуда: Ленинградская область,пос.Красный Остров

Сообщение WatchCat »

ну, это, как я понимаю, просто традиция неприятия printf...
Скорее - сложности в использовании printf с индикаторами,не являющимися последовательными терминалами. Чтобы туда делать printf - придется сначала написать эмулятор терминала для такого индикатора - как минимум в самом базовом варианте. Что должен делать индикатор если выводимая строка длиннее чем его количество символов? Как позиционировать вывод в опеределенное место - обработку esc-последовательностей что ли писать? Если же использовать sprintf в массив-строку,а потом эту строку выводить своими функциями на индикатор - то возможности sprintf чаще всего очень сильно избыточны,а места она занимает в памяти много.
Поэтому обычно проще использовать itoa() или свой упрощенный и уменьшенный аналог, и дальше опять же свою функцию вывода на индикатор,умеющую выводить сразу в нужное его место. А если индикатор графический - то еще и рисовать указанным шрифтом.
Реклама
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18677
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

WatchCat писал(а):придется сначала написать эмулятор терминала для такого индикатора - как минимум в самом базовом варианте
все уже написано до нас :))) почти все используют готовые библиотеки поддержки ЖКИ, в которых есть готовые функции вывода символа на дисплей, и максимум, что еще будет нужно, добавить обработку символов перевода строки и возврата каретки, другой эмуляции будет не нужно.

а, например, библиотечка от Peter Fleury для символьных ЖКИ, уже умеет всё, что надо: и строки переводит автоматически, и символы обрабатывает.
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Реклама
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1455
Зарегистрирован: Ср сен 03, 2008 21:42:17
Откуда: Ленинградская область,пос.Красный Остров

Сообщение WatchCat »

готовые библиотеки поддержки ЖКИ, в которых есть готовые функции вывода символа на дисплей
Да, это так,но в этих библиотеках обычно нет обработки даже символа возврата каретки,не говоря о чем-то еще.
Типично это функция вывода символа в заданную позицию,и написанная на ее основе функция вывода строки начиная с заданной позиции. Причем кусок который на индикатор не влез - чаще всего просто обрезается.
и максимум, что еще будет нужно, добавить обработку символов перевода строки и возврата каретки
Именно об этом я и говорил. А кроме перевода строки и возврата каретки вполне востребована инверсия и мигание.
Если мы пишем свою функцию - то и позицию и признаки инверсии/мигания мы можем просто передать ей в качестве аргументов.
Если мы используем printf то это придется сначала закодировать в строку в виде спецсимволов,а потом обратно эту строку разбирать.
Потому что printf был ориентирован на последовательный вывод данных в терминал,а разбором и визуальной интерпретацией этого потока занимался уже он. В случае же МК и ЖКИ придется реализовывать эту интеллектуальную составляющую терминала в самом устройстве. И я не вижу чем тут подход с printf лучше чем использование itoa() и либо самописных либо библиотечных (если есть) функций рисования символов на индикаторе.
Вот если выводим информацию в uart и ее отображением занимается терминальная программа на компе - тогда да, можно и printf.
Хотя проще sprintf в строковый буфер и потом uart_puts(кстати - у меня он библиотечный). Да,существует способ привязать stdout к последовательному порту и тогда будет работать printf,но это требует дополнительных телодвижений.
другой эмуляции будет не нужно.
Спорное утверждение. Если это например распространенный у китайцев графический индикатор от Нокии - то и позиционирование надо и шрифты.
например, библиотечка от Peter Fleury для символьных ЖКИ, уже умеет всё, что надо
[/quote]
А у этой библиотеки существует версия новее 2003 года? Я вот не находил. Хотя у меня интернет медленный уж очень.
Соответственно, для всех индикаторов которые появились за прошедшие два десятка лет - придется решать вопросы совместимости.
А двадцать лет это не два-три года,ни на что особо не влияющие.
В результате получается что если вывод чего-то на индикатор является в устройстве функцией второстепенной - то да,можно поставить что-то старое и простое,о чем библиотека знает. Но в этом случае и каких-то особых требований к форматированию вывода нет.
Если же интерфейс с пользователем является важной составляющий функциональности устройства - то нет смысла пытаться выжать невозможное из примитивного индикатора,лучше сразу ориентироваться на более продвинутые модели. А тогда потребуется и цвет и шрифты - в результате если продолжать хотеть использовать printf то придется написать вполне себе полноценный эмулятор терминала.
Ну и в подтверждение моих слов - всё же в графических программах под Xorg вывод текста на экран не через printf обычно делается. Соответственно,чем ближе мы подходим к полноэкранному интерфейсу,особенно если с элементами [псевдо] графики - тем менее удобен printf и больше усилий на написание разбора того что он навыводил в эмуляторе терминала.

Вот только что прикручивал к своей программе библиотеку microrl (micro readline) для работы через uart с компом и обнаружил что автор не учел разного поведения эмуляторов терминалов при выдаче esc-кода возврата курсора на N позиций назад. Если число позиций возврата больше чем расстояние от начала текущей строки - то линуксовая текстовая консоль оставляет курсор в первой позиции. А xterm переводит его на предидущую строку и возвращает на оставшееся количество позиций. Хорошо что это вылезло еще в процессе экспериментов с библиотекой,собранной под линуксом,до запихивания ее в прошивку МК.
Я вообще стараюсь по возможности погонять куски кода под линуксом и/или в симуляторе прежде чем на МК их переносить. Меньше возиться с отладкой непосредственно на МК приходится. Всё же я не настолько крут чтобы сразу писать безошибочный код.
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18677
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

WatchCat писал(а):А кроме перевода строки и возврата каретки вполне востребована инверсия и мигание.
ничто не мешает реализовать это, минуя printf
WatchCat писал(а):а потом обратно эту строку разбирать.
нет, разбирать не придется, printf будет отдавать посимвольно строку и можно элементарно отслеживать все нужные последовательности - тут писанины не больше, чем на форуме с объяснениями, как это сложно :)))
WatchCat писал(а):И я не вижу чем тут подход с printf лучше чем использование itoa() и либо самописных либо библиотечных (если есть) функций рисования символов на индикаторе.
как я уже говорил, смысл в том, чтобы самому меньше писать. например, чтобы собрать строку для вывода на любой дисплей, надо обязательно делать так, чтобы строка всегда была одной длины, дополняя её пробелами при необходимости, чтобы всегда удалялась предыдущая информация. попробуйте это написать отдельными функциями - будет такой огород... а printf делает это двумя символами формата строки... причем, стоит поменять на пару символов дизайн, и весь огород придется перепахивать... ну а printf потребует правки только строки формата.

если такие сложности не пугают - я не возражаю, я вот очень ленивый...
WatchCat писал(а):то и позиционирование надо и шрифты
шрифты всегда уже есть в "готовой либе", а позиционирование делается - см. выше - внутри функции вывода символа очень простыми средствами. для текстового дисплея (даже на основе графического) других возможностей (типа вывода не по строкам, а наперекосяк или с полустрочным интервалом) не нужно.
WatchCat писал(а):А у этой библиотеки существует версия новее 2003 года?
в этой библиотеке я нашел одну ошибку, которая проявляется на дисплеях с шириной строки больше 16, ошибка эта исправляется элементарно... а других там нет. и, как по мне, не нужно.
WatchCat писал(а):для всех индикаторов которые появились за прошедшие два десятка лет - придется решать вопросы совместимости
эти вопросы решены на уровне контроллеров - они все так или иначе совместимы с "дедушками" 20-летней давности
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Реклама
Эиком - электронные компоненты и радиодетали
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="WatchCat",url="/forum/viewtopic.php?p=4203589#p4203589"]Именно об этом я и говорил. А кроме перевода строки и возврата каретки вполне востребована инверсия и мигание. Если мы пишем свою функцию - то и позицию и признаки инверсии/мигания мы можем просто передать ей в качестве аргументов.
Если мы используем printf то это придется сначала закодировать в строку в виде спецсимволов,а потом обратно эту строку разбирать.[/uquote]
Очень сомневаюсь, что когда кому-то нужно вывести текст в инверсии он использует для этого спецсимволы, например, для графических дисплеев можно просто поменять цвета фона и символов после чего выводи что хочешь и как угодно.
Реклама
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

[uquote="ARV",url="/forum/viewtopic.php?p=4202386#p4202386"]самое важное - не надо делать все эти 32-битные умножения-деления в обработчике прерываний, и тогда на их быстродействие будет наплевать.[/uquote]

Я делал вот такие жуткие конструкции в обработчике прерывания Atmega168.

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

// Read and convert ADC value to fixed-point voltage value Q.2
voltage = (uint16_t)((uint32_t)(ADC * (uint32_t)(VREF_VALUE + 1)) >> 10);
На быстродействие не жаловался. Камень справляется с двумя интерфейсами и жирной программой, занимающей почти всю память.
I am DX168B and this is my favourite forum on internet!
Контактная информация:
Реклама
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18677
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

DX168B писал(а):Я делал вот такие жуткие конструкции
когда я вижу такое количество явного приведения типов в выражении, меня страх берет. имхо, это свидетельство полного непонимания того, что такое тип данных и как его использовать.
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

Компилятор все константы и промежуточные выражения принудительно приводил к int16,
что приводило к неверным результатам вычислений при отсутствии каких-либо предупреждений.
Так что тут это была сильно вынужденная мера. Возможно, проблема в конфигах сборки, я не разбирался.
I am DX168B and this is my favourite forum on internet!
Контактная информация:
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18677
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

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

Мой уютный бложик... заходите!
Контактная информация:
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1455
Зарегистрирован: Ср сен 03, 2008 21:42:17
Откуда: Ленинградская область,пос.Красный Остров

Сообщение WatchCat »

компилятор поступает, как должен.
небольшое уточнение - как он должен поступать решили полвека назад на машине PDP.
И сейчас как раз в этом месте вылезает одна из неприятностей такого поведения.
А именно, при умножение двух int16,даже явно видя что под результат выделено in32 и соответственно он туда влезет,тем не менее обрезает его до int16 и в таком виде присваивает. Ну хорошо, пусть он это делает по традиции,но почему gcc не выдает вообще никаких предупреждений на эту тему,хотя например в случае если функция возвращает int,а присваивается он unsigned int - предупреждение выдается? Чем этот случай хуже/лучше того?
Кстати, TurboPascal 5.0 несмотря на его любовь к предупреждениями по делу и не по делу - тоже на этот случай молчал.
А вот Ada (gnat) это отслеживает.
во всяком случае самое левое приведение ну вот вообще лишнее, имхо.
Согласен,компилятору оно не нужно. Но удобно человеку,читающему код.

А вообще приведенная в примере форумула
voltage = (uint16_t)((uint32_t)(ADC * (uint32_t)(VREF_VALUE + 1)) >> 10);
- это тоже самое что я в начале темы написал:
a = (unsigned long)b * 1000 / с;
Только сдвиг вместо деления,что не особо принципиально.
И в обоих случаях будет неприятность если результат перемножения окажется сравнимым с делителем.
Но если заведомо знаем что из АЦП придет достаточно большое число - то всё будет работать.
Хуже если оно может быть и большим и маленьким. Считать-то тогда можно всё в int32, но вот если результат потом надо записывать в таймер ШИМ то приводить к int16 всё равно придется. И ситуацию потери точности придется как-то обрабатывать.
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

На счет первого приведения согласен - лишнее, но не мешает.
С остальными приведениями - категорически нет.
Если имелось в виду:

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

#define VREF_VALUE 500ul

uint16_t voltage = ((VREF_VALUE + 1) * ADC) >> 10;
то ему было побарабану на суффикс. Он упорно его игнорировал.
ADC имеет размер 16 бит, а результат умножения легко выпрыгивает за пределы 16 бит,
но при этом он все равно его делал 16-битным.
Даже несмотря на выражение вида

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

((uint32_t)(VREF_VALUE + 1) * ADC)
Компилятор g++ из ардуйневого фреймворка, а код написан на C++ если что.

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

Может такой вариант:

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

uint32_t tmp = ADC * (VREF_VALUE + 1);
voltage = tmp >> 10;
не вызвал бы никаких непоняток, но сделал как сделал.
I am DX168B and this is my favourite forum on internet!
Контактная информация:
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1455
Зарегистрирован: Ср сен 03, 2008 21:42:17
Откуда: Ленинградская область,пос.Красный Остров

Сообщение WatchCat »

[uquote="ARV",url="/forum/viewtopic.php?p=4203635#p4203635"]
WatchCat писал(а):А кроме перевода строки и возврата каретки вполне востребована инверсия и мигание.
ничто не мешает реализовать это, минуя printf[/uquote]
Инверсия,мигание, позиционирование, шрифт,цвет - всё мимо printf.
А от printf остается только умение вставлять нужное количество пробелов.
При том что printf занимает довольно много места в памяти,что для МК весьма критично. И форматные строки тоже хранить надо,с их пробелами.
Можно конечно и так если место в памяти не жмет.
Но в примерах из аппнот я чаще вижу itoa() чем printf. Точнее говоря - именно printf видел только в одном примере где объяснялось как можно stdout привязать к uart. А так если уж именно форматный вывод то sprintf в буфер и потом уже из этого буфера на индикатор или в uart(и у меня также сделано). Привязкой именно stdout к устройству вывода по-моему вообще никто не занимается,во всяком случае мне не попадалось кроме того примера,иллюстрирующего что это вообще возможно.
причем, стоит поменять на пару символов дизайн, и весь огород придется перепахивать... ну а printf потребует правки только строки формата.
В случае необходимости создания сложных меню - я бы поискал именно библиотеку для меню,а не пытался бы рисовать меню на "голом" printf. Попадались в сети упоминания таковых библиотек. Вот буквально вчера видел "библиотека четырехуровневого древовидного меню для avr". Мне пока сильно сложный интерфейс не требовался ни разу поэтому не скачивал и не смотрел как сделано. Так как устройства делаю для себя то меня устраивает выбор нужной функции просто вводом цифры,без бегающих полосок меню. Девяти цифр и нуля для выхода на уровень вверх обычно достаточно.
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="DX168B",url="/forum/viewtopic.php?p=4203698#p4203698"]

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

voltage = (uint16_t)((uint32_t)(ADC * (uint32_t)(VREF_VALUE + 1)) >> 10);
[/uquote]
Если это C++, то тут и скобок куча лишних:

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

voltage = uint16_t(uint32_t(ADC * uint32_t(VREF_VALUE + 1)) >> 10);
А потом можно удалить все три приведения:

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

voltage = ADC * (VREF_VALUE + 1ULL) >> 10;
1ULL - это же 32-х битное значение на AVR?
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

Суффикс ULL по всем стандартам должен означать 64 битную разрядность.

Если я правильно помню (avr-gcc):
int = 16
short = 16
long = 32
long long = 64
Разрядность всех констант по умолчанию приравнивается к разрядности int.

Выражение:

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

#define VREF_VALUE 500ul
voltage = ADC * (VREF_VALUE + 1ul) >> 10;
было написано сразу. Но оно не работало правильно.
Причины я уже расписал выше.
И даже если написать (VREF_VALUE + 1), разве разрядность результата сложения или умножения не должна быть
приравнена к разрядности самого "большого" операнда?
I am DX168B and this is my favourite forum on internet!
Контактная информация:
Мудрый кот
Сообщения: 1849
Зарегистрирован: Вс дек 25, 2016 08:34:54

Сообщение Dimon456 »

Во куда вас занесло.
Меня больше не printf волнует, а точность преобразования.

Провел еще один экспериментик, взял число 21 (0.021) и сложил его 60000 раз.
Что должно выдать 1260.000
обычный float выдал 1259.8

фиксированная арифметика выдала
при 8 бит точности 1171.875
при 16 бит точности 1259.765
при 32 бит точности 1259.999

и чую, где этот друг с урановым рудником, что на С++ 64 битная точность, да даже и 96 битная точность выдаст то же 1259.999
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="DX168B",url="/forum/viewtopic.php?p=4203772#p4203772"]Суффикс ULL по всем стандартам должен означать 64 битную разрядность.[/uquote]
Нет в gcc для AVR 64-х битных типов, по крайней мере раньше не было.

Добавлено after 1 minute 58 seconds:
[uquote="DX168B",url="/forum/viewtopic.php?p=4203772#p4203772"]

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

#define VREF_VALUE 500ul
voltage = ADC * (VREF_VALUE + 1ul) >> 10;
было написано сразу. Но оно не работало правильно.
Причины я уже расписал выше.[/uquote]
Причина в том, что UL для AVR дает 16-ти битные константы. Хотя я не спец по AVR, но если добавить такой суффикс чтобы константа стала 32-х битной, то больше ничего не нужно.
Последний раз редактировалось Reflector Пт мар 25, 2022 18:07:41, всего редактировалось 1 раз.
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

Reflector писал(а): Нет в gcc для AVR 64-х битных типов, по крайней мере раньше не было.
А в документации говорят, что есть. https://gcc.gnu.org/wiki/avr-gcc
но если использовать параметр -mint8 при сборке, то тогда их не будет. Но тогда и int будет восьмибитным.
Ладно, не будем засорять тему.
I am DX168B and this is my favourite forum on internet!
Контактная информация:
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

Проверил в Compiler Explorer, таки 1UL - 32-х битная, тогда не понятно какие проблемы могли быть, должно работать.

ps. Оно и работает, а если оставить просто 500, то уже нет.
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1455
Зарегистрирован: Ср сен 03, 2008 21:42:17
Откуда: Ленинградская область,пос.Красный Остров

Сообщение WatchCat »

Сделал работающий в VMLAB тестовый пример и на мой случай

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

result =  (unsigned long)a * 1000/ b;
и на случай уважаемого DX168B

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

voltage = ((VREF_VALUE + 1) * adc) >> 10;
Считает, форматирует результат с помощью sprintf, и печатает в виртуальный uart.
В частности при

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

#define VREF_VALUE 500ul
uint16_t adc = 23906;
у меня выдало

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

voltage = 11696
Все это я проходил, но только явное распихивание типов и помогло решить проблему.
Для меня самого это показалось какой-то дичью.
И таки да, могу под этими словами подписаться,ибо тоже с подобным сталкивался в сложных формулах и решал вопрос также.
При отладке чего-то большого это самый простой способ обхода "традиционных" капризов компилятора.

Мой пример можно и в реальный контроллер прошить если он есть под рукой и есть чем подключиться к его uart
А если запускать в Proteus то надо в мэйкфайле поменять формат отладочной информации на dwarf-2 чтобы исходник показывало.
Сам мэйкфайл кстати рекомендую - мне показался очень удобным. Давно его использую.
Вложения
formula.zip
(61.73 КБ) 107 скачиваний
Последний раз редактировалось WatchCat Пт мар 25, 2022 19:21:13, всего редактировалось 1 раз.
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

[uquote="Reflector",url="/forum/viewtopic.php?p=4203788#p4203788"]Проверил в Compiler Explorer, таки 1UL - 32-х битная, тогда не понятно какие проблемы могли быть, должно работать.

ps. Оно и работает, а если оставить просто 500, то уже нет.[/uquote]
Вот и я про то же
DX168B писал(а): ему было побарабану на суффикс. Он упорно его игнорировал.
***
Возможно, проблема в конфигах сборки, я не разбирался.
WatchCat проблема заключалась в том, что формула работала правильно при низких значениях АЦП.
Но начиная с определенного значения АЦП, конечный результат становился неверным.
Причина неверного вычисления на поверхности - переполнение промежуточного значения (результата умножения).
Я не стал искать истинную причину странного поведения, так как думать надо было о решении самой задачи проекта, а не о различных мелочах и неоднозначных особенностях инструментов, которые через неделю снова забудутся. В итоге, убедившись, что я нигде не ошибся, просто обложил выражение скобками и типами, явно обозначив то, что компилятор должен сделать. Да, некрасиво. Но это дает стабильный результат и экономит время.
I am DX168B and this is my favourite forum on internet!
Контактная информация:
Ответить

Вернуться в «AVR»