eddy_em: (Костерок)
eddy_em ([personal profile] eddy_em) wrote2015-02-01 11:29 am
Entry tags:

Реверс пятизначного вольтметра на STM8

Вчера я занимался "реверсом" пятизначного вольтметра (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, чтобы работа с АЦП не мешала динамической индикации (скажем, считывание на прерывания повесить).

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org