Привет. Начал эту тему где-то после НГ, хотелось самому детально разобраться, что к чему, в общем медленно, по несколько подходов, но кое-что получилось. Сначала собрал индикаторы на двух 74HC595 и двух 4-х значных семисегментах, потом добавил ещё одну 595, очень помогла эта тема на коте (https://radiokot.ru/forum/viewtopic.php?f=57&t=108443). Те, получились два модуля с индикаторами, для тренировок. Честно говоря помучиться пришлось... :) Писал в geany, на F5 назначил make flash. Над Makefile немного поиздевался. Проекты, платы и код в архивах. Продублирую тут: Для 2-х 595: Для 3-х 595: Makefile примерно такой: И всё время, как заноза сидит мысль в голове, зачем нужен этот avr? Надо наконец браться за stm, хоть как-нибудь... ы. Вольтамперметр я всё-таки собрал (https://www.youtube.com/watch?v=6665u6YP9IA), чуть позже выложу, только плата мне жуть как не нравится, но тк у меня накрылся старый лабораторник (https://vrtp.ru/index.php?showtopic=16392) надо было по быстрому что-то собрать.
Занятно :) Зачётно! Сегодня вволю (на почве своего слабого знания Цэ) помедитировал, как оно работает. Получается, тут не скользящее окно используется для усреднения, а просто суммирование-деление? В принципе, нормально, т.к. скользящее окно много оперативки требует, а тут тинька... У меня пока другой вопрос, касаемо схемы. Там вот цифровая и аналоговая земли разъединены. Это... наверное схема не полная? Что-то я не нашёл, как они соотносятся.
зы: в Сях изящно записывается HEX->BCD преобразование. В последние дни меня муха укусила, опять взялся за свой недоделанный измеритель. В общем, HEX->BCD у меня получился на ~280 машинных циклов (до 300 в зависимости от условий) что в общем-то неплохо (если алгоритм "тупо" прогонять, там все 700 циклов будет. Но говорят, можно решение уложить в раза два более быстрый, а самое главное - компактный код. Мозг уже сломал, ей-богу... Оставлю здесь ссылку для размышлений. http://we.easyelectronics.ru/AVR/matematika-na-assemblere-v-avr.html
А ты ассемблерный код этого места (HEX->BCD), генерируемый Сями не изучал?
зызы: на видео не понял, почему у показателя напряжения сотые всё время 0? В коде что-то не найду...
Хм, тут бился больше с пониманием SPI и тем, что стоит в прерывании: Для 2-х 595: Для 3-х 595: Методом тыка скорее получилось проталкивание байтов в SPI. Не понял момент например, если я на этой же линии SPI захочу ещё одно устройство повесить, но это надо конкретно уже пробовать. К сожалению ассемблер я только пробовал когда-то давно-давно, и на данный момент совсем с ним не дружу. Ну и тут уже совсем не тинька, но желание сделать компактный код есть всегда. Я сначала хотел уйти от деления, но честно говоря сильно не упирался, можно наверное int2segm реализовать ветвлением, что наверняка будет красивее. ++ По поводу земель, я что-то уже запямятовал... но точно плата именно та, которая в кикадовском проекте, попробую по дорожкам завтра глянуть. С сотыми напряжения тоже что-то я делал, сорри, начинаю забывать, с мая к этой теме не притрагивался...
А какой процик в реальности стоит? oops... я посмотрел внимательнее, на схеме Mega8 (виноват, почему-то подумал, что тинька). На 8-меге килобайт озушки, вполне много, чтобы хранить отсчёты АЦП по отдельности. Впрочем, скользящее окно - это на любителя. У него одно преимущество - при резком изменении измеряемого значения, оно не покажет резкий скачок, а "плавно" поднимет показания. В остальном всё то же самое. Но памяти требует много (два байта на каждый отсчёт)
ps: SPI не подразумевает множественные подключения. Если есть такая нужда, то назначать адресата можно, например, синхросигналом "Clock", мультиплексируя его (например, каким-нибудь дешифратором типа ИД4 (http://esxema.ru/?p=1502)). Соответственно, линия Data остаётся одна на всех, а Clock посредством мультипрекса через ИД4 будет подаваться только на тот приёмник, которому предназначена Data. Также можно и с Latch поступить (благо ИД4 прекрасно работает и как два независимых дешифратора). Адрес должен подаваться на входы ИД4, а Clock как сигнал разрешения дешифрации.
Второй вариант адресации в SPI - чисто информационная, т.е на основе анализа того, что в Data передаётся (например, первые четыре бита - адрес приёмника). Но это уже уровень выше, простые микросхемы так не умеют, хотя если заморочаться, хардверно вполне можно реализовать, корпусов только понатыкать придётся. Из-за "понатыкать" оно никого и не интересует, хотя внутри ряда микросхем, например, память, всякие измерители и т.п., именно такой вариант и реализован (я именно про SPI, а не про i2c, который несколько другой вариант, хотя и близнец).
Да мега8, но был карантин и в закромах только 328-ые остались. Про int2segm и деление нашёл ссылку (http://blindage.org/?p=2824) как хотел сделать: Только что-то у меня пошло не так, надо бы макет опять собрать на столе и попробовать. На плате неудачная разводка ОУ и датчика тока. Про земли - gnd на gnda в одном месте. ы:
"Скользящее окно" - это когда в памяти хранится N последних сделанных измерений. При этом при каждом новом измерении самое старое удаляется. Для этого удобно использовать кольцевой буфер длиной N (если в Си, то массив длиной N).
При этом последующее суммирование с целью усреднения делается всегда по всей длине буфера.
зы: визуально скользящее окно можно представить игрой "Змейка". Змейка - это и есть окно, только в игре оно ещё и растёт :)
зызы: Я тут картинку про мультиплексирование нарисовал. Микросхема чуть другая, но это не суть.
↓ спойлер ↓
(https://anklab.ru/is/image.php?di=8BGZ)
[свернуть]
Хотя на деле линию Data, а также Reset и/или OE точно также можно мультиплексировать таким же способом. Такое мультиплексирование применяют, когда если вдруг регистров много, у них разные функции. Без мультиплексирования они все последовательно включены, значит, при изменении информации в каких-то отдельных регистрах нужно обновлять их все. Мультиплексирование позволяет разбить их на группы и выбирать группу, в которой информация должна обновиться, не затрагивая остальные. Но для пары-тройки регистров овчинка выделки не стоит, а вот для массива (например, светодиодная матрица) - уже да.
ps: Про деление на нецелые... В принципе, если сделать диапазон измерений не произвольным, а привязать к возможностям двоичной логики, то становится достаточно просто в плане ухода от нецелых. В двоичной логике легко делить на два, четыре, восемь и т.д. Измерений можно делать сколь угодно много (в разумных пределах). Верхний предел измерения АЦП - 10 бит, т.е. значение 1023. Точку мы можем поставить вручную где угодно. Например, чтобы получить диапазон измерений от 0.00 до 10,23 вольта, достаточно измерить N раз, просуммировать и разделить на N. Например, N выбираем 32 (потому что степень двойки). А в машинном коде разделить на 32 - это сдвинуть сумму вправо на пять бит (можно ещё флаг переноса прибавить для правильного округления результата). Вуа-ля! На индикаторе будет от 0 до 1023 без длинных вычислений.
Диапазон 0.00 - 20.47 делаем точно также, например, суммируем 32 значения, но сдвигаем вправо на 4 бита (делим на 16). Диапазон 0.00 - 40.95 - аналогично, например, суммируем 64 значения и сдвигаем вправо на 4 бита.
Чуть посложнее. 0 - 15 : Суммируем 48 измерений, сдвигаем вправо на 5 (делим на 32) 0 - 16 : Суммируем 50 измерений, сдвигаем вправо на 5. 0 - 30 : Суммируем 48 измерений, сдвигаем на 4... 0 - 51 : Суммируем 80, сдвигаем на 4... ... и т.д. и т.п.... вариантов много, хотя они и не совсем произвольные. Ну, а приведение измеряемого к опорному - это дело аналоговой части (ОУ) :)
С другой стороны, обычно у таких индикаторов времени, не занятого вычислениями, остаётся вагон и туда можно даже работу с Float закинуть
У меня со сдвигами и двоичной логикой туго... но попытаюсь попробовать. Про сотые в напряжении - их нет, скакали сильно, избавился их, да и не хватит значений на нормальные показания с сотыми долями. Тут лаб-то 24 вольт (2х10.5 переменки), не полных, 2 ампера, это временный вариант, есть слаботочная обмотка для индикатора, но уже было поставил реле на переключение обмоток... тока не хватило, гаснут индикаторы при срабатывании. :) +++ Тот ЛБП, про который упоминал выше (схема с вертепа) буду переделывать, корпус от китайского 1502, в который ~200 ватт трансформатор запихнул,на выходе около 40 вольт, две обмотки, три TO247 на радиаторе (радиатор маловат). Твоя плата туда никак не лезет, поэтому пока думаю что туда поставить. +++ А от массива данных с АЦП в этом коде отказались когда в тиньку запихивали код, тут аппаратно тоже сглаживание есть.
Ну, с двоичной логикой достаточно просто ↓ спойлер ↓
блина, были бы предки дальновиднее, они бы двенадцатиричную систему счисления внедрили бы и пользовались, а то, блин, какая-то десятичная мерзость, пальцев, видите ли, на руках не хватало... :-\
[свернуть]
Фишка в том, чтобы не использовать сложные ресурсоёмкие вычисления, при расчётах ограничиться только сложением и делением на двойку в степени. Вот и получается, что делить можно только на два, на четыре, 8, 16, 32, 64... Ну, а суммировать удобно сам буфер (или не буфер, а просто накапливать в ячеечке).
АЦП десятиразрядный, следовательно, минимальное значение у него 0, максимальное 1023 dec = 3FF hex = 0011 1111 1111bin
Считать просто. [предел] = [длина буфера]*(1023)/[делитель]. Делитель - 2(4,8,16,32,64). Длина буфера - сколько памяти хватит.
Перекинув уравнение, отталкиваясь от нужного предела и имеющегося делителя, можно посчитать нужную длину буфера.
[длина буфера]=[предел]*[делитель]/1023
Надо в результате иметь 50 вольт в пределе. На индикаторе хотим 50.00, т.е. 5000 (точку-то всё-равно вручную ставим)
5000*16/1023 = 78,2 - длина буфера нецелая не может быть, значит, округляем её до целого и считаем [результат] по первой формуле. 79 число какое-то некруглое, а вот 80 прикольное. 80*1023/16=5115, т.е. 51.15 (вольт). Хотя и 79*1023/16=5051 - тоже ничего.
Ну а дальше просто - калибровка ОУ таким образом, чтобы при подаче предельного на вход измерителя, АЦП "дотрагивался" до 03FFhex
Ну, а в тиньке длинный массив просто негде держать - места там всего 64 байта. Ну, может 128, но всё-равно мало, временные переменные да стек ещё от неё отъедят...
ps. А вообще, прыгать как-то по сотым и не должно сильно. Мне, правда, в твоей схеме не нравится способ подключения ОУ ко входу АЦП. Я бы на входе АЦП (т.е. прямо на пине) хотел бы видеть конденсатор, может даже 0.1 мкФ, вторым концом сидящий на аналоговой земле, т.к. там в пине какой-то паразитный ток всё-равно есть. А там регулировочный резистор торчит (да ещё маленько странно включенный). Этот паразитный ток (переменная составляющая) на нём падение напряжения делает, и это нехорошо - всяко шум на младшем разряде.
И ещё неплохо бы во время измерения АЦП закидывать ядро в спячку... На ASM это просто, а как на Сях, увы, не знаю. ADC-2-Sleep_Mode.png
Конечно можно! А то в одного я немного забросил эту тему. Макет с индикаторами даже не собрал ещё, смотрю что у меня из индикаторов осталось... Ну и было в планах на stm уже это повторять. +++ Погрешность у АЦП 2 значения вверх и вниз, сам я оверсемплинг не пробовал в железе, можно.
Да, но в кикаде и в протеусе схемы отличаются. В протеусе как надо сделано, а в кикаде и конденсатора нету, и верхний конец подстроечника (по схеме кикада) с движком не соединён. Со схемой в протеусе я согласен.
А конденсатор на пин можно на платке и так подпаять да посмотреть, что будет.
Упс, точно там же центральный пин подстроечника тоже соединён с пином ADC. Только на рабочем варианте не буду менять, показания уплывут, только конденсаторы на пины повешу.
С7 и C11 тоже можно по 100 нан поставить. Потому что параллельно им резисторы в ~10 раз меньше, чем в канале тока. Частота среза будет в районе сотни герц.
Понял, почему GND и GNDA не соединил на схеме, - kicad зальёт полигон полностью, и не даст соединить GNDA с GND в том месте на плате, где я захочу. По поводу выжимки из АЦП атмеги всего, - то, что читал в интернетах успехов сильных не дало, включая noise ruduction и оверсэмплинг, те 4-х знаковые индикаторы наверняка будут излишними. У меня пару MCP3204 лежат уже давно...
Я согласен по поводу шума, ведь там всего 10 разрядов, и младший болтает туда-сюда. Три разряда - это 1024 градации. 1024 - это считай три десятичных знака (с очень небольшой погрешностью). Поэтому есть полное основание оставлять светиться только три "весящих" разряда на индикаторе. Потому что для получения точности в 4 десятичных разряда АЦП нужен 13-разрядный (а фактически 14-разрядный, в бинарном понимании, естественно). 14-разрядные - это уже уровень "продвинутый", а вот 12-разрядные пробовать можно (4096 градаций уровня). Но к ним нужно ещё и ОУ качественные подтягивать, что уже может потянуть на какую-нибудь курсовую по конструированию для пары-тройки успевающих студентов :)
MCP - да, интересно пощупать. Но пока валяются в ящике...
У себя я захотел сделать так: на один измеряемый параметр цепляю два ОУ с разными коэффициентами усиления. Мысль такая. Раз АЦП трёх-(в десятичном понимании)-разрядный, то и "светить" надо три разряда. Однако два ОУ с разными коэффициентами усиления помогут разбить весь диапазон на два. Например, первый ОУ масштабирует 10,2 вольт к опорному, второй - 51,1 вольта. Если данные с первого АЦП в диапазоне - выводим данные с первого АЦП. Погрешность ("вес отсчёта") 10мВ - можно светить четвёртый разряд.
Если видим, что по первому АЦП близко к верхней границе, то переключаемся на вывод данных от второго АЦП, но при этом гасим самый младший десятичный разряд - он становится бесполезен.
В итоге ив обоих случаях на 4-значном индикаторе видим три весомые десятичные цифры. Точку же для удобства восприятия оставляем фиксированной. Ведь и правда, сотые доли вольта интересны только при низких напряжениях, при высоких вполне достаточно десятых.
Из недостатков такого подхода - нужна "удвоенная" аналоговая часть, причём её ещё и согласовывать надо между собой. Т.е. точность нужна.
Чтобы соединить GND и GNDA нужно ввести (придумать) какой-нибудь фиктивный компонент, например, резистор с маленьким сопротивлением, и включить его между землями. Также я заметил, что когда заливаешь полигон, льёшь его на весь участок платы. Рекомендую всё-таки определять полигоны поменьше, располагая их только там, где надо. Это позволит избегать бесполезных "полуостровов", а также делить их для организации "звёзд" и т.п.
... Как дела? Собрал "макет", 2 индикатора, atmega328p, usbasb, ch340. Набросал uart (надергал отсюда (http://www.rjhcoding.com/avrc-uart.php)), только с кандачка не получилось, инициализацию uart пришлось свою делать, а так - работает, мне пока только передача нужна была. Больше 57600 не запустилось, пока не понял почему.
Переписал ещё немного. Опрос АЦП отправил на своё прерывание, сейчас опрашивается 2 канала, но добавить остальные легко. Ушел от функции деления без остатка, функция bin_bcd и структура bcd. Вывод без преобразования на индикаторы и в uart.
В выходные был занят на огороде и неплохо отморозился (в прямом смысле - температурка на улице упала вместе с первым снегом, дак, думаю, отходняк ещё пару дней займёт). Зато остатки яблок снял - уже вкусные стали :) Так что, касаемо процессоров и прочего, ничего и не делал :( В планах первым пунктом стоит завести твою схему в DipTrace, изобразив её в соответствие с ЕСКД (ну, хотя бы приблизительно).
На последнем видео у тебя чёткий 0 - это сигнал напрямую на ногу АЦП подаётся или через ОУ? Если через ОУ, то результат отличный. А то, честно скажу, сомневался по поводу того, что там чёткий 0 будет и хотел пробовать делать небольшое отрицательное питание.
У себя, играясь с изложенным выше алгоритмом, заметил закономерное (закономерным оно стало после процесса "а маленько подумать" :) ) явление, заключающееся в том, что несмотря на возможность чисто математически выводить значения "как бы повышающие разрядность АЦП", на индикаторе значения имею всё-равно согласно разрядности. А промежуточные "типа более точные" значения пробегают довольно быстро. На деле эффект закономерен и вызван... отсутствием стационарных шумов на входе АЦП (в измеряемом сигнале). Вот и думаю, что либо как ввести такие шумы (те, кто занимается аудио, наверняка слышали про dithering (https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%B7%D0%B5%D1%80%D0%B8%D0%BD%D0%B3), либо... либо это вся овчинка вообще выделки не стоит.
Сам на дачу в выходные не поехал, сын только съездил за яблоками ;) - но всё не увезти, яблони хорошие, девать особо не куда, раздаем в основном, ну и немного в гараж отвезу. Дело в том, что дача на острове (https://gotonature.ru/1733-ostrov-sarpinskij.html), и на машине тужа можно, но это паром и крюк довольно большой. Добираемся речным транспортом. Ну и у нас теплее намного :)
АЦП у меня просто на делителях напряжения весит, ОУ ещё не прикрутил. В основном кодом занимался, вроде неплохо получилось, только не знаю надо ли было в своё прерывание АЦП кидать, для индикатора и в прерывании по таймеру ему неплохо жилось. А зачем перекидывать в DT? Хотя если уже привык... ы. На работающем БП ОУ MCP6002 - ноль отличный.
DT - это дань привычке. Я привык к его "крыжикам" (которые более-менее копируют крыжики P-CAD/Altium), а у KiCAD они совсем другие - ломка возникает (как у пилотов: "штурвал или сайдстик" :) ) А поскольку перерисовать всё-равно (мне) придётся, то сохранять прежний инструмент не принципиально. А там и KiCAD'освкую версию можно параллельно вести (глядя друг на друга).
Если 0 с этими ОУ стабильный, то я полагаю, в схеме изменений (кроме вот вышеупомянутой коррекции) делать и не нужно. Только решить, два регистра, или три. Тут есть небольшая засада, заключающаяся в допустимом токе через вывод. Общий вывод индикатора всё-равно коммутировать надо, но через него будет идти суммарный ток сегментов, который при динамической индикации довольно высок. Даже если 10 мА на сегмент - это 80 мА в пике. Нога HC595 такого не держит (предел ~30мА).
Может быть, два регистра + катодные ключи (взять индикаторы с общим катодом, чтобы N-FET транзистором, теми же 2N7002, выборку разряда делать?)
зы: а можно радикально - восемь регистров и хардверная динамическая индикация. Или вообще статика, но это и индикаторы нужны другие. Наверное, это трогать не стоит - 4x индикаторы легко купить только для динамической индикации, на том же Ali статических 4x и не нашёл даже...
Да, катодные ключи - однозначно надо. У меня кучка 2N7002 и BC817 в SOT-23 есть, те мне больше нравится вариант с двумя регистрами и ключами. Статику я твою тоже помню, да симпатичная, но уж сильно большая получается, может потом как-нибудь. Вообще почти всё в smd делать наверное буду, уже привык. ы. Кварцы 3525 вот такие: ыы. Надо подумать на счёт ref192, хотя для 10-бит под вопросом. ыыы. На счет привычки - периодически открываю что-то в DT - не могу... после кикада меня он аж пугать начинает, за больше чем полгода настолько привык, и скорость довольно неплохая - хоткиями левой рукой m, r, e, g, x, w ... правой мышь, - супер! :)
О как, с корабля на бал, про подмешивание шума немного читал, можно попробовать, но с пониманием у меня тоже вопрос. :) Вот тут (https://chipenable.ru/index.php/programming-avr/142-avr121-oversampling-decimation.html).
О, да. Почитал аппнот, посмотрел видео - результат приятен. Однако шум подмешивать однозначно надо. На рисунке, что я выше кинул, наверное плохой способ подмешивания шума (была мысль о подмешивании псевдослучайной последовательности 0 и 1, которую можно легко получить на сдвиговом регистре даже чисто программно (https://habr.com/ru/post/121849/)).
В аппноте же, насколько я понял (они это не уточняют, говорят только про PWM с заполнением 50%), используют треугольный сигнал для подмешивания в опору. Это имеет смысл т.к. всё становится ещё проще. Думаю, стоит сделать закладку на подмешивание такого сигнала (всего несколько деталей и нога порта), а подмешивать или нет - это уже можно решить в программе.
Сейчас смотрю твой последний код и плохо понимаю, как ты синхронизируешь считывание АЦП с пересчётом результатов. АЦП работает по FreeRunning режиму, но пересчёт результатов и вывод на индикаторы разве сами по себе? (повторю, я плох в Си и может просто не вижу - пожалуйста, ткните меня в нужное место)
Я бы попробовал такой вариант алгоритма. Основа синхронизации - прерывания по таймеру. Пусть те же 1000 Гц (250 Гц мерцания на индикаторе - 4 цифры)
1. вывод показаний на индикатор (очередная цифра)
2. запуск однократного преобразования АЦП - (режим Sleep с ожиданием прерывания. В прерывании вообще не нужно ничего делать - оно лишь служит сигналом к продолжению работы программы). Последовательно запускаем так для каждого из каналов.
3. Пересчёт сумм в кольцевом буфере (если буфер накопительный, то пересчёт нужно сделать только 1 раз за n циклов, т.е. когда значения накопятся)
4. Подготовка показаний для индикатора (тоже 1 из 4 раз - только когда на индикатор выведена последняя цифра, если буфер кольцевой, или 1 раз за n циклов, если буфер накопительный)
5. сваливание в Sleep с ожиданием прерывания от таймера.
По времени я прикинул - должно укладываться со значительным запасом.
Что меня смущает... использование аппаратного вывода на индикатор безусловно круто, но... блин, есть две причины, чтобы он мне не нравился. Первая - всё-равно программа "тупо ждёт", когда из буфера выйдет всё, чтобы вывалить туда следующий байт, вторая - всё-равно вручную дёргается PB2 (SS). Ну, программа короче, да... ps. а вот ещё. У процессоров в 32-выводных QFP корпусах есть два "лишних" входа ADC 6 и ADC7, выведенные на собственные ноги и питающиеся исключительно от AVcc. Возможно, что стоит задействовать? Выводы PC0~PC5 при этом совершенно свободны, собственно, как и PD0~PD7 (UART, я понимаю, исключительно для отладки)
В последнем моем варианте никакой синхронизации нет, я тут ещё сам для себя все эти моменты только открываю, опоздал лет так на надцать. :) Во Free runnig режиме, насколько я понял прерывание работает по окончанию преобразования, далее с результатом я ничего не делал, чистый его вывод с задержкой. Надо сглаживать, накопитель и среднее. Тот код который с оверсэмплингом не мой, я просто склеил куски и запустил для посмотреть, опять-таки выхлоп чистый, не сглаженный, потому как вот этот кусок кода для меня не понятен от слова совсем. Отсюда (https://github.com/0xPIT/UltiDMM/blob/master/main.cpp): Вот код, на котором работает оверсэмплинг, каналы ADC0 и ADC1, выдернул оттуда же, забор показаний сделал из переменной adcOversampledValue. Там в коде есть ещё компенсации, с ними не разобрался. Всё сырое!
UART пока только для отладки. У меня есть китайский генератор (FYT3200S) могу попробовать подмешать с него что-нибудь на опору, какое напряжение и сигнал? Подключать также - 33 кОм + 0,1 мкФ? Не знал про ADC6/7 в QFP, да, если от AVCC они питаются думаю результат лучше будет.
Цитата: Slabovik от 07 Окт., 2020, 11:40Первая - всё-равно программа "тупо ждёт", когда из буфера выйдет всё, чтобы вывалить туда следующий байт, вторая - всё-равно вручную дёргается PB2 (SS). Ну, программа короче, да...
Разговор про while(!(SPSR & (1<<SPIF)));? Из этого куска: Не знаю, делать софтовую реализацию SPI? Мне кажется под вопросом.
Да, этот "while" и ожидает появления бита "передача окончена". В принципе, можно и прерывание организовать по окончании передачи, но... будет ли это колхоз проще. Вот и получается, что этот SPI не совсем аппаратный, а "полу". Байт на биты аппаратно раскладывает, но более не умеет.
Подмес шума наверное надо организовать по схеме из аппнота. В этом случае с генератора достаточно подать меандр. Амплитуда помехи на ARef должна быть где-то 2,5 вольта опоры / 1024 отсчёта = 2,5 мВ, можно немного больше. Помеха не должна быть высокочастотной, чтобы ARef за время преобразования (~15 циклов тактовой АЦП - она ниже тактовой ядра на коэффициент деления) ARef не сместился куда-то далеко. Полагаю, что-нибудь в районе килогерца будет нормально. Если смотреть осциллографом с закрытым входом, там на конденсаторе сигнал должен быть близким к треугольнику (при меандре на входе всей системы).
Код оверсемлинга я тоже что-то сходу не пойму, что они там делают. Надо на бумажке попробовать его "пошагать". Я могу рассказать, как работает мой код, но у меня асм :-[ хотя вычислительные приёмы - они языконезависимы.
Мой вариант опроса (без оверсэмплинга) ADC такой: Тут не обязательно было делать можно проще adc_buffer[0] = ADC;. Можно твой кусок на ассемблере попробовать сюда включить, заодно посмотрел бы как вставка asm(); работает
Страшно мне смотреть на выкрутасы с ADMUX. Знаю, что для данного применения нормально, один фиг там управляющие биты по нулям и от нулей отличаются только сами MUX, но... правильно было бы читать ADMUX, изменять MUX (сле-едующий! :) ) и пихать его обратно. Ну или применять здесь дефайны настройки АЦП. При этом завязать номер MUX на переменную-указатель adc_channel (хотя, вынимая номер из MUX всегда можно знать, какой АЦП сейчас был включен.
В реальности же это работоспособности не вредит, только "причёсывает" стиль написания.
А есть .lss этого кусочка? Хочется посмотреть, как оно в машинные команды оттранслировалось... В asm у меня процедура чтения длинная, но я там все 8 каналов щёлкаю, раскладывая по кольцевым буферам, вряд ли для вставки в Си оно пригодно, разве что только сам кусочек, где непосредственно опрос идёт, но я его приводил ранее.
зы: что я ни делаю, у меня только танк и получается. Напильники наверное не той системы :-\ Наваял-10-07.png Две 595 обязательные, третью в общем-то вполне могут заменить либо свободные разряды PC, либо PD полностью отдать. Но третья 595 мне нравится тем, что с ней индикатор меняет своё состояние абсолютно синхронно (это конечно не критично в данном изделии, просто греет ЧСВ). Можно повесить ещё пару светиков. Зачем - не знаю, но имеющиеся два нахожу приятными. Их можно расположить рядом с индикаторами и гореть ими, отображая CC/CV или ещё что-нибудь. Программно, конечно.
А, да... Разъёмчик AntuBug справа - это для возможности нарастить длину индикатора. Иногда полезно в отладочных целях плюнуть туда что-нибудь для проверки.
зызы: "выпихивание" 8 байтов в индикатор чисто программным способом у меня занимает порядка 70 микросекунд при тактовой 14,3 МГц. AsmCode_Burst_8_bytes.png Сможешь посмотреть, как быстро выплёлывает аппаратный SPI? Только для этого SS нужно опускать прямо перед процедурой, а поднимать после (а то ты его сразу же опускаешь после поднятия) - тогда осциллографом легко определить, сколько времени это всё занимает.
Насмешил про танк! Думал 8 транзисторов в имеющуюся схему внедрить, а у тебя вон она как, может всё-таки 8 ключей? Не знаю получится ли найти осциллографом что-то. Это надо сократить код как можно сильнее и сделать плевки с паузой так? Про lss, у меня Makefile каст сокращенный, но внедрил, сейчас при сборке генерирует lss... Только как и какой кусочек нужен/выдрать? У меня ж среда - Geany, если что. :)
Цитата: Slabovik от 07 Окт., 2020, 19:02В asm у меня процедура чтения длинная, но я там все 8 каналов щёлкаю, раскладывая по кольцевым буферам, вряд ли для вставки в Си оно пригодно, разве что только сам кусочек, где непосредственно опрос идёт, но я его приводил ранее.
Ну так и нужен только опрос. Хотя не думаю, что сильный профит будет. ы. Что-то редко я осциллографом пользуюсь... Встал на data.
Я вот про этот кусочек кода Предлагаю его сделать таким По опусканию строба PB2 (RDY, Latch и т.п.) можно чисто визуально наблюдать при помощи осциллографа, что происходит именно эта процедура и, соответственно, измерить, сколько времени она занимает да и вообще как выглядит. Вторым лучом можно Clock смотреть или Data.
Кстати, метод работает при отладке чего-нибудь ещё. Когда надо посмотреть, какое место кода у тебя выполняется и сколько это занимает, опускаешь (поднимаешь) ножку какого-нибудь незадействованного порта, а по окончании поднимаешь (опускаешь).
Не, код вот такой как есть - он самый короткий. Тут просто чисто удивление, что ресурс вроде аппаратный (т.е. работать умеет полностью параллельно), но программа крутит пустой цикл и ждёт, пока "железо" отработает. Второй минус этого решения - привязка к конкретным ножкам. Впрочем, UART тоже можно в этом же режиме использовать (для засылки данных в 595), но суть остаётся та же.
А покажи просто, чего там вообще в .lss. Он выглядит как листинг с кодами? .lss ассемблерного файла выглядит так
Вид-на-lss.png
Сишный должен быть похожим по структуре. Конечно, метки и имена будут нечеловеческие, но кое-что можно подсмотреть.
Восемь транзисторов - это если один регистр с данными делать на оба индикатора. При этом скважность 1:8 получается, соответственно, яркость падает. Думаю, 1:4 всё-таки лучше, а значит, четыре ключа. Регулировку яркости, кстати, легко ввести. Входы OE нужно от земли оторвать, объединить и подать на них PWM с частотой, кратной частоте смены знака. При таком построении регулироваться будет всё, и светодиоды в том числе.
Во, отличная инфа для размышления. Завтра помедитирую :)
зы: не совсем понятно, это один бит даты или два... лучше Clock в таком ракусре глянуть, ну, или чуть переделать процедуру и посмотреть RAY (он же Latch, он же PB2) зызы: фронты малёха звенят. От звона хорошо помогают резисторы небольшого сопротивления (22~220 Ом), включенные недалеко от выходов (на моей схеме наверное видел). На крутизну они особо не влияют, но со звоном (суть - паразитные колебания, как в искровом передатчике) борются хорошо.
:-\ гляжу на STM32 и... полная непонятка а) что у них в качестве основной среды. Многие кивают на IAR, но... там лицензия на раб. место 3к$. Есть небольшой зоопарк из свободного ПО, но не ясно, что с отладкой и т.д. б) полная ж - у них шаг выводов 0.5мм. Если 0.8 на ATмеге ещё терпимо и можно даже на самодельную плату (ЛУТ без маски) их лепить, то что делать с 0.5 - вопрос больной... в) есть всякие платки, но... вот глядел сегодня на витрины. Нашёл такие (https://www.chipdip.ru/product/nucleo-f030r8), относительно недорого. С чем их едят?
А зачем такую плату, не, надо народную BluePill, зоопарк с IDE у них это прям беда и сплошные холивары, пытаюсь Makefile прикрутить, руки не доходят. В своё время взял по 5 штук BluePiill и BlackPill, ну и десяток таких же камней для них. Вот собирал Hldi, там GL850 (USB хаб) ещё торчит, не до конца собрал, потому что нет ещё транзисторов на IRF24N15 (мотор на 42в), но уже прошил и проверил работу энкодера, лазера. Индуктивности - то что было. :) ы. От звона в разрез линий PB2, PB3, PB5 резисторы 22-220 Ом, ближе к МК, так? ыы. uart_04_atmega328p_adc_spi.zip - без оверсэмплинга, с твоим вариантом SPI, все файлы. ыыы. 0,5 мм фоторезистом с принтером у меня нормально получалось, тут основные претензии к шаблону, лазерный принтер, который тонер не жалеет и пары ацетона (или аэрозоль) + сноровка, если нет ламинатора то лучше жидкий фоторезист, если есть Ordyl A340, с нашим НПВЩ - или как его - одно мучение... 4ы. Добавил осциллограмку двумя щупами.
Проведя немного времени за чтением документации, я правильно понимаю, что если я желаю выбраться по таймеру из режима Idle то нужно использовать второй таймер, т.к. системный клок останавливается, а второй таймер может работать от своего собственного клока (настроить надо). Т.е. нулевой и первый таймеры в режиме (команда SLEEP) останавливаются вместе с ядром?
Если это так, то получается, что для введения шума в ARef, также годится только второй таймер, а единственный его выход приходится на PD3, потому как PB3 желает занять аппаратный MOSI (Data) ↓ спойлер ↓
поэтому я ностальгирую по Z80, у которого только ядро, а нужное довешивать надо, но зато оно (довешенное) может работать независимо (в меру возможности и зависимости от CPU) друг от друга. Здесь же идеология наоборот - внутри есть много чего, но выбрать можно лишь что-то одно...
[свернуть]
Я правильно догадываюсь, или нет?
Но ведь тогда получается, что при организации программы "от таймера", вводить от него же шум в ARef бессмысленно т.к. это будет не шум, а коррелированный с выборками сигнал (ну те бесполезный). Период между замерами и период шума на ARef обязаны быть некоррелированными.
В общем, вот такая дилемма. Задействовав хардверный SPI (MOSI и SCK), лишаемся возможности повесить на эти выводы кнопки (знаю, у тебя их не было, но в моём варианте (https://anklab.ru/forum/index.php?topic=43.0), они именно там по соображениям, что они уживаются с разъёмом PGM), и если они будут нужны, придётся "распечатывать" PD.
Гы: генератор "шума" можно и на микросхемке сделать. Однако учитывая современную корпусобоязнь (ни одним корпусом больше!), решение не станет популярным. Или сделать из принципа "потому что я могу"?
Во, теперь понятно. Весьма шустро получается, и картинка как по учебнику. В 2 с небольшим раза быстрее, чем софтверный. ↓ спойлер ↓
Если маленько переработать, можно ускорить процентов на 20, но это будет предел. От счётчика в R2 можно полностью избавиться, задвигая в R1 "1" через флаг переноса при прочтении (-X), а в конце цикла проверяя R1 на нуль. Пара-тройка команд из цикла точно уберётся.
[свернуть]
после переработки
После убирания R2, стало короче. Эта процедура теперь выполняется 820 циклов против 953 в варианте, показанном выше. Но зато чуть длиннее - это способ избавиться от одного джампа, отъедающего время. Увы, за всё приходится платить. Можно ещё сократить время выполнения на 64 цикла, убрав NOP'ы перед поднятием Clock убрав NOP'ы получаем 756 циклов на весь 8-разрядный индикатор
[свернуть]
Итого: было 953, стало 749, быстрее на 21% :)
[свернуть]
о технологиях плат
с фоторезистом я баловался, но не смог получить стабильный результат. И от принтера нужной плотности не смог добиться (не от одного, их было много, лазерники, струйники). Минусом послужила ещё необходимость держать "химию", да и и фоторезист портится со временем (пара рулонов лежит, выбросить рука не поднимается). Т.е. нужна массовость, которой у меня нет и не предвидится. Поэтому редкие единичные платы ЛУТ'ом, остальное на завод - там маску сделают, металлизацию...
Мне затея с засыпанием не очень нравиться. Была где-то ветка со сравнением работы так и так, вывод был одинаково, что так, что так. Но проверить надо. +++ Trigger по SPI если в осциллографе поставить это тремя/двумя щупами же надо? Путаюсь я в названиях, кто из них кто, в осциллографе SCL,SDA,CS, When по timeout`у или по CS. +++ Вот еще что вспомнил, - даже пару слов писал на эту тему: из BluePill stm32 можно сделать отладчик Black Magic Probe, я его сделал, но дальше не продвинулся. :) Вот на хабре есть (http://xn--80aaaaknbcaapcpf0cjhhdcatp9akcqgv6lwexbg1f0a.xn-- -,,,-46gbaaaabbbffdncfacl1ambpcda5heh0djcidcg0a6a6bbsvekidfbbd2fi3cb8cdsadtbdb06ag4u4aje.xn-- -83dlpg1a1cdiaghqd1b8n.xn-- +++ triggerspi-87qa0ccx6cdc7fbf4gda7b6chdddvq5bi1aek3cdab8gye1q1b4g/%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%D1%89%D1%83%D0%BF%D0%B0%D0%BC%D0%B8%D0%B6%D0%B5%D0%BD%D0%B0%D0%B4%D0%BE?%3Cbr%3E%D0%9F%D1%83%D1%82%D0%B0%D1%8E%D1%81%D1%8C%D1%8F%D0%B2%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%D1%85,%D0%BA%D1%82%D0%BE%D0%B8%D0%B7%D0%BD%D0%B8%D1%85%D0%BA%D1%82%D0%BE,%D0%B2%D0%BE%D1%81%D1%86%D0%B8%D0%BB%D0%BB%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B5SCL,SDA,CS,When%D0%BF%D0%BEtimeout%60%D1%83%D0%B8%D0%BB%D0%B8%D0%BF%D0%BECS.%3Cbr%3E+++%3Cbr%3E%D0%92%D0%BE%D1%82%D0%B5%D1%89%D0%B5%D1%87%D1%82%D0%BE%D0%B2%D1%81%D0%BF%D0%BE%D0%BC%D0%BD%D0%B8%D0%BB,-%D0%B4%D0%B0%D0%B6%D0%B5%D0%BF%D0%B0%D1%80%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BF%D0%B8%D1%81%D0%B0%D0%BB%D0%BD%D0%B0%D1%8D%D1%82%D1%83%D1%82%D0%B5%D0%BC%D1%83:%D0%B8%D0%B7BluePillstm32%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%D0%BE%D1%82%D0%BB%D0%B0%D0%B4%D1%87%D0%B8%D0%BABlackMagicProbe,%D1%8F%D0%B5%D0%B3%D0%BE%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D0%BB,%D0%BD%D0%BE%D0%B4%D0%B0%D0%BB%D1%8C%D1%88%D0%B5%D0%BD%D0%B5%D0%BF%D1%80%D0%BE%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D0%BB%D1%81%D1%8F.:)%3Cbr%3E%D0%92%D0%BE%D1%82%D0%BD%D0%B0%D1%85%D0%B0%D0%B1%D1%80%D0%B5%D0%B5%D1%81%D1%82%D1%8C.).
Бумажка - вещь крайне необходимая. И не только в плане гигиены. Ещё она думать помогает и на ней можно рисовать :)
Введение шума в Ref имеет один крайне неприятный подводный камень в плане применения к AVR. Всё дело в её конструкции: Ref имеет только один вывод. Второй вывод Ref жёстко сидит на земле. И это перечёркивает всю задумку. Вот, полюбуйтесь
К-вопросу-о-тщетности-бытия.png
Суть проста. Если мы модулируем Ref только с одного конца (в нашем случае Ref+), то "сетка" из уровней - она как гармошка растягивается и сжимается. И получается, что Ref-, будучи привязанным к земле, не даёт смещаться нижним уровням. Если мы верхний уровень (Ref+) смещаем на 1 отсчёт (один младший разряд), то близкие к нижу уровни смещаются только на 1/1023 отсчёта. Т.е. вообще никак. Серединка смещается на 0.5 отсчёта...
Вывод: в данном применении не годится. Вот на PIC'ах было бы нормально - там выведены оба конца Ref (Ref+ и Ref-) и их можно двигать синхронно.
Смотря как на неё смотреть. Есть разные варианты реализации. Один - это крутить цикл, отвлекаясь на прерывания (опрос АЦП, вывод). В цикле можно что-нибудь делать, если есть чего. Вся работа по прерываниям - там и делается всё полезное. Второй вариант - не использовать прерывания вообще, разве что для синхронизации на вывод. Программа просто бегает по кругу опрос-расчёт-вывод. Засыпание не нужно (старые компьютеры как-то обходились без системы прерываний: Радио-86, Орион-128 и т.п.)
Если опираться на прерывания, то засыпание - это хорошая замена пустому циклу (делать-то всё-равно нечего). В втором случае это отличный способ синхронизировать бег по кругу с выводом на индикатор. В общем, вариантов много, спать не обязательно, просто иногда с ним (уходом в сон) проще.
А вывод конечно одинаковый для глаза будет, тут вопросов нет.
Триггер удобно поставить по ~|_ на том канале, который на CS (Ready/ST/PB2) смотрит. Тогда легко увидеть весь бурст.
ps. В попытках разобраться, как правильно округлять, сподобился изобразить покрасивее. Вот график, на котором я попытался изобразить дизер. ↓ спойлер ↓
Дизер-графики.png
[свернуть]
Цветные линии - напряжения на АЦП. Кружочки - отсчёты. Данные отсчётов в квадратиках выше и ниже - они в двоичном коде. Если без дизера, то результаты измерений были бы 0, 1 и 2. Ориентироваться можно по широким ровным полосам на фоне. А вот с дизером становится много интереснее, отсчёты перескакивают, но ведь именно это нам и надо ;)
В общем, исходное разрешение было 1. Делаем 16 отсчётов с целью увеличить разрешение вчетверо. Вместо 1 цена деления станет 0.25. Дальше двоичный калькулятор в руки (ох, нравится мне эта система: палочка есть - палочки нет :) )
Расчёт первый. Учитываем флаг переноса, когда "теряем" младшие биты при сдвиге вправо. Дизер-расчёт.png Вроде неплохо.
Был вопрос о том, надо ли учитывать округление (тот самый CF). Пробуем без CF Дизер-расчёт-без-CF.png Вот и ответ. 2,63 должно однозначно округлиться к 2.75, но без учёта CF оно осталось на 2.50
Впрочем, вопрос всё ещё спорный.
Ну, а правило для вычисления, надеюсь, стало более понятным: считаем сумму, прибавляем к ней половину от количества слагаемых, делим, добавляя CF от последнего сдвига вправо. Запятую ставим где надо :)
зы... зызы... Продолжим :) Я добавляю сюда т.к. ответов не было. Залез в микрокап и промикрокапал мысль о вводе дизера в сигнал. Микрокап говорит, что вполне реальная вещь. Сигнал подмешиваем на выходе масштабирующего усилителя. Дизер-схема.png Вначале хотел "квадратный сигнал" по причине, что его получить проще. Не прокатило (ну... не мой день). Но генератор хорошего треугольника можно сделать на одном корпусе с двумя ОУ внутри - это то, что надо. И будет он абсолютно независим и некоррелирован с частотой выборки АЦП - это второе "то, что надо".
Вот что получается в итоге (по данным микрокапа, конечно) Дизер-схема-графики.png Сигнал на входе АЦП аккуратненько смещается "треугольником в обе стороны". Амплитуда dither'а при этом совершенно не зависит от величины измеряемого сигнала. Необходимо только выдержать ёмкости. С5 и С6 образуют RC фильтр с выходным сопротивлением мастабирующего усилителя, в то время, как С3 и С7 железно завязаны на подключенные к ним последовательно сопротивления.
Думаю, можно заморочиться сделать ;)
А, да, ещё момент. Период дизера должен быть много меньше (в разы) времени накопления отсчётов. С другой стороны, пока АЦП работает (время, необходимое для выборки, 13 циклов тактовой частоты АЦП, которая может составлять от 50 до 200 кГц), дизер не должен изменить сигнал на заметную величину. По этой причине режим АЦП "Free Running" наверное неприемлем, нужно делать паузы.
Ну вот не хватает у меня понимания сдвигов, двоичных калькуляторов итп. И на ассемблер я смотрю и, к своему глубокому стыду ничего не понимаю :o Для меня функцию опроса АЦП написать на си уже праздник... С указателями до сих пор не понимаю главного, зачем они нужны? Зачем ссылки на переменные, например типа char, они же ещё больше места занимают. Суммируя, уходим от прерываний? Вводим счетчики для выполнения отправки по SPI и для чтения значений АЦП? ОУ добавить в схему я не против. Сегодня глянул ещё опору ref192gs - она подешевле, но и чуть похуже параметры, может её вместо 431.
Ну, ассемблер - это просто. Можно вполне позаниматься. Только у него есть последствия. Как мне сказал один хороший товарищ, "твоё мышление бенадёжно испорчено ассемблером" :) Собственно, так и есть :) Си мне даётся очень трудно :-\ А когда пытался Perl осваивать, чуть совсем не свихнулся :P Бросил - здоровье дороже ;D
Ссылки и указатели - это необходимые элементы языка высокого уровня. По факту, каждое объявление переменной является объявлением ссылки на неё, хотя и неявной. Явные ссылки используют для организации работ с высоким объёмом данных. Абстракция очень простая.
Переменная: я вот тебе архивчик (на пару помещений) принёс, тебе бумашка была нужна на третьей полке. Как закончишь, я всё унесу на место. :o
Ссылка: вот тебе адрес, гони туда (сам ножками), там третья полка, на полке бумашка, прочитай и дуй обратно к себе.
Я надеюсь, алгоритм рассчёта-то понятен?
Цитата: Slabovik от 09 Окт., 2020, 14:54считаем сумму, прибавляем к ней половину от количества слагаемых, делим, добавляя CF от последнего сдвига вправо. Запятую ставим где надо
Один кузовок MCP 6002 (я не ошибся? Два ОУ в одном кузове). И немного усложнить вход АЦП несколькими деталями для подмеса дизера. Алгоритм (не на си, а так, чисто алгоритмически, на си сам будешь ;) ) разрисую попозже, наверное уж на следующей неделе. А то завтра в поля подамся, очередную хуюмбулу точить :)
Да, REF'ы - они отличные в плане точности. Даже G вполне хватит - уже она на порядок лучше чем Tl431. Тут интересны не столько абсолютная точность, сколько температурный уход.
REF198? Она 4,096 вольта - как раз почти полный диапазон питания, саму её можно от этих же 5 вольт запитать (запаса хватает т.к. нагрузки на неё практически нет).
Есть более дешёвые LM4040 (.pdf (https://www.ti.com/lit/ds/symlink/lm4040-n.pdf)) - они в SOT23, по принципу как TL431, только без возможности регулировки.
Вот, чуть перерисовал модельку дизер-генератора. Жалко, но в свою плату я такое вставить уже не могу - они готовые. Впрочем, платы мастабирующих усилителей только в проекте - можно в них предусмотреть.
Тут я скорее не понимаю где и как указатели использовать, если переменная объявлена глобально в функциях она тоже доступна... Генератор наверное можно и на народной 358, мы определились будет двухдиапазонное измерение или хватит 2-х каналов АЦП? Можно и REF198. LM4040 у своих нашёл тыц (http://www.nvcomponent.ru/?search=ref198&search=LM4040). спс.
Если делать дизер, то без двух диапазонов нормально выходит 4 десятичных разряда. Я у себя делаю два диапазона потому что не предусматривал дизер и у меня горят всегда только три цифры. А 4-разрядный индикатор использую только для того, чтобы не двигать точку. Это заметно облегчает восприятие показаний (тут уже эргономика вмешивается).
LM358 можно попробовать. Но у неё ограничение сверху до полутора вольт, можно среднюю точку чуть пониже повесить (точка Half), резисторы примерно с отношением 6:5 (т.е. 1,2 кОм верхний, 1,0 кОм нижний). Это даст среднюю точку на уровне 5*1.0/(1.0+1.2)=2.2 вольта. Если генерации не будет, пробовать понемногу уменьшать R4 (33к на последней схеме). От его отношения к R3 зависит амплитуда треугольных колебаний, если треугольник "наткнётся" на клиппинг, генерации не будет. Чем меньше R4, тем меньше амплитуда, но больше частота. А от R5 зависит скорость роста напряжения треугольника, что тоже прямым образом влияет на частоту.
Да, указатели... Явные указатели чисто идеологически используются для сложных структур данных. Когда есть какой-то "неопределённого объёма" набор данных (в памяти, в стеке или ещё где, причём его местонахождение - не константа а меняется по мере прихода-ухода данных), то указатели становятся очень удобным средством. Самые простейшие указатели применительно к микропроцессорам - указатель стека (регистр стека) и указатель исполняемой команды (регистр адреса). Регистр стека показывает (содержит адрес) на верхушку стека (область памяти). Регистр адреса, он же Program Counter (PC), показывает на ячейку памяти, из которой выполняется очередная команда процессора.
Ведь само понятие указатель - это чисто логическая штука, она в голове. Физически указатель - это адрес. Вот здесь происходит загрузка указателя в регистры Digits2Led - это адрес памяти, которая выделена под буфер "экрана". Фактически константа т.к. буфер экрана "закреплён" строго в одном месте памяти. А Ttal_Digits - это длина буфера. Таким образом указатель показывает на конец "экранной памяти", здесь это просто потому, что данные в регистры пересылать надо с конца - там так запаяно.
и затем "взятие" данных их ячейки, на которую показывает этот указатель т.е. мы по указателю X (бмажка с адресом) сходили по адресу, прочитали то, что там записано и переписали себе в локальную переменную (регистр) R1. С R1 дальше и будем работать.
А вот здесь вот тоже произошло определение локальной переменной (выделили под неё регистр R16) и присвоение этой переменной начального значения - константы Total_Digits (которая в моём случае равна 8 - это определено в самом начале программы). Локальная она потому, что я поработаю с этим регистром (у переменной-то и имени фактически нет), а по выходу из процедуры "забуду" про данные из него - они больше не нужны и регистр можно использовать для чего-нибудь ещё.
В Си-шных эквивалентах это выглядело бы примерно так. ----------------------------- LDI XL,low(Digits2Led + Total_Digits) LDI XH,high(Digits2Led + Total_Digits) ~~ unsigned shortint X = (&Digits2Led)+Total_Digits; ----------------------------- LD R1,-X ~~ R1 = --*X; ----------------------------- LDI R16,Total_Digits ~~ char i=Total_Digits;
Ух и разговорчик у нас получается, - пример на asm, и я неуч в си. Перечитаю пару раз ещё пару раз, хотя вот вчера до ночи вникать пытался, что нашёл по указателям... туго. Не я конечно же посмотрел что есть LDI, LD, CBI... +++ Если 358 неустойчива может быть, то ладно, 6002 есть. Мысль была на две платы разбить, дизер+масштаб_ус+шунт+опора на одной, остальное на другой, опора под вопросом. В своем варианте вывод был двух светодиодов на всякий случай, тут я не совсем определился, LM35 можно ещё добавить, или просто терморезистор на 10k. Ну и переключение обмоток как опция, включение/отключение выхода?
Не, 358 устойчива, просто она не совсем R2R, что при низком напряжении питания надо учитывать. Генератор спаять и на проводках можно, чтобы просто проверить - он не сложный, пять резисторов и два конденсатора (Half тоже надо организовать). Убедиться, что он работает (а если не работает, то наладить), да помучить немного :)
Дизер лучше на платке проца разместить. А вот масштабирующие можно и наружу. Плюс такого решения - мастабирующие не будут греться от индикаторов, да и заменить их легче (например, немного переработав или ещё чего).
Управлять релюшками, коммутирующими напряжение, с индикатора я бы не решился, да и вентилятор лучше сделать самостоятельным узлом (тиньки даже хватит, а если не бояться "тёплолампового аналога, то я там где-то схемку прорабатывал - работает прекрасно). А вот включение-отключение выхода - вещь полезная. Сюда же можно подумать, как замер тока ограничения сделать (есть два варианта: первый - устраивать к.з, второй - измерять задающее напряжение).
Собирал, помню, это я только к кикаду подступаться начал, первый вариант в аттаче, был ещё один, но почему-то кикад плату не открывает, ругается на версию.
Цитата: Slabovik от 07 Окт., 2020, 19:02Страшно мне смотреть на выкрутасы с ADMUX. Знаю, что для данного применения нормально, один фиг там управляющие биты по нулям и от нулей отличаются только сами MUX, но... правильно было бы читать ADMUX, изменять MUX (сле-едующий! (https://anklab.ru/forum/Smileys/fugue/smiley.png) ) и пихать его обратно. Ну или применять здесь дефайны настройки АЦП. При этом завязать номер MUX на переменную-указатель adc_channel (хотя, вынимая номер из MUX всегда можно знать, какой АЦП сейчас был включен.
В реальности же это работоспособности не вредит, только "причёсывает" стиль написания.
Вот эту строку у немца я до конца не понял, её бы посидеть покрутить... Но она мне нравится, и работает :)
Добавил её в свой код и изменил ADC_vect: Так лучше? Вот такой lss получается: +++ Начал отрисовывать, правильно? В опору ничего не надо?
Вот это '<<' побитовый сдвиг влево. Запись простая: [то, что двигаем] << [на сколько двигаем]. MUX0, MUX1, MUX2 - это имена битов в регистре управления выбором канала АЦП. В числовом выражении это 0-й бит, 1-й бит и 2-й бит соответственно. Соответственно, запись, 1<<MUX2 означает, что берём единицу и сдвигаем слево побитово на два (потому что определено, что MUX2=2) В бинарном виде получается 00000001 << 2 = 00000100. Т.е единичка "уехала" на две позиции левее. С MUX1 и MUX0 то же самое. Только поскольку MUX0 равен нулю, это значит что единичка никуда и не уехала - осталась сама собой. 00000001 << 1 = 00000010 00000001 << 0 = 00000001
Далее вот это '|' - операция "побитовое логическое или". Суть простая: в результате соответствующая позиция бита равна 1, если хотя бы у одного операнда в этой позиции есть 1. Очень просто: 0 | 1 = 1, 1 | 0 = 1, 1 | 1 = 1, 0 | 0 = 0
С восемью битами то же самое, только в расчёт идут не все биты сразу, а по позициям. Напишу результат также в столбик (нагляднее) 00000100 | 00000010 | 00000001 = 00000111 Очень прикольно :) Скобка закрылась с результатом '7' (00000111bin = 7) Но на деле интереснее именно бинарный результат.
Далее операция "~" - побитовая инверсия. В общем, те биты, что были '1', становятся нулями и наоборот. Было 00000111, стало 11111000 :o А зачем? А затем, что это будет применено в качестве "маски", при помощи которой биты MUX будут обнулены с дальнейшей целью записать туда новое значение так, чтобы другие биты регистра управления АЦП остались нетронутыми. Для этого используется следующая операция: "побитовое логическое и", суть которой в том, что результат (побитовый, точно также как и в других логических операциях) равен в соответствующей позиции байта только тогда, когда биты оба операнда равны 1, иначе в бите результата будет 0. Получается тоже интересно.
У нас слева в "маске" пять единиц. Первый операнд - содержимое порта ADMUX. Второй операнд - вот эта маска. После того, как мы их '&', результатом будут не изменённые (взаправдашние) левые пять бит из порта ADMUX (либо нули, либо единицы - нам пофиг, главное, что именно они, а не нечто другое), а справа гарантированных три нулевых бита. И позиции этих битов тютя-в-тютю на позиции номера канала АЦП (это как раз выше определили).
Далее, со всем эти результатом - пятью битами из порта ADMUX и свободным местом для номера канала, снова делаем логическое "или" уже с новым номером канала. По факту он просто прописывается (бинарно) вместо трёх нулевых битов (он конечно сам может быть нулевым, но тут главное, что эти новые биты точно соответствуют нужному нам номеру из "channel"). Ну и последнее ADMUX= - это запись всего этого результата обратно в регистр. Пять "левых" битов остались неизменными, три бита справа содержат новый номер канала. Профит :) Ты пишешь adc_select_channel(channel); а компилятор это видит как ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel);
зы: я в объяснении использовал три MUX, 4-й добавить туда самостоятельно, думаю, не составит проблем.
Видно, что переменная adc_channel (да и наверное adc_channels) двубайтовые. Это излишне - ведь там никогда не бывает больше 16 в принципе. Надо бы им в определениях чётко прописать, char мол это, и даже беззнаковый.
Схема imho нормальная. Видятся лишними C1 и C3 потому что C7 и C8 - это они и есть.
А мне вот жужжит мысль "поставь-ка DD3.1 (https://anklab.ru/forum/index.php?msg=271) первой в цепочке, а не последней, как сейчас". зачем - не пойму, просто если как изображено по схеме, первыми в "сдвиг" должны уйти данные для светодиодов и транзисторов, а потом данные на сегменты индикатора, а если переставить - светодиоды и транзисторы будем вдвигать последними. Чисто технически это совершенно пофиг, в какой последовательности ставить, но мысль основывается на том, что DD3.1 может оказаться проще чисто по разводке поставить ближе к процессору. Можно поставить себе крестик, что в этом месте вдруг что-то поменяется и быть готовым к этому.
А опора остаётся как есть. Т.е. что есть - то и ставишь. LM4040 наверное в самый раз, а если REF19x засунуть - это вообще шикарно :) Абсолютное значение напряжения роли большой не играет (это для выходного ЦАП было бы важно) т.к. всё-равно всё приводится к нему мастабирующими усилителями, крутить которыми можно как угодно. Единственное - это то, что значение опорного напряжения я бы выбирал повыше - проще получить стабильный ноль. Как раз идеальное значение в районе 4-х вольт (и уже много, 4 мВ на степ, и до питания ещё не достаёт, что опору можно прямо от этих же +5 и питать). Но и 2,5 и 3 тоже будут нормальным вариантом.
Тут получается как, - по отдельности вроде понятно, в кучу сгреблось - нет. Вот упростим (предположим, что у нас только MUX1 и MUX0): теперь всего может быть 00, 01, 11, 10, так? Значит подставляя в этом сокращённом варианте в channel от 0 до 3 получим эти четыре комбинации?
Цитата: Slabovik от 11 Окт., 2020, 22:14Соответственно, запись, 1<<MUX2 означает, что берём единицу и сдвигаем слево побитово на два (потому что определено, что MUX2=2)
Не понял откуда видно, что MUX2=2?
+++ Начал разводить, пока вот это получается, дороги крупновато взял, 0,44мм, надо на десятку меньше наверное. Поверхностным монтажом выводные потенциометры сделал, не лучший вариант, но эту плату наверное оставлю без индикаторов, те это макетом будет. В окончательном варианте индикаторы будут на другой стороне.
Да, но не совсем. Комбинации из MUX1 и MUX0 могут быть именно такими. Но т.к. в выражении формируется именно маска (как бы "закрывающая" часть битов от изменения), то результатом получается, что channel можно выбирать только из этого получившегося ряда. ↓ спойлер ↓
на самом деле это выражение всё-таки маленько "дырявое" по той причине, что маска на считанное значение ADMUX накладывается, но за значением переменной channel следить нужно программисту, не допуская её увеличение свыше, чем есть каналов у АЦП.
MUX2 - это имя, определённое через #define в заголовочном файла с настройками процессора. Там где-то в начале программы у тебя <include xxxx.h> есть, называется обычно по названию использованного процессора. Там внутри куча сделана дефайнов. Можно найти этот файл и посмотреть на все дефайны - оно бывает полезно.
А то, почему MUX2 равен должен быть именно 2 - это уже от аппаратуры зависит, т.к. оно внутри так работает (у атмела хорошие даташиты с описанием всех узлов, что внутри проца есть). Вот, например, на Мегу 8 (https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2486-8-bit-AVR-microcontroller-ATmega8_L_datasheet.pdf)-ю, про устройство регистра ADMUX там на 199 странице (по-английски, конечно). На другие там такие же.
Ну так я первым делом смотрел значения из таблиц и не пойму где тут MUX2=2? Ведь только 0 или 1 может быть? Вот себе выписывал: Размер платы пока не планировал, корпуса всё-равно ещё нет, но лицевая панель точно в планах больше чем 80х70, твоя плата довольно-таки большая, ну и трансформатор габаритный.
Ну, то, что он 2-й бит, это из определения следует, в железе так. Вот даже в табличке 3 2 1 0 самой первой строчкой, и в даташите регистр разрисован точно так же. Потому и два. А #define MUX2 = 2 - это в .h файле прописано.
Ну дак он и не может каким-то произвольным быть значением. Он (MUX2) - номер бита, не более. Его значение - константа, и она равна 2. MUX2 (а также 0,1 и 3) как раз олицетворяют понятие "адрес", а не сами данные, что по этому адресу лежат.
Да, чисто логически верно. Ещё удобнее будет написать ADMUX = ADMUX & 0xF0 + channel
Вся эта изначально длинная строка придумана из-за того, что MUX0(1,2,3,) могут "вдруг" (в случае трансляции для другого процессора) находиться в другом месте регистра, т.е. быть не первы-вторым... битами, а, например, четвёртым-пятым-шестым... В случае длинной строки при смене проца не придётся искать это место и переписывать под новые значения MUX, а случае, как записано числами - придётся. Это единственная причина, зачем изобретают велосипед с этими длинными записями. ↓ спойлер ↓
а на деле в этой гипотетической ситуации, когда MUXx имеют другие значения, значения chsnnel тоже должны пересчитываться, так что строка "немца" не является универсальной в этом плане. Поэтому можешь спокойно вставить мою - работать будет точно так же :) Но не забывай следить, чтобы channel вдруг не выскочил за пределы 16 (0x0F). Поэтому, я бы засунул туда "предохранитель" ADMUX = ADMUX & 0xF0 + (channel & 0x0F)
Этот "предохранитель" интересное место, и если вдруг ты используешь только первый и нулевой каналы, причём последовательно, то смену каналов записать можно ещё интереснее. ADMUX = ADMUX & 0xF0 + (channel++ & 0x01)
при этом channel надо определить как байтовую беззнаковую величину. И никаких более проверок if делать даже не надо.
Ещё интереснее может быть так ADMUX = ADMUX & 0xF0 + ((ADMUX+1) & 0x01) но тут уже запутаться можно т.к. не зная железо не совсем понятно, почему в этом месте можно +1 :)
[свернуть]
А, да... вот та мысль, про которую говорил чуть ранее
Ну хоть кое-что с этой арифметикой проясняется в голове, спасибо. ADMUX = ADMUX & 0xF0 + channel Ага, ну тут даже проще. ADMUX & 0xF0 побитовое сравнение с 11110000, те первые четыре единицы не трогает, только последние, плюсом подкидываем канал, который как раз и затрагивает четыре последних первых бита, понятно, что за channel в этом случае надо следить. +++ Изменил на:
Ну, со скобками компилятору виднее (обычно да, рекомендуют скобки рисовать даже в явных случаях для того, чтобы не было разночтений. А здесь похоже, что компилятору не ведомо, что channel не может быть больше какого-то небольшого значения, поэтому он просит указать приоритет операций явно).
По деталям, поехал взял три мк, конденсаторов насыпал, MCP6002T-I/SN заказал горсть, и пяток LM4040DIM3-4.1, приедут после 22-го. C REF беда, ну может доеду как нибудь до чип-дипа, уж пару-тройку надо.
Интересно, что чуть ли не вся линейка (по объёму памяти) контроллеров имеет одну и ту же цену. Поэтому хочется всегда взять побольше - обыкновенное нормальное чувство. С другой стороны, кода ожидается от силы пара-тройка килобайт. Впрочем, я бы сам 328-х взял пачку и в ус не дул :) Дырочек бы только на плате сделал... для опытов :)
Я тут другое подумал. Мне понравилось - подумал ещё. Дело вот в чём. MCP600x хотя и неплохие ОУ, но обладают довольно большими дрейфом и напряжением смещения. Дрейф температурный, это означает, что при изменении температуры изменится напряжение смещения, а напряжение смещения - это систематическая ошибка на выходе усилителя (т.е. либо плюс сколько-то, либо минус) особенно заметная на усилителе с токового шунта.
Есть ОУ получше. Например, AD855x (8551, 8552, 8554) (https://www.chipdip.ru/product/ad8551ar). Обладают обалденными характеристиками - смещение и дрейф примерно в тысячу раз меньше, чем у MCP, но и стоят дороже. Фишка в том, что и те и другие пересекаются по кузовам SOIC-8. И, если предусмотреть на плате усилители в этом кузове, то можно поставить либо одни, либо другие, в зависимости от ситуации. И таких взаимозамен может быт довольно много, не только вот эти AD'шки). А если зацепиться только за 6001 в кузове SOT23-5, то выбор замен станет заметно уже, хотя тоже не нулевой. Например, OPA333 весьма вкусен, есть ещё...
Да, я думаю, не стоит торопиться тратиться. Запаять MCP на место измерительных, а при удачных опытах попробовать ОУ поточнее. Если же опыты будут неудачны (бывает, и нередко), то для 10-битного разрешения и MCP хватит.
А, да, ещё вопрос. Эта штука для блока питания, но шунт хочет иметь свой собственный? Или будет измерять падение на шунте самого блока?
Ну 6002 в soic-8 у меня просто нет, и они не пропадут, да на AD поглядывал конечно, знаю там характеристики на пару порядков лучше. Думаю не проблема если будет два шунта, сейчас-то так и есть, только один момент напрягает - земля на корпусе. Про температурный дрейф - там в немецком коде предусмотрен замер температуры МК (ADC8) и калибровка, относительно этого.
Калибровка вынудит работать с Float-числами. Не хотелось бы... Не то, чтобы нельзя, но это очень ресурсоёмкая штука для данного процика.
А ещё. Включение-отключение выхода. Тут прямо на плате? Релюшка объёмная, но вполне возможная.
И момент с "землёй" не там, где надо решается просто - используется свой собственный источник питания для этого хозяйства. Иначе ничего хорошего не придётся ждать - перетоки токов по земляному проводу испортят всё, что можно. Соединение "земли" с основной схемой должно быть только в одной точке.
Я продолжаю медитации, уж немного терпения. Много вопросов, которые хочется угадать, чтобы угодить. А если буду делать по-своему, получится очередная вундервафля, бессмысленная и беспощадная :)
В общем-то у меня тоже вундерфавли получаются, если помнишь БП с дежуркой, с отрицательным напряжением, обратноходом в качестве основного питания... Ошибкой было желание всё впихнуть на одну плату. Тут хотел применить вот такое китайское чудо на 5 вольт (купил в основном из-за того, что самому боязно изготовить мелкий трансформатор, да и брать их где-то надо). ы. Мы же не спешим, блох вроде не видно? ;D
Т.е. плата индикатора отдельно? Или этажерка? Для разных опор нужен пересчёт резисторов масштабирующих усилителей. R25 не нужен. Sleep можно подключать напрямую. Или вообще не подключать (внутри есть подтяжка). Про C1, C3 не помню, говорил ли - не нужны они при наличии C7, C8. Vin для U5 может быть напрямую от 5V1, нет никакой необходимости его от AVCC питать. С10, С14 выглядят огромными. Они такие надо? Я бы туда SMD-шных чуть (со всяких девайсов можно наснимать от 1 до 10 мкФ в формате 0805)...
Во, вспомнил (и добавил сюда, чтобы не писать лишний пост). Небольшой ликбез на тему "Как правильно спать на AVR MCU (http://we.easyelectronics.ru/AVR/avr-power-management-ili-kak-pravilno-spat.html)". Просто оставлю здесь, т.к. даташит на Мегу в этом месте написан каким-то не очень понятным языком, а здесь немного разжёвано по-русски. Главное в Си - не забыть подключить библиотечку "sleep.h"
Сам я пока использую только ADC Noise Reduction, но таки предлагаю сделать режим Idle для всеобщей синхронизации. Бегло алгоритм я уже рассказывал, а подробнее... подробнее это писать надо :)
Покажу свою - см. вложение. Индикатор уже показывал - там всё то же самое. Часть с микропроцессором.
Интерфейс индикатора зацеплен как софтверный. Можно перецепить на хардверний (MOSI/SCK), тогда освободятся для чего-нибудь PB0 и PB1, но потеряются кнопки, которые слева. На самом деле в этом варианте они не особо нужны, т.к. есть кнопки справа около реле - они тёпло-ламповые, аналоговые. Но можно и наоборот - убрать кнопки у реле и переключать их от проца.
Релюхи добавлены из следующих соображений. Первая - отключать нагрузку от выхода. Весьма необходимая вещь. Вторая связана с тем, что большинство относительно простых конструкций лабораторников не предоставляют информации об установке тока ограничения и устанавливать его приходится при помощи закорачивания выходных клемм блока. Релюшка это делает сама - только нажать кнопку и крути ручку. Но только при отключенной нагрузке. Если нагрузка подключена, закорачивания не будет (это на случай, что нагрузка вполне может быть аккумулятором, либо с "батареей" конденсаторов на входе).
До сих пор не уверен, что получится чёткий 0 даже на этих ОУ. В общем, всё, каждая деталька обсуждаема, пока не поздно... Реле - это типа таких (https://finder-relay.ru/katalog/products/promezhutochnye-rele/-40-serija-miniatjurnye-pcb-rele/-405270120000-rele-s-2-perekidnymi) (стандартизированы в размерах)
Согласен - мелковато, но.... читается. Надо выбрать "скачать" (нажать на имя файла, а не на картинку) и оно откроется уже локально в полном разрешении (просмотрщиком, который у тебя на компе/мобильнике). Иначе движок форума его вписывает в имеющуюся ширину экрана, а это действительно может быть нечитаемо... Да и номиналы всё те же. Около ОУ только посчитал, надеюсь, не сильно ошибся... Тут главное - концепт.
Но если не получится - сделаю побольше...
Да, я что-то сходу не нашёл... ты какой предделитель для SPI иcпользуешь? /16?
зы: ну вот, уже ошибку нашел. Как раз в номиналах...
Ну да, скачал более-менее видно, буду кряжить свою схему :) Вся настройка SPI и прерывания по таймеру 1000Гц: Отправка в прерывании, которое ты редактировал. +++ Пересократил ... слово то какое, ... оверсэмплинг, вот так оно работает, без сглаживания:
Прежде чем коряжить ты меня проверь. Я сам мог накоряжить :P Щас выверять надо :) Номиналы я уже нашёл (завтра исправлю и пересчитаю) - там фигня по ним в ОУ напряжения какая-то нарисована. Но номиналы не суть. Суть - соединения. Любые вопросы...
Меня больше интересует, нужны ли кнопки? Изначально я их впихал с целью выводить на индикатор установочные значения с задатчиков (опорников) блока питания. Нажал кнопочку - он тебе данные (пересчитанные конечно) с соответствующего опорника показал. Потому у меня в том прототипе все восемь каналов задействованы. Это позволит, например, устанавливать ограничение тока вообще без замыкания, или напряжение в режиме ограничения тока (замыкании). Но сам блок на это должен быть рассчитан. Многие блоки не будут иметь такой возможности - вывести напряжение со своего опорника.
Ага, с частотой понятно - оно не написано явно. Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI. Вот, почему так быстро.
Ну я же тоже проверяю что и как, обвязка ОУ у меня осталась от подбора собранного варианта, её конечно надо пересчитать. На кнопки согласен, лучше порой лишнее предусмотреть, чем потом навешивать, реле тоже ok. Просто именно этого размера у меня нет, наживное. Вот у себя увидел, что генератор не правильно, полпитания на 5-ую ногу забыл. ы. Что на счет процедуры оверсэмплинга?
0805 у меня только 8 пик, около кварца будут, все остальные размеры резисторов, конденсаторов = 1206. Я 1206 закупился так, что хватит на долго. :) Да, там с номиналом переборщил, у меня тантал 47х16 и 100х10 есть, а электролиты smd как раз 220х10 и 68х20. ыы. Искал подстроечные резисторы в smd исполнении, немного офигел от цен. 3ы. Режим standby и реле на включение трансформатора/ИИП? 4ы. Крайний код и видео к нему (https://www.youtube.com/watch?v=hJSyaevqvCo). (генератора пока нет).
Сегодня у меня день неработающей головы. Торчит чего-то на плечах, а для чего - сам не помню... ну, ем туда, да...
Снял картинку своей обновлённой софтовой процедуры запихивания в индикатор. Типа хвастаюсь, что действительно быстрее стала. Не, кварц тот же самый на 14,318МГц (когда-то кварцы на эту частоту были страшным дефицитом, что даже в массовый ZX Spectrum ставили 14.000 МГц, что приводило к немного неверному формированию времянок для телевизора).
Выдача-8-байтов-в-индикатор.png
На заваленные фронты не смотрите - осциллограф относительно низкочастотный так что в этом месте это не показатель.
Хочу зафиксировать один момент. Поскольку у нас есть незадействованные ноги портов, для отладки на них очень удобно выводить какие-нибудь сигналы. Например, ставим "1" на выбранную ножку в начале какой-нибудь интересной процедуры, а в конце снимаем. Или цифру вывести, или ещё чего - вариантов множество. Вот, я например, захотел оценить, сколько времени занимает процедура опроса АЦП и сколько по времени длится подсчёт результатов по всем кольцевым буферам. Вывел три сигнала. Один - ставлю единичку, когда производится вход в процедуру. Второй - ставлю 1 когда "набиваю" регистры АЦП для запуска и снимаю её, когда прочитал из АЦП результат. Третий - ставлю единичку перед началом пересчёта сумм и убираю её по окончании. И, если посмотреть на эти сигналы осциллографом, становится всё очень и очень наглядно. Вот
ADC_work.png ADC_summ.png
Чётко видно, что АЦП работает примерно 63-65 мкс, чтобы оцифровать сигнал. АЦП работают в режиме Noise Reduction (проц отправляется поспать с выходом из сна по прерыванию). Все восемь каналов получается опросить за примерно 520 мкс, остальное время уходит на пересчёт кольцевых буферов.
Таким образом можно контролировать любую процедуру (с четырьмя лучами оно ещё интереснее, но здесь у меня только два). Ещё интереснее иметь кнопочку с прерыванием, когда интересует результат расчёта какой-нибудь процедуры (регистр там или ячеечка), то можно в конце процедуры вывести нужное на индикатор/порт и встать в Sleep, ожидая нажатия кнопочки. Так получится чисто визуально прочитать, что там есть. Метод эффективный, всё-таки живое железо :)
Напоследок просто памятка (больше мне самому надо) с установкой фьюзов. Установки не догма, но...
Она близко к 4.096. Имеющиеся подстроечники легко компенсируют разницу между 4.00 и 4.096, а 4.096 может оказаться дороже.
А то, что SMD подстроечники дорогие - это да. Ставлю обычные. Подгадывают так, чтобы подстроечником нужно было только компенсировать разброс параметров деталей - тогда, поскольку подстроечники нестабильны, вклад подстроечника в энтропию не особе сказывается, а регулировка получается точнее.
Релюшки у меня на схеме только на установку подкл-откл выхода блока и установку тока. На включение силовой части нужна ещё одна. Но мне кажется, простой выключатель всего - он интереснее. Брутальный и надёжный. А вкл-откл выхода нужен чтобы обесточивать питаемое без отключения блока. Как он должен правильно работать - всё ещё думаю. Просто тумблер(кнопка с фиксацией) не совсем нравится, но и триггерная нажималка имеет свои недостатки. Но первый вариант проще и от проца не зависит.
Процедуру посмотрел только краем глаза. В прицнипе, как и было: накопление в ячейке и затем расчёт по заполнении. Тут главный критерий - правильность результата, а это ты сам на живом лучше оценишь
Не пойму как проверить засыпает ли? На основе этого примера (https://microchiptechnology.sharepoint.com/sites/DeveloperHelp/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2FDeveloperHelp%2FShared%20Documents%2FWiki%20file%20sharing%20folder%2FBox%20Files%2Fadc%5Fnoise%5Freduction%5Fexample%5Fcode%2Ezip&parent=%2Fsites%2FDeveloperHelp%2FShared%20Documents%2FWiki%20file%20sharing%20folder%2FBox%20Files&p=true&originalPath=aHR0cHM6Ly9taWNyb2NoaXB0ZWNobm9sb2d5LnNoYXJlcG9pbnQuY29tLzp1Oi9zL0RldmVsb3BlckhlbHAvRVk2OFhKaldxQ2RJdU9vYk02eFhGVjBCelZNU2tObC1nUDhCVUJjSEVWQUFsQT9ydGltZT00WUNScDJwdzJFZw) добавил в свой код, как там показано. Тыц (https://microchipdeveloper.com/8avr:adcnoisereduce). main.c из примера: Строки set_sleep_mode(SLEEP_MODE_ADC); sleep_enable(); добавил перед включением прерываний. ADCSRA |= 1 << ADSC; сразу после чтения из ADC, в прерывание. Строку sleep_cpu(); в тело основного цикла программы, перед обновлением значений для вывода на дисплей. +++ Вот мой вариант, #define NOISE_REDUCTION 1 тоже оставил.
Мнэээ... мне реально трудно разбираться в этом коде, ведь нужно его в голове держать, а я только пробегаюсь взглядом. Я смотрю, ты в инициализации ADC сразу делаешь режим FreeRunning. А... зачем? Алгоритм должен быть такой. В инициализации настраиваем регистры ADC, но НЕ запускаем (т.е. никакого FreeRunning) Разрешаем команду Sleep. В процедуре прерывания от ADC ничего не делаем. Совсем (это не обязательно, можно и прочитать, что там пришло, но... зачем? Там С родит гору лишних пуш-попов при этом, оно нам надо? Всё-равно программа стоит строго в определённой точке - нужно её просто с этой точки продолжить) Это не значит, что процедуры нет, процедура есть, но там заглушка типа "SEI - IRET"
В основном цикле засовываем в регистры ADC нужный канал, засовываем разрешение на работу и валимся в Sleep. Следующей за Sleep командой будет команда чтения данных из ADC, рассовывание по местам, подсчёт...
Вот, я сегодня нарисовал блок-схему алгоритма, по которому должно всё бегать. Основа всего - таймер, формирующий "системный тик" ::) Он синхронизирует всё (а "всего" здесь совсем чуть-чуть). Суть такова. Настраиваем таймер, делающий прерывания с частотой 1 кГц. И отправляем процессор спать (режим Idle, как самый быстрый в плане скорости выхода из него, либо Advanced Sleep - скорость выхода из которого всего 6 процессорных циклов, но работает только таймер 2 плюс тактирование обязательно от внешнего кварца. Думаю, idle универсальнее.) Как только случается прерывание, в самой процедуре прерывания ничего не делаем - сразу выходим. Делать там что-то вообще незачем т.к. прерывание - просто метод "подождать" в определённой точке круга программы.
Прерывание случилось - сразу бежим выставлять очередную цифру на индикатор. Выставили цифру - идём заводить очередной опрос АЦП (тоже с прерыванием и режимом Noise Reduction). Опросили АЦП - сложили в буфер (организация буфера - вопрос отдельный), посчитали чего надо и если надо, и, если у нас показывалась 2-я или 3-я цифры - готовим новое изображение для индикатора. (На деле надо бы чуть посложнее синхронизировать, но на первое время и так хорошо). Оно будет запихнуто туда как раз вовремя.
Потом можно почитать кнопки (просто опросить порт, без прерываний), обновить на них счётчики антидребезга, сделать какую-нибудь указиловку для режима отображения на индикатор или дёрнуть ножкой порта на управление чем-нибудь... В конце концов, сделав этот круг, снова впасть в спячку - до следующего тика...
зы: и ещё пересчитал и, надеюсь, правильно изобразил номиналы в измерительном усилителе. Расчёт от условия опоры 4,096 вольта. 12,5 - это 10 кОм плюс половинка от подстроечного 5 кОм. 14 кОм - это 12,5 кОм + 1.5 кОм на выходе ОУ. Банальный делитель, в общем....
Забыл инициализацию переделать, и в задатчике опоры ошибка была, вот так сейчас: Оно-то работает, просто не пойму как узнать, на самом деле режим adc noise reduction включился или нет? Понял, ты предлагаешь оставить лишь одно прерывание 1000 Гц, остальное в коде, мне сейчас интересно можно ли оставить прерывание как в примере от микрочипа: грубо говоря вот это в основном цикле программы: А пробуждается автоматически после завершения преобразования, так? Обработка прерывания так:
Ну у меня почти так изначально и было, всё выполнялось в одном прерывании, которое было привязано к таймеру 1000 Гц. Тут от перемены мест ничего не меняется, ну почти... Потом я добавил прерывание ADC_vect .... :) Больше из интереса как это всё будет работать. Посижу посоображаю как вернуть всё в спять... Кстати забыл уже на каком этапе в spi появилась pgm_read_byte, это кажется ещё на коте... ы. Последние дни как-то тоже всё кругом, сосредоточиться и спокойно посидеть - никак.
Оставить-то можно, но... зачем? Одно дело, когда прерывание асинхронное (сигнал пришёл совершенно асинхронный к циклам контроллера) - надо принять, что-то быстренько посчитать и оставить след в системе - запись о свершившемся событии. Здесь же прерывание представляет собой исключительно "спусковой крючок - разрешающий сигнал" для продолжения процесса. Всё, что есть внутри прерывания ровно также считается и снаружи прерывания, ну уж так получается. Исходя из этого, помня о правилах "хорошего тона" в обработке: в прерывании работы нужно выполнять минимум, чтобы как можно быстрее покинуть процедуру и дать возможность вовремя обработать другие прерывания. Понятно, что здесь кроме одного этого прерывания ничего толком и нет, но это не повод расслабляться и нарушать эти правила "хорошего тона".
Если пойти от обратного, то в прерывание можно засунуть вообще всё, оставив снаружи единственное while (1) sleep(); Но это, вкупе с тем, что прерывание одно, как раз и показывает, что всю работу можно делать и снаружи.
В "больших" системах нечто подобное. Возникающие входящие события (нажатия клавиш, приход байтов по интерфейсу и т.п.), обрабатываются по-быстрому (прерываниями), складывая отчёты о событиях в специальную очередь. А уж основная программа, возобновляя свой бег по кругу в связи с каждым событием (буквально пинок получает), как раз и занимается тем, что читает запись об очередном событии из очереди и делает соответствующие действия, потом берёт следующее событие и следующее. Если брать нечего - идёт спать до следующего события.
А посмотреть, сколько у тебя процессора спит, сколько выпоняется то или иное, можно - я как раз на прошлой страничке пояснил метод с высовыванием контрольных битов в свободный порт. Прямо перед Sleep можно поставить какой-нибудь бит порта в 1, а первой командой в прерывании вернуть его в 0.
Про дрыгание ногами до и после событий я понял. Факт засыпания где увидеть? ... На кварце будет прекращение генерации? +++ Про спусковой крючок тут, в моём варианте немного не так. Если уйду от второго прерывания, то чтение ADC выныривает в цикл программы, никаких спусковых крючков тут и не надо. В прерывании 1000 Гц оставлю посылку SPI на индикаторы и всё, как оно и задумывалось с этой частотой для динамической индикации. Ну и опрос антидребезга кнопок туда же можно.
Если используешь Noise Reduction, то прерывание оформить необходимо. Как - уже говорил выше: внутри прерывания ничего не делать, а поставить заглушку - сразу выход. Про 1000 Гц то же самое. Повторю принцип: в обработчике прерывания делать нужно необходимый минимум, всё остальное делается в теле основной программы. Поскольку здесь необходимый минимум - это простой "прыг" в нужную точку кода, то, соответственно, и такой подход.
Вот что Cи делает, когда ты в прерывании хоть что-то начинаешь делать. Это входа это выходИтого 19 байт в стеке и 78 тактов только на их отработку (целых 5 мкс по времени). А по факту эта работа со стеком просто засунута в совершенно линейно идущий кусок программы. Всего-то только нужно - это чтобы этот кусок программы запустился в нужное время.
Сравни с тем, что я предлагаюА вот сам кусок кода, в который встраивается указанное вышеЗдесь первая и последняя команды - это тот самый "индикатор" (вначале "вкл", в конце "выкл", по которому можно смотреть, сколько работает АЦП (картинка на предыдущей странице).
Для Си-шника это конечно неважно, т.к. памяти хватает и всего этого просто не видно - мышление другими категориями. Однако к примеру, на маленьких PIC с их 8 ячейками в стеке (потому что больше просто нету) такой подход катастрофе подобен.
Прерывание же для смены знака индикатора как раз очень удобно тем, что длительности его периода: а) достаточно для исполнения всего круга программы + нехилый запас на возможные модификации; б) время опроса АЦП составляет не более 10% от этого времени (тут я с большим запасом взял, вдруг частоту АЦП захочется понизить), что позволяет спокойно опрашивать АЦП в режиме Noise Reduction прямо внутри круга программы. Преимущество такого подхода - АЦП измеряет не абы когда, а "строго по часам" - те же 500 Гц (1000/2 канала).
Всё-всё-всё уговорил... ;D ;D ;D Переписал, пока без засыпаний и оверсэмплинга, тиканье в byte clk1000hz, в самом прерывании только clk1000hz = ~clk1000hz;. Для чтения АЦП функция adc_read, в которой и планируется спать. Отсылка по SPI отправил в функцию seg7_show, предварительно обновив цифры. Ну и селектор каналов чуть по другому... :) uart вынес в библиотеку, ну он для подстраховки, тут не используется. lss в аттаче.02_all_new.zip
Эмммм, только теперь я не пойму как спать, если стоит SLEEP_MODE_ADC, и засыпаю после ADCSRA |= (1<<ADSC) то не просыпается. А когда sleep_cpu() была в прерывании ADC_vect и ADIE=1 то вроде всё работало (https://anklab.ru/forum/index.php?topic=49.msg326#msg326) (на основе примера от микрочипа).
Ну вот тут тогда вопрос - достаточно ли этих данных для оверсэмплинга? Может есть смысл тактировать не по таймеру, а как-то реализовать по завершению преобразования АЦП в прерывании ADC_vect?
Вот этот момент вряд ли влияет, но вместе с ADSC (start conversion) ADIE (interrupt enable) надо бы проверять, потому что по ходу программы кто-то другой мог и опустить этот флаг. Второе. Вот что пример (https://microchipdeveloper.com/8avr:adcnoisereduce) показывает в обработчике прерывания. Это просто пример - там он увеличивает счётчик на единицу. Т.е. по факту ничего не делает (чтение результата в качестве примера, не более. Интересно, в Си можно сразу на выход?) В основном теле программы у него так Давай глянем твой код и... я не вижу установку 1 << ADIE Т.е. прерывания от ADC не будет, проц выйдет из спячки (может быть) по какому-нибудь другому событию.
Вот ещё выбор каналаНадо 0xF0, а не 0xF8. Старший бит может быть был взведён (кем-то, типа температурку померить рад в пять секунд?)
И ещё интересный момент. Если ждём прерываний, зачем делать вот это внутри adc_read?Т.е. оно противоречит как-то.
Возьми за основу код примера (по ссылке), добавь туда вывод в serial и более ничего. В serial выведи считываемые с ADC показания и можно adc_buffer_count. Как только у тебя всё побежит - значит оно заработало. Вывод на индикатор пока не вставляй. Дело в том, что после того, как отработало прерывание ADC и считаны показания, нужно на время выключить запуск ADC, потому что следующий sleep должен быть на ожидание таймера, он не должен запустить ADC.
Рабочий вариант. ы. Вот так adc_select_channel(channel) ADMUX = (ADMUX & 0xF0) + (channel++ & 0x01) работает только один канал. Со скобками крутил - не помогло. Поэтому пока adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel) оставил.
Цитата: undefinedПрислал пустышки - чипы звонятся насковозь по ногам питания, ни один из 5 не работает
;)
ЦитатаADMUX = (ADMUX & 0xF0) + (channel++ & 0x01) работает только один канал.
Возможно, что я что-то намудрил с ++, но на деле если без этой маски работает - так и оставить. Потому что здесь оно не сильно-то и надо, просто лишняя операция.
Да, этот лот лотерея на пустышки, но всё-равно 5 штук живых там есть где-то за рублей 300-400. Не вижу у тебя R12 и R15 - не нужны? Развожу потихоньку, вот так пока.
R12 есть (у меня он вроде R32 на последней схеме, но нумерация ещё съедет - к бабке не ходи), а R15, поскольку в усилителе от токового датчика нет отрицательного сигнала (и резистор ОС и вход "+" измеряют точно относительно Gng), можно не ставить. При этом при подсчёте коэффициента усиления, он (коэффициент) будет получаться на единичку больше, чем просто отношение резисторов в ОС.
Быстр на руку :) А я всё в раздумьях :-\ Не могу отделаться от того, что для получения прерываний с частотой один килогерц, глубины таймеров 0 или 2 не хватает. Остаётся только таймер 1, который 16-битный. Прерывать приходится по совпадению регистра сравнения.
Казалось бы, можно 8-битные таймеры запустить в режиме прерываний и с пропуском n-ного количества прерываний, ан нет - они либо станут мешать АЦП (прерывание будет вызываться когда АЦП ещё не закончил), либо не будет чёткой выдержки интервалов. И то и другое противоречит концепту.
зы: всё написанное выше - не правда. Потому что я туплю, с какого-то посчитав тик с предделителя прерыванием с самого таймера. А это неправильно. С 8-битным таймером можно получить частоту прерываний вплоть до ~61 Гц...
:) Разглядываю какие индикаторы есть/заказать. Хочется чего-то эдакого... размер, не знаю 0,5 (https://aliexpi.com/atg5) или может 0.8 (https://aliexpi.com/sFfs). Желтых и белых как-то мало, белые в основном на плате с модулем (https://aliexpi.com/6Y9g) i2c. Вот жёлтый (https://aliexpi.com/5l65) из четырёх знакомест, но ОА.
14мм (0,56") по-моему самое то. А чисто визуально лучше всего читается красный. Зелёным как правило нужен ток побольше. С синими проблема - глаз на синем плохо фокусируется, хотя они яркие. Можно сочетать: красный (жёлтый) - напряжение, синий - ток. Вот белые я не пробовал.
Синие вырви глаз, ну их. Вот жёлтые (https://aliexpi.com/j8cJ) более-менее приемлимо по цене, только 0 заказов. Белые по вот (https://aliexpi.com/dxTE), но 20 штук, я их тоже не щупал. Не плохо смотрятся оранжевые (https://aliexpi.com/od2M), на фото не очень, в живую лучше. Чип и дип вообще всегда дорого, хотя последнее время появились позиции с приемлемыми ценами. Заказал STM32F030K6T6 (https://aliexpi.com/Qhap), макетную делать будем? Тему заведём?
Будем. Programming Manual (https://www.st.com/resource/en/programming_manual/dm00051352-stm32f0xxx-cortexm0-programming-manual-stmicroelectronics.pdf) уже изучаю :) Я только пока не врубаюсь, как к ней ST-Link подключать. У Атмела (да и у Микрочипа на PIC'и аналогично) вся дока на чип в кучке, а тут как-то разбросано... Правда, я в последние дни больше про STM8 читал. Вполне по-зубам (моим) чипик, хотя и регистров мало. Смешно: один аккумулятор да два индексных. Но зато Memory-Memory операции умеет, DMA есть (для SPI, ADC и пр. очень полезно).
Я пока мучаю алгоритм, что немногим ранее предложил. Констатирую, что вполне рабочий - только что завёл на своей плате (за исключением динамической индикации - ну просто статика у меня сделана). Из нюансов - таймеры стопятся, когда ADC работает (в режиме Noise Reduction) так что в каждом цикле обязательно должно быть одинаковое количество запусков ADC (собственно, я по одному запуску предполагал, чередуя каналы). При таком построении задержка таймеров тоже одинаковая в каждом цикле и всё чётко, без дрожаний. Без остановки может работать только таймер #2, да и то в режиме внешнего тактирования. Лепить ещё один внешний генератор совсем неохота, не стоит оно того (не, реально грустная система: заюзал одну фичу - стали недоступны несколько других)
Я пока займусь тоже макетом, приближённым к реальному изделию (т.е. ничего лишнего, но разъём PGM обязателен) чтобы попрактиковаться в асме (нравится мне это прямокодие).
Собрал. Прыгают иногда индикаторы. Надо наверное ещё замедлять. Номиналы на схеме соответствуют тем что на плате. За ток пока не брался. Напряжение из расчёта 0-40. Делитель 10k/91k, почти хватило, пришлось R2 увеличить с 10k до 12k, и попал. Программно просто делю на два. С19 - не нашёл 4,7 мкФ - поставил 1 мкФ - можно? Только 1 мкФ и 10 мкФ есть сейчас :) R27, R25 пока перемычки. R41 (на опоре) 47 Ом, норм? Конденсатор не нужен же на Aref? C15 не стоит, те на Aref ёмкости никакой нет. Видео (https://www.youtube.com/watch?v=2hmNShjNryc), снял плохо, бликует индикатор, сорри. На диапазоне 0-14 вольт почти попадает 0,01, ближе к 30 расхождение на 0,05 примерно. Не знаю как в плане точности UT61E... но вроде не плох.
R41 надо выбирать исходя из необходимого тока через LM4040. Достаточно в пределах одного-двух миллиампер (потребление по Ref не превышает четверти миллиампера). Если стоит на 4.096 вольта, то нормально вполне 470~680 Ом. А вот 47 Ом явно мало. К сожалению, не нашёл в даташите данных о подключении ёмкостной нагрузки к LM4040. Я полагаю, с ней можно и без ёмкости.
А вот если Ref198 стоит, то R41 ставить совсем не надо, а на выходе Ref ёмкость нужна.
Если U1 стоит MCP, то R31,R32 надо одинаковые. Если LM358 - то как на схеме. Сигнал "Tre" осциллографом смотрел?
С19 можно "какой есть", он не является критичным. Можно и десятку засунуть - будет только лучше.
А что значит "индикаторы прыгают"? Если это про неустойчивость показаний в последнем разряде, дак это нормально.
А расхождение 0,1 для 30 - это 0.3% точности. На деле UT61 не является каким-то офигенно точным прибором, увы...
А, у LM4040 Irmin = 45 μA, те 45 мкА, а я считал 45 мА... и поставил 47 Ом, то-то я смотрю, а там 4,086 вольт, сейчас поставил, 470 Ом - ровно 4,096 вольт стало, как и положено. А про ёмкость я тоже не нашёл ничего, поэтому не поставил. Ref198 у меня пока нет. ОУ MCP стоят. Осциллографом чуть позже гляну, покажу что там. Прыгают бывают по два знакоместа последних на обеих индикаторах синхронно, не пойму из-за чего. Сначала с землёй намудрил, ноля не было, потом переделал, ноль отличный.
Два знакоместа синхронно - это когда -9-0-9-0- младший разряд меняется. Это вынуждает меняться разряд, который старше. Из-за шумов последний разряд всегда неточен в пределах одного отсчёта, как бы там ни было. В алгоритме для получения 40.92 нужно просуммировать 32 отсчёта и поделить их на 8. При этом, если буфер суммирующий, показания будут обновляться с частотой примерно 15 Гц (если следовать алгоритму, что я ранее показывал, 1000 Гц/2 канала /32 выборки). Можно суммировать 64 выборки и делить на 16 - младший разряд болтать станет чуть меньше (возможно). Но болтанка ещё и от самого сигнала зависит. Поэтому постепенно осознаётся то, что быстрая смена показаний на дисплее не улучшает восприимчивость. Максимум - это порядка 8-10 раз в секунду. Ибо всё-равно в младшей цифре будет мешанина. Тут уж от обстоятельств думают, оставлять мешанину (при шуме в измеряемом), или что-то делать (уменьшать частоту вывода, изменять способ подачи показаний, гасить разряд или ещё что-то).
Болтанка (https://youtu.be/5xdwYBwdfQM). Чуть изменил напряжение на входе - перестала. Тока в цепи его измерения нет, те должны быть 0000 (зелёный индикатор). Tre и монтаж добавил, волоски на плате - это от кисточки.
Треугольник хороший, на 30 Омах R16 R17 должно хватать для смещения.
Осталось либо рассказать, как делаешь подсчёт, либо сразу перейти к постройке алгоритма. Такая синхронность говорит о том, что где-то ошибка (в замерах, в вычислениях, в способе прочтения АЦП, да мало ли). Там на плате с земляным проводом как-то не очень (хотя бы потому, что R16, R17 и AGnd смотрят в три разные точки, а это только то, что я сходу увидел), но это как-то сильно влиять не должно. Вывод в UART не надо бы использовать, он прерывания генерирует. Если FreeRunning оставил, то с длинной обработкой прерываний можно запросто пропустить снятие данных с АЦП и тоже будет ошибка...
Код почти такой же и остался, пока ничего не менял, от "немца" оверсэмплинг и сглаживание. R16 и R17 замаскированные 15 Ом, два по 30 бутербродом :) UART не используется тут. Про FreeRunning тоже вроде договорились - не используем. Для чтения ADC спим, прерывание только для пробудки.
Не синхронизированы запуски ADC и отображения. ADC по факту бегает сам по себе, хотя и в режиме Noise Reduction. Нужно: 2 режима Sleep. Перед запуском ADC нужно установить режим "ADC Noise Reduction", а по выходу из него нужно установить режим "Idle", снова впасть в Sleep (уже как Idle), теперь уже дожидаясь прерывания от таймера (перед Seg7_show), и продолжить уже показом цифр. Это если не менять тут по ходу написанного. Мне ещё вот не ясно. Bin2BCD вызывается каждый раз, когда меняется показываемая цифра. Собственно, а зачем? Его надо вызывать один раз, когда окончен расчёт буфера ADC. Соответственно, BCD надо где-то похранить (памяти-то полно), сделав им массивчик (или он уже есть?). Итого будет 128 опросов АЦП (по текущему состоянию счётчиков), 16 показов индикатора, одно преобразование BCD. Экономия существенная (хотя и незаметная - всё-равно проц стоит, ждёт прерывания... да...).
Расчёты немца сне не очень понятны. Если ты ихъ прокомментируешь, может, будет лучше. А пока мне неясно, для чего он вычитает
V = (adc_oversampled[1] + (V << 2) - V) >> 2; A = (adc_oversampled[0] + (A << 2) - A) >> 2;
А они должны быть на Agnd? bin2bcd вызывается только перед отправкой данных по SPI. Цикл основный программы молотит, а if (~clk1000hz) при изменении переменной clk1000hz выполняется опрос ADC. Переменную clk1000hz изменяет прерывание таймера.
Ну да, я как раз про это. Отправка по SPI отдельная для каждой цифры. Вызывается 8 раз для всего индикатора. А Bin2BCD нужно считать хотя бы только для индикатора в целом. А ещё лучше только один раз - по результатам обсчёта ADC
А это реально работает? Выведи, как я советовал, в свободный пин какого-нибудь порта (то же D) единичку перед вызовом ADC и нулик сразу после окончания работы ADC. Осциллографом можно будет посмотреть, есть ли там 1000 Гц. Мне кажется, что сейчас в условии какое-то неправильное выражение и работает не так, как задумано (а именно, 500 мкс цикл "чешет мимо условия", а вторые 500 мкс гоняет АЦП в хвост и гриву). Лично я бы предпочёл SystemTick байтовой величины, накладывал бы на неё маску и по результатам выбирал и канал, и цифру индикатора.
Передачу данных на сегменты писал выдумывал сам, она работает, да может не правильно, посоветоваться не с кем было. :) Счёт от 0, всего 10, 8-цифры и две последние точки. Для этого был сделан: if (~clk1000hz) - работает, я uart'ом проверял, переменную какую-то дополнительно, осциллографом попробую. Ещё раз bin2bcd считает только для индикатора, тут лишнего точно не вижу.
Оно считает только одну цифру? Или сразу все? Показываешь-то ты только одну...
UART'ом нельзя проверить - он слишком медленен. Условие (~clk1000hz) работает каждый раз, когда оно ~true, а не true (т.е. false) оно каждый второй тик таймера. Пока оно false - гоняется АЦП по кругу всего цикла по принципу "а сколько успеет", а потом, когда таймер его перекидывает в true, АЦП вовсе не запускается, главный цикл гоняется впустоту. А гонять нужно один раз за один тик. Исправить можно, если clk1000 перекидывать в первоначальное состояние после (перед) работой АЦП. Тогда каждый тик таймера будет его "взводить", а запуск АЦП "спускать". Будет один запуск АЦП на тик.
ЦитатаА они должны быть на Agnd?
Собственно, земля там едина, но вот если по ней побежать - не совсем, ибо проводник длинный и имеет сопротивление. Но для тестовой платы, повторюсь, это скорее всего не имеет значения. В продакшн такое нельзя - выводить надо точнее (поэтому, потому я за двуслойные платы, там это делать много проще, чем на однослойке)
Вот смотри тут тикнули в одну сторону 0/255: При следующем - в другую. А тут if (~clk1000hz) при любом изменении сработает. Плата двусторонняя, но с землей намудрил однозначно. Переходы запаяны кусками выводов. Слишком медленное замеряю счетчиками, даже светодиодом можно проверить быстрые вещи.
Да. 0 - это False 255 (точнее всё, что не 0) - это true Таймер у тебя перекидывает значение переменной : -false-true-false-
Проверка if на условие (~clk1000hz) по факту означает, что then выполняется, когда clk1000hz имеет значение false А теперь смотри. У тебя "then" выполнялось, скобочки закрылись. clk1000hz как-то изменяла своё значение? По-моему, нет, я не вижу в коде. Проверка в if значение clk1000hz не меняет (это же только проверка). Это значит, что согласно while(1) тут же начнётся, без всяких вариантов и каких-либо ожиданий, снова этот этот же if и снова с точно этим же условием... А нужно, чтобы он дождался, пока таймер перекинет clk1000hz в противоположное значение...
Надо так при этом у условии что ~clk, что просто clk - результат будет один. Таймер перекинет clk в одну сторону, первый же if (clk) перекинет его обратно, выполнив условие только один раз до следующего таймера...
Bin2BCD скорее всего саму редактировать не надо. Надо убрать её вызов из SegmentShow и вставить туда, где
if (++counter[adc_channel] > 128 - 1) { сюда }
На выходе BCD у тебя циферки BCD уложенные в индексированный массив "counter7s". Следом, уже вне этого условия, у тебя SegmentShow берёт очередную циферку по индексу (нормер разряда индикатора) и дует его в SPI. А counter7s, однажды посчитанный по результатам обсчёта буфера ADC, стоит как вкопанный до получения следующего результата от фуфера ADC, что случается только один раз из 128 проходов clk1000hz
Кстати, сюда же, в скобки нужно и подсчёт V и A вставить, а то они тоже считаются с каждым проходом, что совершенно не нужно. Заодно и промежуточное adc_oversampled можно будет забыть (а может и оставить, надо только придумать, зачем)
зыыы. Сейчас вспомнил... у меня же ардуина есть и бредбоард. Вполне могу собрать макетку... Правда, она поганая будет по части аналоговой разводки, что нуль там крайне маловероятен, но можно отлаживать.
По порядку. Вот кусок кода 1. После if добавил переброс ноги вниз, в конце вверх. Кусок кода 2. Где добавил clk1000hz = ~clk1000hz; в конце then. Ну и картинки с этой ноги. По-моему ничего не изменилось? Только вот почему частота под 5 kHz???
И теперь внимание: во время работы ADC ты ногу опускаешь, по окончании поднимаешь. Первое: почему у тебя ожидание (нога поднята) такое короткое? А опущенна нога 200 с небольшим микросекунд - это время работы АЦП + расчёт. При 125 кГц 13 циклов работы АЦП занимают примерно 105 микросекунд, ещё сотня - расчёты да выпих в SPI. Из всего получается, не работает таймер. Совсем. Если бы он работал, преобладала бы поднятая нога и период был бы порядка 1100-1200 мксек, а не 200 с небольшим, как сейчас.
Делай: 1. внутри прерывания таймера clk1000hz++ (увеличение на 1) 2. внутри while(1) Я не уточняю, какие именно буквы писать, я показал общий алгоритм, как оно должно работать.
Seg7show надо поправить. У тебя есть преобразованный массив bcd цифр для показа (counter7s), он глобальный, на входе Seg7Show нужен только индекс цифры, которая предназначена к показу.
Хммм. Сделал. Но это не решило проблему, болтанка только увеличилась. +++++++ Нашёл! Я тут экспериментировал с этим таймером, который 1000Гц. Там есть строка: Цифру 125 заменил на 250 = всё! болтанка (дребезг) ушла! Но вопрос по частоте остаётся, сейчас она 8,47 kHz на ноге контроллера.
Помедитируй-таки над алгоритмом с предыдущей страницы (https://anklab.ru/forum/index.php?msg=327) (белый такой). Именно его я изобразил парой постов выше. Должно работать, только нужно немного переработать процедуру show7seg - она должна в качестве параметра получать номер показываемой цифры, которые она и так берёт из массива, куда любезно их уложил Bin2BCD. Только в "белом алгоритме" позиций индикатора у меня 4 (я так задумывал), а у тебя все восемь (если я не ошибаюсь)
Также, если пока не ломать, попробуй добавить sei(); на выходе из прерывания по таймеру, а также на выходе из прерывания от ADC. Я не знаю, добавляет ли Си автоматом это, или нет. ВРоде в бинарном коде я не видел SEI на выходе из прерывания. А если нет SEI, значит, прерывания не работают (работают, но только один раз).
Цитата: Slabovik от 23 Окт., 2020, 22:05Сейчас вспомнил... у меня же ардуина есть и бредбоард. Вполне могу собрать макетку... Правда, она поганая будет по части аналоговой разводки, что нуль там крайне маловероятен, но можно отлаживать.
Фьюзы даже менять не надо, только бутлоадер затрётся. Архив с makefile. Все что надо - geany и две команды: make all make flash :) Добавлю, на всякий архив проекта кикад, земля там не правильная :)
Ну как же не работает? Вот есть (http://www.8bit-era.cz/arduino-timer-interrupts-calculator.html) генератор кода. Взял кусок оттуда: Ровным счётом ничего не изменилось, работает хорошо, на осцилорафе с ноги LED = 8,4 kHz. sei(); пробовал добавлять .... не. Попробую другие таймеры ещё.
А, вот ещё. clk1000hz объявление и инициализация где? В процедуре TimerInit ей надо что-то присвоить. "Что-то" - это 0. Потому что в бинарном коде там ерундастрока 128 - команда на комплементарное преобразование. У тебя clk по факту знаковое! Надо: а) объявить clk беззнаковым; б) при инициализации присвоить 0. Поскольку при старте проца в ОЗУ может находиться какой-нибудь мусор от прошлых запусков. Да и вообще взять за правило начальную инициализацию переменных.
Да, команды SEI я тут не вижу. Она должна быть перед RETI (если конечно не задумывалось сто-нибудь другое). Вход в прерывание сбрасывает флаг EI, запрещая другие прерывания совсем. Восстанавливать флаг нужно вручную - это программист должен заботиться. Макет я всё-равно быстро не соберу. На выходные снова в поля, помёрзну малость (у нас уже минус, снег вон не тает).
А, это я проглядел. Ну, значит, здесь порядок - есть инициализация. Хотя с другой стороны, в .lss я не обнаружил, чтобы в эту ячейку что-то писалось до входа в main... Правда, там какой-то блок из памяти программ в ОЗУ в самом начале копируется. Возможно, это инициализация и есть. Тут без отладчика трудно смотреть...
Это деревня. Ведь Тюмень - столица деревень :) А там и огород, и сарай, и поля, и велосипед, и добрейшей души уличная собака ::) И работы край непочатый :'(
Красота! А я вот сюда (https://www.google.ru/maps/place/%D0%A1%D1%83%D1%80%D0%BE%D0%B2%D0%B8%D0%BA%D0%B8%D0%BD%D0%BE,+%D0%92%D0%BE%D0%BB%D0%B3%D0%BE%D0%B3%D1%80%D0%B0%D0%B4%D1%81%D0%BA%D0%B0%D1%8F+%D0%BE%D0%B1%D0%BB./@48.6028632,42.8247538,17.75z/data=!4m5!3m4!1s0x411b77bab4a328e5:0x787e482262353969!8m2!3d48.6102216!4d42.8528334) часто езжу. Речка, собака тоже там... В этом году только редко получалось.
Красота красотой, но полгода зимы вгоняют в депрессняк :( ↓ спойлер ↓
(http://anklab.ru/is/image.php?di=GTWQ)
правда, фотографироваться вообще не любит
[свернуть]
А местность у вас весёлая. Более "бугристая", а отличие от местных болотистых равнин :) На велике должно быть прикольно :))
Тем не менее, продолжаю смотреть .lss Будешь смеяться: вот как выглядит тот самый if (~clk1000)Ну т.е. никак. Никакого IF компилятор не создал вообще. Он читает этот clk, инвертирует его и идёт опрашивать ADC. Никаких ветвлений здесь нет. Значит, IF не работает - его просто нет. Что в свою очередь значит, что нужно менять условия - делать их более явными. Ну, или смотреть, где накосячено со скобками, точками с запятыми и т.п...
Ха, ты прав! Какая - такая штуковина получилась. :o Сделал явное: Нууууу.... заработать-то заработало, но! индикатор стал медленным как черепаха. Попробовал вообще убрал прерывание по таймеру и этот злополучный if, - таки всё работает великолепно. Может что не так с тактовой частотой? На кварце нормальные 16 MHz. ы. Код похудел до 1008 байт... :) +++ ыы. Залил вот такой код, для 28-ой ноги: Картинка такая получилась:
Значит, компилятор наумничал :) Теперь давай посчитаем. Пусть предел по напряжению 4096. 4096 больше 1024 в 4 раза. Для увеличения разрешения вдвое нужно 4 отсчёта. Вчетверо - четыре по четыре. Следовательно, для получения точности 1 на шкале 4096 необходимо 16 отсчётов. Сейчас делается 128 отсчётов.
Таймер тикает 1 кГц (реально немного меньше из-за торможения от Noise Reduction). Канал замеряется один раз в два тика, т.е. 500 Гц. Для накопления 128 отсчётов нужно 128*2=256 тиков. Итого: показания обновляются два раза в секунду. Не часто.
Когда ты отказываешься от таймера, частота опроса ADC идёт с максимально возможной скоростью (картинку ты ранее показывал).
Необходимо сделать (ччёрт, я же это писал уже) вариант 1: снизить количество отсчётов до необходимого. Нет никакого смысла считать чего-то долго, если мы всё-равно не пользуемся результатом. Вполне достаточно иметь 32 отсчёта. При 32 отсчётах обновление будет 8 раз в секунду - вполне приемлемо. Если хочется, можно немного (раза в полтора) ускорить таймер - место (по времени) там есть. Можно отказаться от принципа "один цикл - один канал ADC" и опрашивать в каждом цикле оба канала ADC - это ещё увеличит скорость накопления буфера вдвое.
вариант 2: сделать кольцевой буфер. Тогда показания можно будет обновлять хоть каждый отсчёт. Однако каждый отсчёт обновлять индикатор настоятельно не рекомендую. Человек всё-равно не в состоянии уловить столь быструю смену цифр. Быстрее 10-15 Гц нафиг не нужно...
А с опытом и картинкой не понял, какая цель преследовалась. То,ч то получился короткий цикл и такая скорость переключения - это всё закономерно.
Тут вот как получается, сейчас, когда без таймера и опрос постоянный, - мне нравится как работает. Скорость не большая, вот снял (https://www.youtube.com/watch?v=DBajoM08A6s). freq_test.lss приложил.
Крутится там вот это sbi-cbi выполняются за два цикла rjmp тоже выполняется два цикла. Итого два цикла в положении "1", четыре цикла в положении "0", всего шесть. при 16 мегагерцах один цикл 62,5 наносекунды. В положении "1" ожидаем 125 наносекунд, в положении "0" - 250 наносекунд. Всего 375 наносекунд, что соответствует 2,66(6)МГц частоты. Именно это твой осциллограф и показывает.
зы: Если после SBI вставить два NOP, то на ноге будет ровный меандр, а его частота 2 МГц.
Пока оставил тот вариант как запасной. Таймер пришлось настроить для комфортного обновления на 8 kHz (6 тоже нормально). Тут пока некоторые моменты не так, но мне надо понять где теряются данные. Если вот этот кусок в теле основного цикла, то работает. Если я его отправляю под if (clk1000hz==0) { то всё по 0000. ... делать вторую структуру для bin_bcd?
Не могу увидеть причину неработы. По мне так структура нафиг не нужна (просто лишняя сущность, без которой можно прекрасно обойтись), но почему там нули - это мне не ясно. Значит, на входе процедуры BCD нули. А это A и V. Обсчитываются они странно, что-то внутри клока, остальное снаружи... Считать ведь надо один раз, а не каждый проход, уж как мне сейчас это поправить? Только переписать полностью, но мой макет не готов...
Но... почему бы не начать писать свой код, добиваясь его работоспособности, а не пытаться обрабатывать методом тыка чужой? (это я про немца). По крайней мере будешь чётко понимать, что и зачем делаешь - уже будет легче.
Как они будут нулевыми, если я их забираю сразу после преобразования? Мне кажется дело в сопоставлении массива counter7s[] цифрам что-то не так. +++ Ну или пойти по другому пути, оставить одно прерывание АЦП, добавить две глобальные переменные, которые инкрементируются каждый цикл while(1), допустим: freq7seg_update - будет отвечать за частоту обновления дисплея. freq_adc_update - соответственно АЦП. ... при достижении нужного кол-ва - делаем то что надо, сбрасываем счёт. Ну или к одной переменной можно привязать. +++ Вот, ещё раз, так = все нули: А вот так всё работает:
Нельзя использовать ADC Noise Reduction асинхронно с таймером обновления экрана. Потому что таймер во время Noise Reduction останавливается. Если это игнорировать, будет неодинаковая засветка разрядов. Именно поэтому ведущим объявляется таймер обновления сегментов, а запуск ADC встраивается внутрь его циклов, благо время позволяет с большим запасом. Если так не делать, то либо отказаться от Noise Reduction, либо от динамической индикации средствами процессора. И просмотри внимательно точки с запятыми и скобки в if'ах. Там мне как раз в место подсчёта неоднозначность какая-то рисуется. Лучше лишнюю скобку поставить, чем гадать, что выполнится, а что нет...
Ну как же хватает? Я пробовал выполнять оверсэмплинг с частотой 1 кГц, а после обновлять сегменты, - не хватает. Ну или я что-то делаю не так. Пойду думать... :)
Ну, вполне хватает. Частота тактирования ADC 125 кГц, если делитель не врёт. Получение результата замера с ADC занимает 13 тактов - это 105 микросекунд. Посчитать фуфер - ну ещё сотня микросекунд (твой осциллограф это прекрасно подстверждает). Выгрузить в SPI - ну пусть 50 микросекунд. А таймер тикает каждые 1000 микросекунд. Где же не хватает? Хватает и с солидным запасом.
Тут вот какая штука вылилась - начал переписывать заново. Переписал только для сегментов. Вот такой код вышел. По идее на всех индикаторах должны быть нули. Заливаю = ноль только на первом! Крутил и так и так, короче вывел он меня, бросил, потом подошёл typedef unsigned char byte; и помянуя о "ты не поверишь! (lss)"поменял на typedef volatile unsigned char byte; и оно заработало = нули на всех, ну ладно, дай уберу volatile = убрал, и оно продолжает работать как надо, ходя заливал по нескольку раз.... Ну я в Makefile смотрю Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization. У меня стоит s.... :'(
volatile - это способ отвадить компилятор от попыток "оптимизировать" переменную. Если volatile нет, то переменная может просто исчезнуть в недрах оптимизации, когда компилятор посчитает, что с ней никто и не работает. Вот, по-быстрому ликбезик нашёл: http://microsin.net/programming/avr/how-to-use-volatile-in-c.html
Кстати, c "if" возможно надо было попробовать не действие "~" (neg), а условие "!" (no). Есть подозрение, что действие в качестве условия не прокатило. Хотя х.з. на самом деле.
зы: ещё раз упомяну, что я не знаток Си и для меня эти сишные выкрутасы самому в диковинку.
Ну вот нюансы Си я сам только познаю. Хотя я хорошо знаю, что, например при установке Gentoo в Makefile (https://wiki.gentoo.org/wiki/GCC_optimization/ru)с оптимизацией gcc шутить не надо, те обычно это -O2, а я влепил -Os.
Цитата: undefined-Os: На этом уровне код будет оптимизирован по объему. Он активирует все параметры -O2, которые не приводят к увеличению размера генерируемого кода. Он может быть полезным на компьютерах, которые обладают чрезвычайно ограниченным пространством жесткого диска и/или процессоры с небольшим размером кэша.
Макет жирный такой! :) ы. У меня тут вопрос как накатить маску на байт цифры для точки?
Там не маску надо, а за-OR'ить символ точки с символом знакоместа, в котором точка нужна (ну или через &, если отображение инверсное, т.е. когда "0" в порту - это "горит").
Вычисляешь BCD, сопоставляешь этому BCD 7-сегментный код, пока без точки, кладёшь его в буфер вывода (из которого SPI будет брать байты для выдвигания). К позиции буфера, в котором нужна точка, добавляешь операцию типа
dig_for_out[n] = dig_for_out[n] | symbol_DP
где symbol_DP - это код точки для семисегментника (у тебя оно вроде 11-я позиция по списку, после девятки). Названия вымышлены с целью адаптации для понимания :)
зы: макетка китайская, супервонючая. Уже получил за созданную вонь...
Процик засуну в дип-корпусе, чисто ради удобства паяния проводами. Ну, а схема приближена к тому, что я рисовал (у тебя по сути аналогично, только 595-х вроде две). Только добавил два индикатра-столбика, вывод в них сделаю 5-м символом. Есть мыслишка по поводу "мельтешащих цифр"...
А сколько значений достаточно для оверсэмплинга? Вот я попробовал 16 суммировать: Тогда, при условии выполнения опроса 1 кГц, скорость обновления показаний достаточная. Если 32 - ещё более менее, а уже 64 и 128 надо кидать либо в основной цикл программы либо ещё один таймер задействовать. Залил код с 16-тью сэмплами и сглаживанием, понаблюдаю как будет себя вести. ы. И вопрос к линейности, не претендую на точность, но если настраиваю на 20 вольт, то при уменьшении до менее 1 вольта расхождение в один-два знака, например 20 точно, 0.23 у меня, 0,24 на мультиметре.
Минимально-достаточно 16, подстраховаться - 32. При 16 сдвигать вправо нужно на 2 (>>2), при 32 - на 3. Не забыть добавить (забыто) перед сдвигом 1/2 (половину) от количества просуммированных отсчётов (т.е. 8 для 16 или 16 для 32)BufLength определить в самом начале как глобальную установочную константу типа unsigned char (т.е. один беззнаковый байт). Можно две - на каждый канал свою. Это потому что в общем случае глубина буфера у каналов не обязана быть одинаковой.
В результате adc_oversampled содержит величину, которую нужно "скормить" Bin-2-BCD. Уже без всяких делений - это готовый результат измерений.
adc_oversampled и sum - двубайтовые величины (не помню, вроде unsigned short int - проверь сам). Двубайтовая обрабатывается быстрее, чем просто int (он четырёхбайтовый - проверь тоже, вдруг нет). Двубайтовыми они могут быть до глубины буфера включая 64 (проверка: 03FFh * 64dec = 0FFC0h, 0FFFFh-0FFC0h=3Fh (3Fh=63dec)
Это, если не делить на два. А так да при 16 сдвигаю на 2 получаю сразу результат в напряжении нужный, это уже понял :)
Цитата: Slabovik от 26 Окт., 2020, 20:39Не забыть добавить (забыто) перед сдвигом 1/2 (половину) от количества просуммированных отсчётов (т.е. 8 для 16 или 16 для 32)
1/2 нужна для правильного усреднения результата. Потому что всё, что меньше 0,5 - это 0. Все, что 0,5 и больше - это 1. Если 0,5 не добавлять, то нулём будет даже 0,9. Раздирали здесь (https://anklab.ru/forum/index.php?msg=286)
Делить на два при подсчёте BCD - совершенно не нужная операция, размазывающая процедуру сдвига вправо по нескольким местам. Зачем? Подсчитать нужно всё в одном месте.
Это уже вопрос к ОУ. Или реализации схемотехники. Можно попробовать проверить, закоротив ногу нужного входа ADC на землю (там по схеме можно прямо закоротить, вреда не будет). Возможно это то, чего я и опасаюсь - несколько милливольт на выходе ОУ, которые удалить можно только переводом ОУ на собственное отдельное питание с минусовым напряжением хотя бы на 0,2 вольта ниже, чем Gnd...
Так это я сразу сделал = 0,02 при АЦП на землю. Если включить при замкнутом АЦП (или входа пробовал и так и так) вниз, сначала нули, при первом проходе вверх, вниз уже ноля нет.
А что такое "проход вверх"? Но при старте 0 есть. Значит всё считает правильно. Можно попробовать отключить дизер, посмотреть, что изменится. Проконтролировать милливольтметром, осциллографом, что там на ноге АЦП.
Поднятие напряжения - чем? Резистор - одно, ОУ - другое, закоротка - третье. Я же точно сказал, что именно сделать, чтобы можно было думать дальше... Вангую, что при закоротке входа АЦП (физически, проволочкой на ногу Gnd), показания будут стабильно 0. Если я прав, то всё дело в аналоговой части - она не может обеспечить 0 на своём выходе. Проверяется это также милливольтметром (мультиметром) либо осциллографом (это хуже т.к. у цифровых чувствительность мала).
Дело не в аналоговой. Коротил и то и то, ведёт себя одинаково. ы. На самом деле меня уже такая работа вольтметра уже устраивает. Сейчас, когда отключал дизер заметил, что работает хуже, то-ли плавать то-ли дёргаться показания начали. С током сейчас осталось разобраться, как лучше считать, у меня шунт 0,05 делитель 15k/1k.
Тогда я не знаю. При нуле на АЦП и верной программе там должен быть чёткий 0. Нуля нет. Хотя... ноль-то есть, при старте. Не хочешь ли ты сказать, что при старте формула по-другому считает? :o Надо проверять всё. Гадать невозможно. Всё и досконально. Сколько милливольт на входе АЦП при выкрученной в "0" ручке? Чтобы показывало 0,02, должно быть не менее 6 (шести) милливольт. Они получаются легко и непринуждённо при применённой схемотехнике и ОУ, у которых только напряжение смещения может достигать четырёх милливольт.
p.s. А это ты ЗАЧЕМ ОСТАВИЛ? :o :-\ Зачем таскать всюду код, который не понятно как работает? Уж столько раз говорил про это, но ведь нет - это ВСЁ там, и эта формула, и совершенно неуместное деление на двойку в BCD :'(
Сглаживание УЖЕ есть, без вот это й херни... Вот эта фигня (а занимается эта фигня тем, что даёт якобы "плавное приближение цифр") и даёт этот неустранимый остаток, плюсом таща за собой затормаживание с выдачей верных показаний.
Суть этой "немецкой" формулы в том, чтобы плавно приближать показания при изменении. Но это дико тормозит выдачу верных показаний, наблюдающий вынуден ждать много проходов, прежде чем показания станут близки к верным. А "забыв" откинуть "мусорные" биты от суммы, недоделив, начав работать с ними (а ты именно работаешь с мусосом, "недосдвинув" ту adc_oversampled), получаешь соответствующий мусор, видимый при околонулевых значениях. Ну и получается, "на колу висит мочало"... Уж извини, я крепко расстроен...
Убрать это нафиг и таки попытаться сделать так, как говорил, ну хотя бы здесь (https://anklab.ru/forum/index.php?msg=409)
Без сглаживания у меня последняя цифра дребезжит с вероятностью 50/50, попадешь - стоит, нет болтается например 8-9, или 4-5. Хотя бы если сдвиги делаю на 1 - то уже воспринимается лучше. Ну и 0,02 никуда не девается, если я убираю эту "хрень". Завтра уже осциллографом гляну.
Ну тогда убери эту 8. Мистика какая-то. Потому что не может 8, поделённая на 16 в бинарном виде стать двойкой. Также не могут быть одновременно все озвученные наблюдения.
Без дизера. На ноге АЦП МК, земля на Agnd - DS1Z_QuickPrint8.png На первой ноге 6002 (out), земля щупа на 4-ой ноге - DS1Z_QuickPrint9.png V+ закорочен на Out-.
Тут я наверное зря соединил все земляные пины 3, 5, 21 под корпусом меги. (Хотя в элементе кикада эти выводы вместе). Если пин 1 вверху. 3, 5 надо соединить и вывести слева, рядом повесив 0,1 мКф. Пин 21 справа, он рядом с Agnd и Aref, его вправо, тоже на ёмкость. И потом объеденив их соединить внизу (или вверху) с Gnd. Приблизительно изобразил... переделать надо.
Тут важно, чтобы точки минусового питания ОУ были выведены вот на толстую площадку GND внизу, а точка, куда подключаются резисторы на входах и выходах этих ОУ была выведена под процик, оптимально к C2 при таком расположении как сейчас или к C1, если C1 и C2 под проциком не соединяются (Gnd от C1 идёт тоже на толстую Gnd, не проходя через C2)
А я забыл, какой процик у тебя? Или пофиг на совместимость кода, сам напишешь? Я только свой показывать буду...
Было бы интересно залить твой hex конечно, но и сам могу. У меня atmega328p U-TH, вот что про них нашёл (https://hamster.in.ua/viewtopic.php?f=40&t=236). Про ногу ОУ - понадеялся на полигоны земляные, они и вверху и внизу почти сплошной. Меня больше интересует 21-ая нага gnd, она рядом с aref, и по идее под корпусом соединять не надо, а с двух сторон, а ботом на полигон..
У меня из DIP (а на макетке DIP, для QFP надо плату делать, а мне неохота) есть Mega8A и Mega48-20PU. У них у обоих код отличается от 328-й (и друг от друга тоже, у 48-88-168-328 ресурсы хотя бы одинаковые, а у 8А ресурсы на других адресах). Но в принципе, мне ничто не мешает сходить в чип-дип да прикупить 328-е. Но они тоже будут DIP. Это значит, что 6 и 7 каналов АЦП у них нет (на кристалле есть, а на ногах нет). Я присоединю какие-нибудь другие. А вот по поводу аппаратного SPI ещё не решил. Можно задействовать, можно не задействовать - просто другие ноги порта.
Я это к тому, что схему надо синхронизировать, чтобы код подходил...
зы: на ALI можно брать только их, китайские контроллеры. В остальном подделок дофига. Я пару раз обжёглся на PIC'ах, более не экспериментирую - то было 100% попадание на перепиленый хлам...
Не, я не к тому, что надо обязательно в DIP или QFN, надо просто чтобы тип совпадал. Если не совпадает, значит, просто перекомпилировать надо будет с другими установками, готовый бинарник не подойдёт.
Не знаю на счёт асма, не готов пока. Мне бы C получше изучить. Сегодня психанул - переделал плату, только вытравил. На установку поставил переходники на камеру (пятак 0,8 теперь 75% кадра занимает), теперь совмещение сторон вроде лучше. Хотя пока только первый раз, посмотрю как с повторяемостью.
Не, я больше со старьём... с USB надо конкретно отдельно разбираться - там протокол обмена замороченный... А так само ядро такое же, как в Меге.
зы: в качестве совета: на макете предусматривай возможность переподсоединения линий на другие ноги портов. Часто пригождается, когда вдруг по макету готовишь релиз, а разводка не идёт и удобнее переподсоединить оборудование куда-нибудь в другое место. Позволяет сделать это на макете, не дожидаясь платы, и поправить-отладить программу.
Сегодня целый день (благо спокойствие на работе позволяло) упражнялся с Цэ. Нет, ребяты, это не моё. Хочется иметь предсказуемый и прогнозируемый результат, но однако с Цэ это не так. Не спорю, часто решающим является стиль написания, но всё-таки многие моменты совершенно не ясны, например, почему он из-за незначительной перестановки порядка обработки переменных корёжит получающийся бинарник до неузнаваемости. Непонятно...
Кстати, вот один очень простой пример, скорее относящийся к тому, как надо делать, а как делать разве что от лени. Коротенький обработчик прерываний. Картинка. В картинке показаны код на Си и бинарный код, ему соответствующий. Конкретно, в прерывании обрабатываются две переменные. Одна инкрементируется, вторая декрементируется с условием.
Вариант первый, вполне логичный для пишущего на Си.
Короткий-в-Цэ---длинный-в-байтах.png
Вариант второй, вроде бы нелогичный: заводим локальную переменную и "гоняем" значения туда-сюда.
Длинный-в-Цэ---короткий-в-байтах.png
Однако смотрите, что происходит. Короткая на Си запись приводит к гораздо более затратному и бессмысленному с точки зрения кода "гонянию" память-регистр и обратно.
Собственно, к чему это я? К тому, что не нужно уповать на глобальность переменных и работать только с ними. Передача их значений для обработки в локальные переменные функций часто приводит к улучшению получающегося кода. На asm'е это логично и естественно: грузим данные в регистры - крутим - выгружаем. На Си совершенно не очевидно. И гоняться за наиболее компактной записью выражения часто бессмысленно и даже вредно.
зы: а ещё я расстроен тем, что не могу добиться правильного возврата из прерываний с установкой флага "Interrupts Enable". Дело в том, что код SEI необходимо давать непосредственно перед кодом RETI, что в Си мне сделать не удаётся - там на входе и на выходе "push-pop'ная" безусловная заглушка, имеющаяся даже при абсолютно пустой процедуре обработки прерывания, из-за чего SEI вставляется перед гуртом POP'ов, задолго до RETI, что в принципе неправильно и при "лавине" запросов на прерывания приведёт к переполнению стека.
Ну я не знаток Цэ. Но тут (https://habr.com/ru/post/309796/). (поискав чуть-чуть) Про это есть:
Цитата: undefined...нужно искать возможность ввести временную локальную переменную, в которую производить запись, и только через какое-то время произвести запись из этой переменной в память....
Тут бы сесть и разобраться, кстати это указателей тоже касается. А то статью эту по-диагонали просмотрел... :) :o
В общем, в последние дни был занят тем, что возюкался с программой на Си для макета. Сегодня оно было дописано до вменяемо-рабочего состояния (полировка конечно отсутствует как класс - лишь бы все процедуры на месте были).
Из приятного: оно работает. И дизер работает тоже. Причём работает как надо. При выключенном дизере (просто микросхемку вынимаю) показания дискретны с шагом 4 (предел 4096, либо ступенька 6 с пределом 6144). С дизером я могу поставить любое, так что все четыре знака (десятичных) в деле.
Из неприятного. Ты оказался прав, когда говорил, что у тебя всё прыгает на прерываниях. Суть в том, что код на Си весьма медленный и банально не успевает. Мне, переползшего с ассемблера, на такое непонятно как смотреть, потому что, как я уже говорил, за один тик я успеваю окучить 8 (восемь!) каналов и ещё время остаётся на "покурить". На Си у меня даже один не проходит без того, чтобы не занизить скорость отображения дисплея. Если поставить номинальную скорость, начинаются пропуски отсчётов. А поскольку не успевший закончить работу ADC возвращает нули (потому что прибегает "чужое" прерывание от таймера), то и начинается расколбас показаний. Что делать я пока не представляю. Попробую погонять программу на выяснение, какие процедуры сколько времени отнимают. Варианты лечения. Первый - перейти на статическое отображение и тупо гонять программу по кругу без всяких таймеров, останавливаясь только на ожидание показания от АЦП. В этом случае уже ничто не будет мешать, а с какой скоростью будет идти измерения - с такой и ладно. Второй - попробовать написать то же самое, но на асме, посмотреть, велик ли выигрыш. Займёт время.
Недоделка-на-макетке-заработала.jpg
зы: я конечно тактовую взял не 16 и даже не 14, а 12 МГц, но это из расчёта поставить частоту АЦП 187 кГц - немного выше, чем это получается при 16 МГц. Хотя, при 16 МГц можно было бы 250 кГц выставить на тиктирование АЦП и, думаю, ему хуже бы от этого не стало. Но аппноты говорят, что для точности надо ориентироваться на ~150 кГц плюс-минус...
Воот, только я не понял, мы же, когда запускаем преобразование ADC разве проснуться можем в прерывании таймера? Вроде нет. Макет получился что надо, выглядит брутально! Я пробовал также примерно делать, - не, не хочу так больше, трудоёмко. У меня плату получается сделать быстрее. ы. Пробовал и пластиковые макетки штыревые - мат стоял в 33 этажа...
Я поковырялся сегодня и нахожусь немного в тупоре (именно так, без буквы 'c'). Осциллограф мне показал, что таймер исправно тикает, но вот... сведения на индикатор (по SPI) идут в 1000 раз чаще, чем нужно по таймеру. Возможно, опрос ADC вместе с ними (это предстоит выяснить). Ну т.е. почти непрерывно, но... чётко зависят от настроек таймера (который тикает в 1000 раз реже!). Явно ошибка в логике кода, но... пока её не вижу - тормоза стоят где надо...
А со штыревыми макетками, которые BreadBord, довольно весело. Но мне их вечно не хватает т.к. они не предполагают плотный монтаж. Плюс тянется проблема контактов - частенько он то есть, то где-то его нет. На фото макетка 15x18 см, но и то я ещё не приступал к аналоговому узлу. С другой стороны, на ней сделана куча ошибок в расположении, из-за чего хочется переделать, но уже плотнее. Благо, выводно-проводной монтаж это позволяет.
Когда мы запускаем преобразование ADC в режиме Noise Reduction, мы не ожидаем никаких прерываний кроме прерывания от ADC. Таймер и сама программа специально настроены так, чтобы в это время ничего "лишнего" не прилетало. Это раз. Контрольный здесь - это сам механизм. В режиме Noise Reduction приостанавливается тактирование всех (!) таймеров, кроме RTC (если он есть) и возможна работа таймера N2, но только если тактовая на него поступает извне. У нас ни одного из этих "кроме" нет. Поэтому когда мы останавливаемся на ожидание ADC, то первым придёт именно прерывание от ADC. Ну типа это как проснулся утром по таймеру (солнышку), и до следующего тика (восхода солнышка), успеваешь приготовить завтрак да сходить отлить (вывести данные в индикатор), поработать (опросить ADC), книжку почитать (посчитать результаты), да поужинав пойти спать до следующего восхода.
ps. Разобрался, в чём проблема. Она одинаковая в наших случаях. Заключается в том, что ADC не останавливается по окончании преобразования. Либо (предположение №2), снова запускается, как, только режим переключается (регистр SMCR) из ADC Noise Reduction. В итоге, когда случается Idle для ожидания таймера, весело отрабатывает прерывание от ADC, выводя проц из режима ожидания в нормальную работу.
Выход из положения по-красивому пока не придумал как сделать, но просто отключение ADC на время, когда он не используется, работает. Из минусов такого подхода - это то, что на преобразование тратится не 13, а все 26 тактов (на частоте работы ADC), что при моих настройках (12 МГц, коэф.деления 64) даёт 140 микросекунд на преобразование. Но в принципе, это не является какой-то проблемой (такое долгое время) - в запасе остаётся ещё как минимум 700 мкс (именно столько проц находится в режиме Idle в ожидании прерывания от таймера). ↓ спойлер ↓
Ссылка на образовательную статью, где есть формула, по которой "немец" считал сглаживание. Use Software Filters To Reduce ADC Noise (https://www.electronicdesign.com/technologies/analog/article/21778422/use-software-filters-to-reduce-adc-noise) ↓ спойлер ↓
вообще-то я хотел, чтобы ты сам попробовал - та формула на бумажке за пару проходов понимается, ну а в статье она "A Filter with Feedback"
[свернуть]
И вот ещё по следам, но по-русски http://we.easyelectronics.ru/Theory/chestno-prostoy-cifrovoy-filtr.html
А вообще здорово помогает колупание то там, то сям. В голове какие-то следы остаются. А я сам пока что несколько проредил работу с кольцевым буфером, сделав ячеечки накопительными. Это значит, что в ячеечку (она двубайтовая) кладу не один отсчёт АЦП, а много, штуки четыре или восемь (максимально можно 64 - у нас АЦП десятибитный). Соответственно, обновление данных для индикатора в это количество раз становятся реже, что приятно сказывается на постоянстве цифр. Сейчас я могу выставить (регулируя ручечкой напряжение на входе АЦП) любую цифру с дискретностью 1. Перемаргивания конечно есть (особенно досаждает, когда перемаргивают сразу три или даже 4 цифры, типа 2999-3000, но уж таков недостаток цифровой индикации), но из-за более редкого обновления индикатора воспринимаются гораздо лучше.
И такие накопительные ячейки кольцевого буфера здорово сокращают потребую память под сам буфер. В 200 байт легко уложиться.
Не совсем пойму, нужна ли схема моего макета. Она немного отличается от той, что я показывал (ну хотя бы кузов другой), соответственно, программа привязана к схеме, хотя всё возможно переделать.
Всё-таки сделал я 4 канала измерений исходя из двух вариантов логики. Первый: два канала измеряют традиционное напряжение на выходе блока. Они выводятся на индикатор по умолчанию. Вторые два канала измеряют опорные напряжения для ограничителей тока и задатчика напряжения и по-умолчанию они не показываются на индикаторе. Но индикатор переключается на их показ (каждый индивидуально) если только опорное напряжение начинает изменяться (человек крутит ручку). Как только напряжение перестаёт изменяться, происходит переключение (через некоторое время) на показ выходного напряжения. Таким образом, применяя индикатор можно не задействовать (т.е. не подключать) входы этих каналов, показания по ним будут нулевые, и автопереключения на них не будет происходить и пользователь вообще может не знать о их существовании.
Второй вариант - дополнительные каналы используются традиционно для автоматического переключения диапазона измерения.
Когда я был против? :o Я против применения без понимания, и тем более против применения в качестве "розовых очков" :) Ну это когда последующими обработками пытаешься буквально скрыть косяки в работе (аппаратуры или программы) на предыдущих стадиях. Касаемо этого фильтра - скрыть иногда неверно прочитанные отсчёты АЦП.
Ноль безпроблемный, но масштабирующие ОУ аналоговой части пока ещё не распаяны. Что будет с ОУ - посмотрим в ближайшие дни.
зы: думаю, что добавлю конфигурационные перемычки для того, чтобы можно было выбирать режим работы не прибегая к всяким "конфигурационным зажатиям кнопок". Кнопки можно две - принудительное гашение младшего разряда у каждого индикатора в том случае, если имеющих значение знаков становится четыре
Тут же как получается, - разбираю примеры и на этом учусь, но некоторые вещи пропускаю, в надежде на разобраться потом, всё-равно сразу всего не охватить, а возвращаться приходиться, вот и повод на ещё один подход... За ссылку на изиэелектроникс спасибо, из любопытсва надо бы попробовать ещё и с БИХ фильтром, что там упоминается.
+++ Ноль именно с таким же как у меня способом подсчёта? Потому, что у меня даже при замыкании каналов АЦП при X=8 ноля не было. Новую плату правда ещё не собрал. Надо ещё в первый мой макет попробовать залить эту прошивку. ы. Мне тут белые индикаторы подоспели на TM1637 подоспели. Не пойму как их лучше задействовать, софтовый i2c или железный? Применять тут их наверное не буду, к stm уже. ыы. Запустил их - яркие сильно, даже на яркости установленной в 0 отлично. Фильтр сверху им нужен из какого нибудь платика.
Гм, я же показал формулу расчёта немного выше (https://anklab.ru/forum/index.php?msg=455). Она в самом низу на картинке. Но буфер у меня устроен по кольцевому принципу. Это значит, что предыдущие отсчёты вплоть до (-n) учитываются автоматически. В формуле "немца" примерно похоже, толко кольца нет. Принцип работы той формулы - новое значение имеет вес только на 25%, остальные 75% - вес старых значений. Это и хорошо и плохо. Хорошо тем, что приближение происходит по экспоненте. В первый проход - 25% новой инфы. Второй проход - 44 процента новой инфы, третий проход - 62 процента новой инфы и т.д. Плохо тем, что 100% новой инфы не бывает никогда и время установления рассчитывается, когда "остаток" старой инфы не будет приводить к изменению самого младшего разряда. А это довольно долго.
Кольцевой буфер "выкидывает" всегда чётко ровное количество старой инфы, поэтому приближение у него линейное, а не по экспоненте. Возвращаясь к тому же примеру, в первый проход 25 новой инфы, второй проход - 50%, третий 75, четвёртый - вся инфа новая.
25% я взял ради примера, поскольку у "немца" было столько.
Индикатор можно применять любой, только процедуру вывода для него написать соответствующую. I2C программный или аппаратный - в данном применении значения не имеет т.к. для использования преимуществ аппаратного нужно придумать, где он может работать параллельно основному ходу программы.
TM - микросхема удобная тем, что там всё внутри есть. Туда "плюнул и забыл". Но I2C подразумевает передачу не только "голой даты", но и предварительных управляющих команд. А максимальная частота Clock у ней ограничивается 500 кГц (типовая для I2C считается 400 кГц). Соответственно, вывод будет длиннее по времени. Вывод: как всегда, смотрим по месту и выбираем, что больше нравится :) Лично мне не трудно на пригоршне ТМ2, приправленных ЛА3 подобное сколхозить :) А сюда я её вставлять не буду - пусть традиционные 595-е остаются.
зы: поправлю схему в соответствии со своим макетом - поделюсь и схемой и исходником. Надо-всё-таки решать, куда дальше двигаться. зызы: Выяснил, что REF198 надо хоть немножко нагружать. Сочетание "без нагрузки" (вход AREF нагрузкой не является) и керамический конденсатор на выходе иногда приводят к генерации (после касания рукой, пинцетом и т.п.). Добавление буквально 47 кОм (ток 9 мкА) избавляют от этого свойства. Генерацию цифровым осциллографом не видно (был как-то спор, мне упирались, мол цифровым всё можно посмотреть). Выявил, когда смотрел аналоговым, по утолщению линии. Частота - несколько МГц, вид - спадающая пила, размах - несколько милливольт. Проявлялось как некоторое увеличение показаний АЦП, буквально на несколько отсчётов в пределах величины дизера, после касания пинцетом или рукой вывода выхода REF198. Вот такая вот закавыка. Кстати, в даташите указывается на наличие электролитического алюминиевого конденсатора на выходе - похоже, это связано с этим явлением.
Ну вот, а разговор был именно про мой вариант оверсэмпла, ну да ладно. Модуль именно с TM пришлось купить, так как с белыми индикаторами и дешевле чем отдельно индикаторы покупать. Один модуль с дисплеем 0,36 дюйма и TM = 86 руб. Так что микросхема в довесок :) Не охота конечно распаивать, но была мысль... Двигаться надо в сторону stm, хорошо бы у нас одинаковые камни были бы. У STM32F0 отличия свои есть, и я уже не раз напоролся на них. Макросы из библиотеки cmsis тоже разные, хотя похожие. Хотя ты сейчас сразу по даташиту начнёшь с asm и регистрами... тогда я тут пока пас... :) REF198 у меня пока так и нет. ↓ спойлер ↓
Сейчас на столе лежит трансформатор 2 обмотки по 28 вольт переменки на 3 ампера 1.5 каждая. Те наверное будут в параллель. Думал ПиДБ очередной прикрутить, но спрошу у тебя вариант простой и более-менее готовый может есть?
Ну я же сразу говорил, что я всегда делаю по-своему :) Вариант "немца" я и не думал вставлять т.к. изначально окучивал кольцевой буфер. Мне нравится как он работает. Был бы вариант с малым ОЗУ (как в PIC - там 64 байта ОЗУ типично, а 128 - роскошно), то тогда да.
В сторону STM - да, но надо ли бросать недоделанное? Как я понимаю, тут вопросов ещё куча. Я тут Си подтягиваю (весь экран уже исплёван...) в надежде получить от него некий изящный код... Надо бы довести, чтобы оно работало да попробовать пару фичек, чтобы знать, надо ли оно. А потом и платформу можно поменять. Честно говоря, для измерителя быстродействия меги много-много излишне, не говоря уже про STM. А у STM я действительно хочу asm пощупать, плюс у него ОЗУ большое и памяти программ ещё больше. Так и тянет на что-нибудь графическое.
ПиДБ я в железе не собирал т.к. ужаснулся от того, как он сделан. В симуляторе он показывает ужас, некоторые версии (из последних) вообще не должны работать (и похоже, так оно и есть потому что куча жалоб с симптомами точно как в симуляторе). Из хорошего могу кивнуть в сторону польского блока на трёх ОУ, но с парочкой доработок (на коте, кстати, я показывал, можно здесь завести темку и повторить) и ограничением в 25-26 вольт на выходе.
Заведешь тогда тему по польскому варианту? Кстати я его повторял раз .... эм не помню ну 4 точно. Раздал все. И почему-то мне там опора не нравилась, не помню уже.
Целый день сегодня провёл за макетом. Думал, дел наворочу, а нет... практически совсем ничего, ну то есть абсолютно. Выяснял различные способы подключения масташбирующих усилителей, зависимости и т.п. Понял, что абсолютно универсального получить не выйдет, поэтому придётся прнять какое-нибудь, но волевое решение: либо так, либо эдак.
Положу пока схемы непосредственно макета. Естественно, он никак не может считаться чем-то законченным. Позиционные номера деталей тоже как попало (совсем не слежу за этим в таком процессе).
Потому что у простых лабораторников есть извечные проблемы: а) невозможно установить ток ограничения, не сделав короткое замыкание на выходе; б) невозможно понять, какое напряжение установлено на выходе, если блок находится в режиме ограничения тока.
Измерение опорников решает эти вопросы без необходимости устраивать к.з. или обесточивать нагрузку. Всего-то задействовать два лишних входа...
Нет, не знаем. Опорное измерителя - это не то опорное, которое хотим знать :) Это то опорное, относительно которого измеряем. А в лабораторнике напряжение и ток задают его собственные опорные. Если измерить их, будем иметь ответы на два пункта выше без необходимости рвать-коротить выходные цепи.
зы: в индикаторе у меня слайдеры (столбики из 10 светодиодов) для опытов. Думаю попробовать на них показывать, в какую сторону происходит движение измеряемой величины. Иногда за мельтешением цифр это сложно понять.
Мой недоделанный код хоть откомпилился? Там в нём логические косяки есть, ну и недоделан конечно. Гашение незначащих нулей, расстановка запятых - это то, что сразу бросается в глаза.
Ну тогда требую пояснений что такое RefC+ Ref- RefV+, где располагается ОУ МУ, так сказать огласите весь список! :) :) :) Да, код собирается нормально, 1606 байт говорит, но залить пока не могу.
Во, это уже нормальный подход. Пояснять - моё любимое :) Потому что это хороший способ находить ошибки. Это "внешние ссылки" к опорнику (задатчику) внутри блока, который измеряем. Точно такие же, как идут к выходу того же блока. Ну т.е. все масштабирующие усилители входами смотрят наружу. Ты помнишь мою навороченную схему от платки? Там даже разъём предусмотрен под это дело.
Схемы - просто пример с целью понять, куда двигаться. Тут либо можно поставить реле на закоротку - отключение для узнавания, какие установки сейчас у измеряемого блока, либо добавить пару масштабирующих усилителей.
Там с расчётом усилителей от опорников блока не совсем тривиально т.к. нужно учитывать больше данных. Во-первых, это опорник измерителя и максимальное значение измерителя. Во-вторых, это максимальное значение на выходе самого измеряемого блока и напряжение опорника, ему соответствующее. Но на деле не так страшно, можно табличку накидать для подбора номиналов...
У меня ещё октрытым остался вопрос о частоте среза мастабирующих усилителей. Полагаю, её нужно выбирать достаточно низкой, однако чтобы можно было обойтись керамическими конденсаторами - они до 10 мкФ легкодоставаемы с распая...
Интересно, но у меня код собирается более коротко 1434 байта. Atmel Studio, настройки "по умолчанию". Вначале собирал прямыми переменными, но передавать в процедуры ссылкаи оказалось и компактнее и быстрее.
А, да... я уже говорил, что кварц у меня сейчас 12 МГц, на схеме указан 14,318 МГц, у тебя 16 МГц, так что возможно надо будет константы для таймеров/периферии посчитать. И там в PortD кое-что плюётся (найдёшь), может конфликтовать с UART...
На той схеме, которая у меня (1902) Ref/Set-V/Set-I только, они? По размеру - это у меня -O2 стоит, сейчас попробовал -Os, получилось 1442 байта. ы. Аааа всё дошло - ты хочешь в обоих режимах работы БП знать какое напряжение/ток заданы независимо от того что на выходе.
Да, именно. До сих пор этот вопрос, как мне известно, никто не решал таким способом. По крайней мере, я не видел таких конструкций. Те блоки, к которых эту возможность можно было наблюдать, работали немного по-другому: у них цифровое управление и задающие напряжения выставлялись при помощи ЦАП с соответствующей индикацией. Примерно так же, как в радиоприёмнике на микросхеме из соседней темы: дал в ЦАП код, его же и показал на экранчике. Но хочется решить это для "аналоговых" блоков.
зы: я за день поискал свои старые материалы по тому блоку, скоро что-нибудь нарисую.
Ну это же не самоцель сделать так, как никто не делал. :) Вообще конечно интересен вариант задатчика напряжений ЦАПом, потом их измерение и корректировка на выходе, но не на этом этапе.
Опять - таки повторять что? И повторять как? А упираться и делать лишь бы было впервые... Есть много схем которые хотел бы повторить, несмотря на то, что известны давно. Вот например лежат TDA7040T и КР174ХА34... их бы в утиль уже, а интересно было бы. ↓ спойлер ↓
оффтоп: Тут схему с кнопкой изобразил. Может будут какие рекомендации?
В общем помучал модуль tm1637, - это что-то, процитирую:
Цитата: undefinedПоследовательный интерфейс, разработанный для этого контроллера, является творческой переработкой широко известного "квадратного" I2C. Отличия состоят в других названиях линий, обратном порядке следования битов, и в отсутствии адреса устройства.
Хотя я его завёл на stm, ну как всегда творческая интерпретация взятого из сети материала. :) Из неприятного - виснет i2c, не пойму почему, наводки/ёмкость... подключен напрямую к ногам мк. Питание должно быть таким же как и у контроллера, ну или уровни выравнять, если питать от 5-ти вольт и управлять с контроллера 3.3 вольта - не работает.
а) согласование уровней обязательно (см. "конструкцию" входа той же STM и представь, что получается, когда туда +5 с чьего-то выхода придёт) б) скорость. TM не особо тороплива, 500 кГц для неё предел, выдерживать же нужно типовые 400 кГц.
В рабочем варианте i2c у меня 100kHz. DS1Z_QuickPrint2.png - хардварный i2c stm32f030. DS1Z_QuickPrint3.png - софт вариант, но пока связи с tm1637 нет, где-то у меня не получается. Но фронты дрыганьем ног получаются лучше.
В хардверном варианте однозначно велик резистор подтяжки - нужно уменьшать. Ещё и вопрос, что там навешано у модуля, в типовой схеме я конденсаторы 100 пФ наблюдал, что до 400 кГц и 10 кОм подтяжки более-менее нормально, а что здесь? p.s. Забыл вставить ссылку. Исправляю недоразумение: https://radioprog.ru/post/198
Обе картинки модуль напрямую к пинам stm. На модуле два резистора по 10k к питанию. При прзвонке сопротивление +5 и DIO = 5k, +5 и CLK = 10k. Схема типовая, прикрепил, только без клавиатуры. Вообще не нравятся они мне в итоге, главное адреса нет, значит на одну линию их никак.
А ёмкости? Можно просто попробовать их убрать и посмотреть, как изменится хардверная осциллограмма. Также интересно, как соотносятся фронты импульсов, а на такой мелкой не видно. Видно только, что там не 100 кГц, а раза в полтора больше (это не проблема, конечно). 10к и 100 пФ (+15 пФ на входе самой микросхемы) дают постоянную времени немного больше 1 мкс, что даже для 400 кГц вполне нормально (из этого типовые номиналы и указаны на схеме). Но если там что-то "лишнее" на проводах, оно может рождать сюрпризы. Потому внимание каждой мелочи...
Безадресная - это конечно косяк, так, микросхемка на "побаловаться". Уж тогда интереснее MAX7219 заюзать...
Но тут же при одинаковых равных осциллограммы, те просто перезаливаю прошивку, в одном случае хардверный i2c, и не снимаю проводов - софтверный, не трогая настройки осциллографа.
А на софтверном ты выходы в режим "открытый коллектор" ставишь? С другой стороны, открытый коллектор для "чистого выхода", когда на нём точно не будет никакого "чужого" сигнала (в общем случае у I2C 'это не так), вполне допустимо не иметь, а иметь самый обычный двутактный выход. Что у софтверного, похоже, и есть.
Также нужно смотреть соотношение во времени фронтов сигналов. Если где-то не хватает (дата выставляется поздновато и т.п.), то вполне возможно неправильное восприятие сигналов микросхемой.
Ну да, они родимые - 10 нан вместо 100 пик. Удивительно что работало. Вот сейчас стало, вообще без ёмкостей. ы. Тут уже два раза плату вольамперметра "чинил", оба раза smd конденсаторы превращались в ~100 Ом.
Совсем другое дело. Там, похоже, общий провод высокого сопротивления ещё маленько портит дело, но вроде к ложным не должно приводить (во время смены одного сигнала есть провал и на втором, хорошо видно в момент времени "начало передачи").
зы: во время 'Ack' TM "сажает" линию Data на землю, так что двутактный вывод на эту линию применять нельзя - обязательно должна быть типа "общий коллектор".
Ещё одно применение - зарядка, точнее "добивка" аккумулятора. Добавил в разрыв плюса на входе реле навесом, и сделал "качели" до 16.2 вольт для Ca/Ca аккумулятора. Правда нарвался на грабли, начал уже функции и дополнительные переменные вводить, задержки итд, короче бился пару часов.... Оказалось выключал пин неправильно те так PORTC |= (0 << PC0);
ну, как так, основы же, элементарная вещь, делал это не счесть сколько раз, все блинки на этом....!!! :o :'(
Да, блин, ошибка элементарная - чисто внимательность. Ну, для того и отладчики есть. Порой поражаешься, "как же такой можно было ляпнуть", но... бывает ;)
А вот по поводу "добивки" до таких высоких напряжений на аккумуляторе хочу предостеречь, ежели хочешь долгой и счастливой жизни аккумулятору.
Воды долил, плотность никак не подниму... Зарядил до 14.8, плотность меньше 1,2. Аккуму пол-года 75-ка Аком (https://akom.su/sites/promo/index.php/akom-modelnyj-ryad/67-akom-75).
Неприятно, но надо "бодяжить" (правильно - "откорректировать") до нормальной плотности. Зарядкой только сульфатацию пластин сделаешь. Сразу не заметно, но следом пойдёт относительно быстрая деградация (уж такой дорогостоящий опыт проделывал несколько раз, и всё на своём кошельке).
Как вариант, слить этот электролит, купить новый, с типовой плотностью (концентрат серной всё-равно не продают), залить.
зы: типовой малообслуживаемый аккумулятор. Может работать годами без вмешательства, и только при длительном простое (в отключенном состоянии 4-6- месяцев, если на машине с сигналкой - раз в месяц т.к. сигналка потребляет) подзаряжать (именно под-) до 13,8-14.0 вольт. А вот добиваться 100% зарядки - imho фетиш, ни к чему хорошему не приводящий...
Это уже глубоко сидит - не доливать электролит... Ночь простоял на ~100 мА токе. Если точнее с утра качели 13.5 ... 15.8 током 1А Измерил сейчас плотность - всё не так плохо мы в синей зоне зоне, плотность поднялась. Может и обойдётся.
Нагородил для кнопки и реле включенного в разрыв плюсового входа вольтамперметра такой код:
Два режим получилось, обычный режим - реле всегда вкл, и режим добивки акк. Кнопкам задержки выдумывал... может и намудрил, работает нормально.\ (Заряжал второй аккум made in Казахстан "Электра", обычный свинцовый 60Ач, влезло в него почти 50Ач током 2А + добивка до 14.8, плотность набрал хорошо.) ы. А всё почему - наконец-то минусовая температура, -15 в начале декабря уже редкость, жалко только снега нет вообще, пару недель назад Ростовскую область завалило снегом, мимо нас за 100 км... Но новый год чувствую опять будет около 0 градусов...
Пытался понять твою функцию ReadADC - запутался. Можно как пример для одного/двух буферов? Вот это PORTD |= 0b00000100 там для чего? SMCR = Sleep_ADC это вместо set_sleep_mode(SLEEP_MODE_ADC)? Выписал кусок: И застрял на адаптации. Про функцию с изиэлектроникс. Если я все правильно понял вот так её сделал. Но на практике не пойму какие значения в Nb и k лучше...
Чтобы сэкономить память, часть результатов можно "складывать" в небольшие суммочки, оставляя структуру буфера кольцевым. Собственно, в моём примере так и сделано - суммируется по сколько-то в одну ячейку (ибо результат от АЦП десятибитный и мы можем спокойно в одну двубайтовую ячейку суммировать до 64 раз). Это даёт резкую экономию расходуемой памяти вкупе с тем посылом, что всё-равно нет необходимости пересчитывать суммы после каждого измерения (ибо это нужно для вывода на индикатор, а вывод на индикатор нет нужды делать быстрее 10 Гц, разумно 4~8 Гц).
Меня больше терзает вопрос АЦП, либо попытаться на хорошей опоре питание vdda на stm32f0 сделать. Или сразу подумать о выборе микросхемы. ADS1115 дешёвый, ADS1220, вот такой AD7705BRZ - тоже вроде ничего... Надо определиться и попробовать.
Так, на счёт АЦП советовать не хочешь ничего... понятно. Вот сделал плату для двух реле и вентиляторного шим`а. Будут замечания? :) Петля vcc какая-то длинная... ;0
Как я могу что-то советовать, когда я не понимаю, какая цель преследуется? Тем более, что ты же сам пенял, что лучше делать, как другие делают и не выдумывать... :-\
MCP320x (https://ww1.microchip.com/downloads/en/DeviceDoc/21034F.pdf) - дёшево, сердито и двенадцатибитно. Работать с ними тоже просто, питание годится как 3.3, так и 5.0
А эти ADS... они же 24-битные. Куда такое? Это как микроскопом гвозди без соответствующей обвязки и конструкции, которые тоже в копеечку влетят...
Не... непонимание какое-то просквозило... забыли. По факту всё равно - те конструкции которые собирал 1 в один мало что было..., узлы какие-то - да. Коннектор J5 - вентилятор, pwm. Реле одно - в разрыв плюса перед входом. Реле второе - замыкание выхода, там указан порт, но повешу просто на кнопку. Эти ADS1115 - недорогие, рублей по 100-150. Про ADS1220 - да загнул.
Посмотрел и эти... в наших краях нету, пощупать не могу. 16-битные, недорогие. Можно, если есть, но опять же, надо знать, зачем оно столько. 16 бит - это вес младшего разряда полторы тысячных процента. Всё тоже должно соответствовать, или 16 разрядов тут же превратятся в те же 12, хорошо если 13. 10 разрядов при опорном 4.096 дают 4 мВ на младший разряд. 12 разрядов - один милливольт. 14 разрядов - 0.25 милливольта. 16 разрядов - 64 микровольта. Для измерения напряжений блока питания 12 разрядов вполне достаточно, остальное всё-равно избыточно. Для какой-нибудь точной измриловки - это интереснее, но там и аналоговую часть надо вылизывать, и детали прецизионные ставить. ОУ со смещением два-три милливольта и дрейфом 50 мкВ на градус испортят эту ложку мёда бочкой своего...
Крутил - вертел по всякому, опять вернулся к простому усреднению с оверсэмплом. Ну скачет с буфером и всё тут, пока аккумулятор висел, понятно - всё супер. Обновление значений сделал редкое, как-раз где-то 8-12 Гц. Этот вариант оставлю - пусть работает такая точность меня устраивает. Видео (https://youtu.be/fE0cK-Uy6FU).
Нужно убедиться (при помощи "закладок" в порты, про них рассказывал ранее), что АЦП работает синхронно с производством выборок из него. На Си и у меня такая проблема и встала, пока я не стал стопить АЦП, в то время как на асме почему-то не было вопроса. Производство чтения из регистров АЦП в то время, как он ещё не закончил обсчёт, приводит к неверным показаниям и прыганию общих результатов.
оффтоп: мне определённо нравятся TPS5430/5450 - не капризные совсем, припаял - работает. Если помнишь, ещё на коте мучился с L5973 - попили крови... >:(