У меня как-то исторически не сложилось с семейством STM32F030, лет 5 назад попробовал поработать с ними и долго удивлялся корявости работы большей части периферии, а потом забил на них. И вот на днях мне все таки пришлось вернуться к данной серии, нужно было измерять за минимальные деньги постоянное напряжение на свинцовом АКБ (или сборке до 4 штук последовательно) от 8 до 60В с точностью не хуже ±0.1В с небольшой частотой опроса.
Решение задачи «в лоб» позволило достаточно точно измерять напряжение только когда на входе АЦП значение больше 1,5...1,6В, то есть только во второй половине диапазона, что для меня означало 30...60В вместо требуемых 8...60В. Основная проблема была в интервале 0...1.6В, выглядело это все как будто у меня делитель напряжения «плавал» или опорное напряжение для АЦП (Vref) было крайне нестабильным.
Нужно было быстро решать задачу, пускай и не самым элегантным способом, но хотя бы без явных костылей. Для этого предстояло сначала изучить проблему и понять откуда «ноги растут», а затем устранить эту проблему. Если устранить не получится, то хотя бы как-то обойти ее, чтобы в итоге получить работающее устройство и отправить его заказчику.
Суть задачи
Вообще я за такую мелочь давно старюсь не браться, но тут ко мне обратился родственник, а по совместительству хороший человек, который еще и работает в близкой мне тематике — собирает где-то в в Подмосковье небольшие СЭС. Отказывать не хотелось, да и в тот момент эта задача мне казалась «пара часов железо + пара часов код». Проект в Altium Designer и правда занял у меня пару часов, а вот борьба с АЦП съела весь вечер, поэтому решил поделиться информацией, чтобы другие не тратили время.
Само устройство крайне простое, алгоритм работы следующий:
- измеряем напряжение на сборке из 1...4 последовательно включенных свинцовых АКБ;
- если напряжение меньше «нижнего порога», то замыкаем реле и оно включает генератор, который заряжает наши АКБ;
- если напряжение поднялось вы «нижнего порога + гистерезис», то есть АКБ зарядились до установленного порога, тогда выключаем генератор;
- если напряжение выше «верхнего порога», то запрещаем на всякий случай включать генератор.
Все! Пример: есть один АКБ на 12В и от него питается инвертор. Если напряжение упало ниже «нижнего порога», по умолчанию 10.2В, то включаем генератор. Если напряжение на АКБ выросло до «нижнего порога + гистерезиса», то выключаем. По умолчанию гистерезис установлен 2В и нужен для того, чтобы бензиновый генератор не вырубался сразу как только чуть зарядил АКБ до 10.3В. От постоянного вкл/выкл генератор просто умрет. Ну и на всякий случай защита: если напряжение на АКБ выше 14.4В, то генератор точно не включать.
Алгоритм простой и понятный, дополнительно нужно было сделать небольшое меню, чтобы можно было изменять три переменные: «нижний порог», «гистерезис», «верхний порог». Ничего сложного, но дьявол в мелочах.
Изначально в компании, где работает родственник, использовали китайское устройство со схожим функционалом. Из мелких минусов — нельзя было изменять гистерезис, для питания нужен был дополнительный источник на 5В и измерение всего до 30В, то есть для 1 или 2 АКБ. Из больших минусов — китайское устройство иногда зависало и перезагружалось в момент запуска бензинового генератора, которым управляло. Последняя «особенность» как раз и стала причиной попытки отказаться от китайского решения.
От меня хотели устранения всех этих минусов и чтобы цена устройства была как у китайского, то есть 10$. «Дьявольской мелочью» в данном случае было то, что они хотели покупать у меня готовое устройство за 10$ партиями всего по 20-30 штук, правда стабильно и достаточно часто. То есть мне надо было в мелкой серии сделать устройство сильно лучше и очень сильно дешевле китайцев, заработать то тоже надо в перспективе. Ага, мне тоже было смешно в первые 10 минут, но к моменту осознания сей ситуации я уже сказал «ДА», то есть за Волгой для меня земли уже не было…
Решение железных проблем
Как я выше писал, основной проблемой является нестабильная работа устройства во время запуска генератора. В итоге было куплено китайское устройство с алиэкспресс для тестирования и исследования. Основная причина «сноса башки» оказалась не в генераторе, а в реле :)) В момент переключения по питанию в шине 3.3В проходил импульс с амплитудой около 25В, что как бы намекало… Так же помехи шли и на сигнальные цепи. В китайской схеме для борьбы с такой проблемой стояли диоды LL4148, которые типа преграждали путь помехам. Этого достаточно оказалось, чтобы устройство работало нормально на столе, но не в условиях кучи внешних дополнительных помех типа генератора и прочего оборудования. Чтобы навсегда избавиться от выше описанного я решил применить гальваническую развязку через связку «оптрон + dc/dc», что позволило полностью исключить электрический контакт и путь прохождения помех между управляющей обмоткой реле и остальной схемой.
Альтернативой такому решению было применение защитных TVS диодов вместе с синфазным дросселем, а так же усложнение фильтра по питанию. Но зачем такой колхоз? Поставить dc/dc проще, а на практике оказалось даже дешевле — китайский модуль Mornsun B0505S-1WR2 обошелся мне в 0,4$ при стоимость одного синфазного дросселя на мелкой партии около 0,32$.
В итоге после такого решения и тестирования прототипов устройство начало работать как автомат Калашникова и проблемы с перезагрузкой ушли. Вообще я немного удивлен, что реле + чутка генератор все таки заставляли перезагружаться stm-ку, китайские разработчики в принципе сделали все неплохо: 10 кОм + 0.1 мкФ на reset, блокирующие конденсаторы по питанию, ферритовые бусинки, все было, но этого все равно оказалось мало.
Второй минус «китайцев» был в необходимости дополнительного питания, сэкономили видимо на dc/dc. Я решил проблему в лоб — взял питание с входного сигнала, прямо с одного разъема. Для этого надо было просто поставить dc/dc, который переварит минимум 4 * 14.4В, то есть 57,6В. Мой выбор пал на LMR16010PDDAR. Во-первых, это Texas и этим все сказано. Во-вторых, мне данную микросхему азиатские товарищи предложил таскать очень дешево.
Предыдущий пункт комплексно решил третий минус — возможность подключать до 4-х АКБ последовательно. DC/DC легко переваривает 60В, начинает пытаться сгореть только при 72...73В, так что максимальные 57.6В ему точно не страшны. Делителю напряжения вообще все равно сколько на входе, поэтому все решилось с минимальными усилиями.
Посмотреть как все это реализовано на схеме вы можете тут — PDF. Схема достаточно большая, поэтому картинкой не стал заливать. Кстати, в pdf-ке вы можете увидеть еще и габариты с печатной платой, но там ничего сверхестественного.
В итоге для первой пробно-тестовой партии было заказано компонентов на 10 устройств, а после сборки получилось вот так:
Без происшествий не обошлось — когда создавал компонент для dc/dc модуля перепутал 1 и 2 ногу местами, пришлось немного наколхозить. Хотя на последующих платах сделал аккуратнее, чтобы никто не заметил, а плата на фото осталась у меня в качество отладки на всякий случай или для доработок по софту, если заказчик чего придумает во время тестирования.
Борьба с точностью АЦП
Теперь перейдем к основной части статьи. Как писал в начале статьи — АЦП у F030 оказался неточным, то есть до напряжения 30...32В на входе устройства показания плавали с отклонением до 15...20%, а затем ошибка плавно сходила на нет. Одно меня радовало — на первый взгляд отклонения имели какую-то закономерность, а значит это не рандомная ошибка и ее можно отследить и попытаться скорректировать.
Давайте чуть подробнее об ошибке… АЦП после завершения преобразования отдает сырые данные в регистр DR, который содержит значение от 0 до 4095 (212). Чтобы пересчитать данное значение в напряжение нужно умножить его на шаг квантования. В моем случае напряжение на выводе VDDA, с которого АЦП берет опору, составляло 3,3072В и соответственно шаг равен 3,3072В / 4096 = 0,000807В, я его округлил до 0,0008. Чтобы получить напряжение на входе устройства, полученное напряжение нужно умножить на коэффициент делителя напряжения, в моем случае резистор в верхнем плече 100 кОм, а в нижнем 4,7 кОм, что дает делитель 22,2765. Исходя из этого напряжение на входе устройства, то есть напряжение АКБ, находится с помощью формулы:
float voltageReference = 0.0008;
float voltageDivider = 22.2765;
adcVoltageResult = (float)adcData * voltageReference * voltageDivider;
Получается, что после считывания данных ADC1->DR, они приводится к типу float и просто умножаются на коэффициенты, которые константы, и получаем результат в привычных вольтах. На практике оказалось, что все сильно плохо с точностью.
Вспомнив о бритве Хэнлона, я начал искать место где совершил ошибку. Сначала проверил напряжение на ноге VDDA, думал что оно как-то плавает и зависит от входного напряжения, например, LDO неисправный. Вооружившись настольным мультиметром следил за напряжением на VDDA и изменял напряжение на входе от 8 до 60В, при этом напряжение на ноге VDDA мертво держалось на отметке 3,3072В плавали только следующие 2 знака, что очень хорошо для линейника за 10 центов.
Следующим местом потенциальной ошибки был делитель напряжения. Хотя мне показалось странно, что резисторы от Bourns на ±0.1% плавают так, что данные имеют ошибку до 20% и эта ошибка имеет нелинейный характер. Провел такой же эксперимент: мультиметром измерял напряжение после делителя, а входное напряжение изменял с шагом 0.5В и в итоге коэффициент делителя был так же намертво зафиксирован на уровне 22,2768.
В этот момент начало становиться интересно. Оставался один компоненты в котором я мог сомневаться — это операционный усилитель LMV611MFX. Включен данный ОУ как повторитель напряжения. Напряжение ДО и ПОСЛЕ него было одинаковым до 4-х знаков после запятой. Странно… По даташиту он неплох и это все тот же TI, сомневался, но решил проверить, т.к. именно этот ОУ никогда не использовал. На всякий случай впаял на его место мой любимый и проверенный в куче проектов OPA320, который у меня лежит в катушках и он показал такой же результат.
Оставался последний компонент — МК, а именно его АЦП. За годы применения STM я привык доверять их продукции, тем более беру только оригиналы, поэтому на МК подумал в последнюю очередь. Первым делом подумал, что забыл сделать калибровку или сделал неправильно. Полез в reference manual, там требовали не только вырубить АЦП записью нуля в бит ADEN, но и выставить 1 в бит ADDIS и 0 в бит DMAEN. Последние 2 шага не были сделаны, обычно я вырубаю АЦП и все работает хорошо, в итоге поправил кусок кода с калибровкой:
ADC1->CR &= ~ADC_CR_ADEN; // Выключили АЦП
ADC1->CR |= ADC_CR_ADDIS; // И еще раз выключили
ADC1->CFGR1 &= ~ADC_CFGR1_DMAEN; // И еще раз, но уже DMA
ADC1->CR |= ADC_CR_ADCAL; // Теперь нас благословили на калибровку
while (!(ADC1->CR & ADC_CR_ADCAL)); // Ждем божьей милости и окончания действа
К сожалению не помогло и решил провести следующий опыт… Коэффициенты уже проверил и они 100% правильные, значит буду подавать на вход напряжение с лабораторного блока питания, изменять его и выводить сырые результаты измерения АЦП на семисегментный индикатор, а затем сравню с тем, что должно там быть с тем, что реально измерил. В итоге получил следующие результаты:
Как видите теоретический график имеет отличную линейность, т.к. не привязан к железу. График, построенный по реальным данным, тоже практически линейный с минимальными отклонениями. По сути график с реальными данными может быть совмещен с теоретическим графиком путем параллельного переноса на некоторую константу. Если говорить языком электроники — у АЦП имеется смещение!
По данным, на основе которых строились графики, я выяснил, что АЦП имеет смещение в разных точках 71...73 шага. В этом и была проблема, а «нелинейность» мне почудилась потому, что смещение в 71 шаг при 10В это около 14%, а при 30В уже 4%. То есть если построить график отклонений в %, то зависимость будет иметь экспоненциальный вид, но такой график не интересен.
Было решено, что для уточнения результатов, попробовать ввести в формулу еще одну переменную, которая будет смещать мои значения вверх и иметь следующий вид:
uint16_t offsetVoltage = 72;
float voltageReference = 0.0008;
float voltageDivider = 22.2765;
adcVoltageResult = ((float)(adcData+offsetVoltage))*voltageReference*voltageDivider;
После этих несложных манипуляций мое устройство начало точно измерять напряжение и данные перестали плавать. До этого же момента оно врало на 72 * 0,0008В * 22,2768 = 1,28В, что в случае контроля одного АКБ очень критично. Свинцовый АКБ конечно не взрывается как Li-ion, но все равно быстро выходит из строя, особенно если его разряжать постоянно не до 10.2В, а до 8,92В.
Вот такая небольшая история о небольшой железке. Надеюсь кому-то данный материал станет полезным или хотя бы будет просто интересным для чтения. Будьте внимательны со всякими этими АЦП и прочими гадостями :))
Немного новостей
Новость номер раз — у товарищей из PCBway, появилась возможность теперь делать сложные и очень сложные прототипы по очень вкусным ценами:
- Стали доступны СВЧ материалы Rogers 4003C и Rogers 4350B;
- Появилась возможность делать слепые и глухие переходы, 50 мкм и прочие прелести HDI.
Самое интересное, что в честь сего события еще и действует скидка 30%. Думаю еще месяца 2 будет действовать акция, так что если хочется быстро и дешево сделать прототипы, то вперед.
Я тут заказал себе уже на тесты HDI платы с Cyclone 10LP и DaVinci на борту, так что про них еще и расскажу через некоторое время, а так же об особенностях проектирования плат с BGA компонентами.
Новость номер два — у PCBway сейчас проходит конкурс. Еще есть 2 месяца на подачу заявки. Правила простые. Печатные платы для вашего проекта вам сделают бесплатно. Собственно призы — чуть чуть бумажек с американским дядькой на paypal + виртуальная валюта, которую можно потратить на покупку печатных плат + почет и уважение + частенько предлагают работу хорошую где-нибудь не в России. Студентам будет особенно интересно, тем более техническая конкуренция не особо высокая, шансы быть замеченным и на победу у «крепких» DIYщиков высокие.
Автор: NordicEnergy
Интересно придумать проблему и героически её решать) Ошибка в расчете коэффициента делителя 100/4,7 = 21,276595, а не 22… И ещё, возможно стоило повысить Sampling Time