Top.Mail.Ru
? ?
flag

eddy_em


Емельянов Эдуард Владимирович


Previous Entry Share Flag Next Entry
Реверс пятизначного вольтметра на STM8
Костерок
eddy_em
Вчера я занимался "реверсом" пятизначного вольтметра (buyincoins), (ebay). Конечно, я так и не понял, зачем китайцы запихнули туда 5 знаков, если он и 4 вряд ли сможет точно во всем диапазоне (0..30В), но захотелось для него прошивку написать.
Реверсинг 5-значного вольтметра; тест на 12В
Вот и сам вольтметр, тест на 12В

Проведенный на кухне вечер дал плоды: я нарисовал в кикаде схему, а также определился с логикой работы. Остается "всего лишь" написать прошивку. Результаты — в репозитории stm8samples на сосфорже и гитхабе. Под катом — файл README с некоторыми пояснениями.
Схема вольтметра


Логично, что т.к. для покрытия пяти знаков необходимо как минимум 15 бит (учитывая ограничение в 30000мВ), здесь используется внешний АЦП. В данном случае это MCP3421. Я оставлю в стороне свои сомнения по поводу достижения точности лучше 1мВ на этом чуде китайской промышленности и перейду к делу.

АЦП


Информация из даташита:

Вкратце процитирую даташит (для любителей "tl;dr").
АЦП достаточно просто работает, на все про все у него один конфигурационный регистр:
|!RDY|C1|C0|!O/C|S1|S0|G1|G0|

  • !RDY == 0 когда преобразование окончено (МК может послать NAK и считать данные); при записи в непрерывном режиме не учитывается

  • C1,C0 не задействованы

  • !O/C - режим преобразования (по умолчанию 1): 1-непрерывное, 0-одноразовое

  • S1,S0 - разрешение (по умолчанию 0): 00 - 12, 01 - 14, 10 - 16, 11 - 18 бит

  • G1,G0 - усиление (по умолчанию 0): 00 - 1, 01 - 2, 10 - 4, 11 - 8


Поддерживаемые скорости: 100кбит/с, 1400кбит/с и 3.4Мбит/с. Вот здесь интересно: вроде бы, можно было бы и на 1400 работать, если бы китайцы подключили ноги SCL/SDA АЦП к соответствующим ногам аппаратного I²C микроконтроллера (или хотя бы на UART), но они сделали софтовую эмуляцию протокола. Т.е. это задачу несколько усложняет (не забываем, что нам еще надо аж 5 цифр динамически отображать).

Протокол: стартовый бит, данные (MSB first, данных может быть сколько угодно байт), стоповый бит
Каждый байт данных оканчивается битом ACK.
Данные считываются при SCL==1, если SCL==1, а SDA меняется, это START(1->0) или STOP(0->1)

Обе ноги у АЦП работают в режиме "открытый сток", а у микроконтроллера надо настроить так: SCL - push/pull (можно и open drain, но тогда нужно будет допаять отсутствующий резистор R6 на 5.1к); SDA - open drain.

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

После стартового бита первый байт - адрес (4 бита - код устройства - 1101, 3 бита - адрес устройства/000 по даташиту/, 1 бит - R/!W)
В режиме записи (R/!W = 0) пишем конфигрегистр
В режиме чтения (R/!W = 1) устройство посылает данные (1 байт == 8 бит данных + ACK бит, после адреса ACK инициируется устройством, после данных - микроконтроллером) и конфигурационный регистр.

В 18-битном режиме отсылаются 3 байта данных, следом 1 байт конфиг; первые 7 бит первого байта данных - MSB (знак операции, т.е. ноль), LSB третьего байта - LSB данных.

GENERAL CALL
Если первым байтом передать нули, то второй байт читают все устройства линии. Второй байт:
== 0x06 - сброс АЦП на настройки по умолчанию
== 0x08 - одноразовое преобразование

Сигналы:

  • NOT BUSY (умолчательное состояние) - SDA=1, SCL=1

  • START DATA TRANSFER - SCL = 1, SDA = 1->0

  • STOP DATA TRANSFER - SCL = 1, SDA = 0->1

  • DATA VALID - данные не должны изменяться при SCL=1



ACK: девятый такт SCL на каждом байте данных используется как ACK. Для прерывания чтения достаточно установить SDA=1 на этот бит
Этот бит нужно проверять: если АЦП сдох, то будет 1 вместо 0 при попытке записи чего-нибудь в АЦП (т.е. на этот бит при записи нужно отпускать линию - устанавливать SDA=1 - и читать состояние; а при чтении обязательно подтягивать его к нулю, иначе АЦП решит, что МК на него обиделся)

Таким образом, для работы с АЦП достаточно выполнять следующие действия.
После включения нужно инициализировать АЦП: 1 байт - start(0);1;1;0;1;0;0;0;0;ACK(1)
2 байт - 0;0;0;1;1;1;0;0;ACK(1)
не забываем проверять ACK: если он !=0, то АЦП сдох
Далее - непрерывно читаем: 1 байт - start(0);1;1;0;1;0;0;0;1;ACK(1)
2-5 байты: 8 бит; ACK(0)
6 и последующие (пока тикает SCL и не установлен ACK/STOP): конфиг
Читаем конфигурационный регистр до тех пор, пока в не получим RDY==1; затем прерываем операцию (ACK=1,STOP) и инициализируем следующую порцию считывания; здесь уже данные сохраняем (отображаем сразу, либо же накапливаем для усреднения, скажем, по 8 штук).
Прервать операцию чтения можно в любой момент, отправив бит STOP.

ВОЛЬТМЕТР


Теперь перейдем собственно к железячной схеме и алгоритму работы.
Вычисление.
В 18-битном режиме реально АЦП дает 17 бит (т.к. у нас однополярное напряжение без сдвига). Таким образом,
LSB=2.048V/2¹⁷
Входное напряжение уменьшается резисторным делителем в kr раз (17.5 в идеальных условиях), следовательно, U = ADU*2.048*kr/2¹⁷, где ADU — показания внешнего АЦП (коэффициент усиления = 1). Кстати, при желании можно на малых напряжениях увеличивать коэффициент усиления. Но я сильно сомневаюсь, что реально получится поднять точность...
Следовательно, для вычисления напряжения в милливольтах (используем 32-битную целочисленную арифметику, т.к.
float не справится, а double и 64-битные целые sdcc не умеет) используем формулу
U = (ADU*K)>>17
K - коэффициент преобразования, по умолчанию K = 2048*17.5=35840, но при калибровке мы его можем поменять.
Из-за того, что коэффициент K занимает 15-16 бит, может возникнуть ситуация переполнения, т.е.
нам нужно еще и хранить ADUmax — предельное значение, выдаваемое АЦП, которое еще можно обработать
это значение вычисляется так:
ADUmax = 0xffffffff/K
(по умолчанию 119837)
"Умолчательное" ограничение дает нам предел измерений: 32.758В (естественно, в реальных условиях этот предел
варьируется как минимум на 5% из-за погрешности резисторов делителя).
Коэффициент K при калибровке вычисляется так:
K = Uref<<17/ADU = Const / ADU
Uref — опорное напряжение в милливольтах (жестко зафиксировано при компиляции).
Следовательно, Const можно вычислить на этапе компиляции, скажем, для Uref=12000мВ мы имеем
Const = 12000<<17 = 1572864000; для улучшения калибровки можно считать значение ADU 16 раз и усреднить.
Служебный разъем имеет подключение к ноге 3 (PD6/UART1_RX), следовательно, используется для калибровки.
Вариантов калибровки 2 (причем, можно реализовать их одновременно):

  1. при получении на PD6 логической 1 производится считывание результата, вычисление и запоминание констант

  2. при получении данных по UART1 вольтметр воспринимает их в качестве множителя, вычисляет предельное значение и сохраняет константы

Во втором варианте можно пробежаться по всему диапазону (0..30В) и линейной аппроксимацией вычислить оптимальный коэффициент.
Режим отображени вычисленных данных прост:

  • динамическая индикация на 5 позиций (т.е. нужно либо поочередно читать/показывать, либо делать это псевдоодновременно, но ждать окончания преобразования, непрерывно читая, не стоит)

  • после вычисления U в милливольтах нам надо отобразить это значение:

    • если U < 10000, т.е. используется 4 знакоместа, мы рисуем 4-значное число (дополняя спереди нулями), а десятичную точку ставим в четвертой позиции (digit2)

    • иначе рисуем на пяти знакоместах, просто выводя целое; десятичную точку ставим в пятой позиции (digit1)

Вот, собственно, и все. Самым сложным в данном случае будет реализовать софтовый I²C, чтобы работа с АЦП не мешала динамической индикации (скажем, считывание на прерывания повесить).


promo eddy_em september 27, 16:30 Leave a comment
Buy for 10 tokens
Как уже писал, код закончил утром, а только что закончил Readme.md дописывать. Вот и сделал пуш во все свои кодохранилища. Издевался над парочкой из более свежей партии: Самый первый почему-то перестал "общаться" по RS-232 (однако, по модбасу и кэну работает - видимо, преобразователь уровней…

  • 1
Как вариант повышения точности/снижения ошибки – считать медиану для серии отсчетов (циклического буфера).

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

А можете дать ссылку/название этих алгоритмов? Тоже есть проект (даже реализованный «в металле»), где медиана смотрелась бы лучше среднего, но сдерживает необходимость сортировки.

Edited at 2015-02-01 11:20 am (UTC)

Не помню, честно говоря, названий: брал из "C snippets". Для произвольного числа членов функция называлась quick_select, для фиксированного - opt_med.
Здесь пример (мой старый astrovideoguide).

Для микроконтроллеров я еще медианную фильтрацию не использовал — не было необходимости (т.к. обычно у меня микроконтроллер к компьютеру подключен, соответственно, вся математика — в т.ч. преобразование из ADU в реальные единицы — идет на компьютере).

При обработке изображений медианная фильтрация значительно лучшие результаты дает, т.к. избавляет от выбросов (шум типа "соль-перец"). Ну и в некоторых случаях для обработки одномерных данных использую скользящую медиану (если не получается аппаратно убить шумы).

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

Алгоритмы opt_medX выполняют частичную пузырьковую сортировку (нам нужен средний элемент, поэтому полностью сортировать смысла нет).

Я на AVR и ICL7135 делал с предусилителем и драйвером ШД для монохроматора.

Приветствую! Есть у меня вольтметр, он на STM8S103F2P6, на нем я увидел контакты TXD/RXD, к сожалению, по найденной в сети инфе, никакие данные туда он не выводит (сам не проверял, если что).



Занимались ли вы реверсингом таких вольтметров? Меня интересует индикация данных (собственно, то, что он и делает) и параллельное считывание этих данных по I2C/UART/etc. какой-нибудь платкой. У меня, например, есть платка на базе Atheros AR9331 с OpenWRT-подобной ОС.

Именно таких — нет. Но мой код для любых подойдет, нужно лишь соответствующие изменения в распиновку внести.
Для реверса схемы если под индикатором есть элементы, возможно, придется индикатор выпаивать.
Судя по контактам Rx/Tx, выведенным "наружу", можно сделать вывод, что каждый вольтметр после прошивки индивидуально калибровался. Знака всего три, так что ног свободных полно.

Подключать это чудо к чему-то для считывания показаний не считаю разумным: дешевле на нормальном АЦП с прецизионным источником тока (или хотя бы резистором с очень низким тепловым дрейфом опорное напряжение снимать) сделать человеческий вольтметр. Да пусть даже на STM8, если нужен не вольтметр, а показометр! Главное — по-человечески развести и не париться. Потому что китайская схемотехника — это жесточайшая содомия! Ради удобства разводки они зачастую лепят программный I2C/UART/etc, хотя есть аппаратный! Извращенцы чертовы!!!

Охотно верю, но я в этом не рабрираюсь, я не электронщик, думал есть относительно простой способ и показать напряжение визуально и одновременно снять его для запихивния в лог и дальнейшей отрисовки графиков (в этом плане есть все нужное, RRD и т.д.), а оказывается более менее умных /аппаратных/ датчиков-то и нет. Обидно...

Можно, конечно. Для этого нужно как минимум сменить прошивку "вольтметра".
Есть в продаже и готовые вольтметры с выводом данных хоть по тому же RS-232. Но там и цены соответствующие, т.к. вольтметры, а не показометры.

Для сопрежения же с компьютером лучше на STM32 делать: там и USB уже есть, да и флешку можно прикрутить, чтобы на нее данные писать.

C компьютером и не нужно, только с платкой, она на Atheros AR9331 и размером с коробок спичек. Она как Ардуино может дергать данные с датчиков, например. Там I2C, Serial и GPIO порты.

  • 1