Драйвер ads1118 для stm32

Продолжаю писать про создание своей печки. Теперь хочу представить вашему вниманию драйвер для термопары ADS1118. Хочу рассказать с какими сложностями столкнулся и как их решал.
Дома сейчас 24.4 градуса. Весна. Батареи на всю катушку жарят...
Свои изыски прилагаю, может кому пригодится.

Введение:
За основу схемы и программы были взяты данные приведенные в даташите (и еще один даташит, полнее). Все на английском, поэтому решил привести описание протокола работы и некоторые детали реализации.

Схема:
Схема из даташита
Ей в деталях посвящен целый раздел в даташите: THERMOCOUPLE MEASUREMENT WITH COLD JUNCTION COMPENSATION. Что это все значит я не знаю. Не хватает знаний и опыта. Я просто сделал как сказано: поставил и конденсаторы и резисторы. "+" термопары подключается к AIN0 или AIN2 (в зависимости от используемого канала). Как читать данные c нужного канала опишу ниже.
Микросхема прекрасно работает от 3.3 вольт. В доке сказано что она может работать при напряжении от 2 до 5.5 вольт.

Общее описание:
Микросхема управляется одним 16-битным регистром. Он полностью задает параметры и режим работы микросхемы.
Регистр настроек. Часть 1
Регистр настроек. Часть 2

Bit 15 OS: При записи 1-ки в режиме одиночного преобразования начинает новое преобразование.
при чтении: 0 — идет преобразование, 1 — преобразование не идет.
Bits[14:12] MUX[2:0]: Режим работы мультипрексера:
000: AINP = AIN0 and AINN = AIN1 (default)
001: AINP = AIN0 and AINN = AIN3
010: AINP = AIN1 and AINN = AIN3
011: AINP = AIN2 and AINN = AIN3
100: AINP = AIN0 and AINN = GND
101: AINP = AIN1 and AINN = GND
110: AINP = AIN2 and AINN = GND
111: AINP = AIN3 and AINN = GND

Bits[11:9] PGA[2:0]: Программирование усиления сигнала
000: FS = ±6.144V
001: FS = ±4.096V
010: FS = ±2.048V (default)
011: FS = ±1.024V
100: FS = ±0.512V
101: FS = ±0.256V
110: FS = ±0.256V
111: FS = ±0.256V
Не должно превышать напряжение питания более чем на 0.3 вольта.

Bit 8 MODE: Режим работы:
0: Непрерывное преобразование
1: Режим одиночного преобразования (default)

Bits[7:5] DR[2:0]: Частота преобразования
000: 8SPS
001: 16SPS
010: 32SPS
011: 64SPS
100: 128SPS (default)
101: 250SPS
110: 475SPS
111: 860SPS

Bit 4 TS_MODE: Режим работы:
0: Внешний датчик (default)
1: Внутренний температурный датчик

Bit 3 PULL_UP_EN: Есть ли подтяжка DOUT к питанию. Подтяжка автоматически отключается как только CS поднимается.
0: Подтяжка отключена (default)
1: Подтяжка подключена

Bits[2:0] NOP: Зачем это надо, не знаю. Я предполагаю, что это что-то вроде защиты. Только если передано 01 — произойдет запись в регистр.

Bit 0 CNV_RDY_FL: Флаг готовности данных:
0: Данные готовы. Нет преобразования.
1: Данные не готовы. Преобразование идет (default)

Режимы работы:
continuous conversion mode — непрерывное преобразование
single-shot conversion — одиночное преобразование.
В первом случае, после завершения преобразования сразу начинается следующее. Во втором случае, после окончания преобразования, микросхема засыпает до прихода новой команды. Как говорит дока: In this configuration, the ADS1118 consumes about 1/100th the power of the ADS1118 operated in continuous conversion mode (В этом режиме, ads1118 потребляет примерно 1/100 от энергии в режиме непрерывного преобразования).

ADC mode — оцифровка внешнего сенсора
Temperature sensor mode — оцифровка внутреннего датчика температуры

Если с внешним все понятно, то о внутреннем чуть подробнее. Т.к. при работе с термопарой у нас фактически 2 спая: «горячий» — который нам и интересен и точка подключения к схеме или «холодный спай». Для более точного измерения температуры, данные с «холодного спая» требуют компенсации. Для этого используют режим «Temperature sensor mode» и дальше, программно, компенсируют ток «холодного» спая. Это толком нигде не описано (или я не нашел). Свое решение приведу ниже.

Готовность данных:
После готовности данных, на выводе DOUT/DRDY выставляется НИЗКИЙ уровень. Это актуально только в случае низкого уровня CS. Вообще, дока рекомендует держать CS всегда в низком уровне, если больше на шине никого нет (If the ADS1118 does not share the serial bus
with another device, CS may be tied low.).

Результат:
Данные в регистре преобразований кешируются, т.е. если вы читаете его, а в этот момент завершилось очередное преобразование, вы все равно вычитаете верные данные. Новые данные будут записаны после окончания чтения (In One-Shot mode, the conversion data are buffered, holding the current data until new conversion data replace it).

Протокол взаимодействия:
По сути это стандартный SPI. Одновременно идет запись 16 бит регистра конфигурации и чтение данных из регистра результата преобразований. Тут есть тонкости:
1) Если продолжать подавать такты, после записи регистра конфигурации, то в след. 16 бит придет только-что записанный регистр конфигурации — таким образом микросхема подтверждает корректность полученных данных (можно использовать для чтения). Во втором блоке рекомендуется передать тот-же набор параметров что и в первом. Опционально, можно прижать DIN к низкому уровню, тогда микросхема проигнорирует его.
2) Если хочется прервать передачу, то после 16-ого такта, необходимо поднять CS, что сбросит состояние SPI интерфейса (If Config register data are not required to be readback, the ADS1118 conversion data can also be clocked out in a short 16-bit data transmission cycle, as shown in Figure 46. Therefore, CS must be taken high after the 16th SCLK cycle. Taking CS high resets the SPI interface).
3) Если удерживать тактовый сигнал в высоком состоянии более 28мс, микросхема перезагрузит свой SPI интерфейс (Holding SCLK low longer than 28ms resets the SPI interface.).
4) Передавая настройки, вы задаете данные для СЛЕДУЮЩЕГО преобразования. А читаете в это время, уже прошедшее преобразование по предыдущим настройкам.

Конвертация данных в температуру:
Отдельная проблема возникла при переводе того, что отдавала микросхема в температуру.
Внутренняя температура выдается четко, согласно документации: 14 бит выравненные влево. Каждый бит дает 1/32 градуса. Делим на 4 (сдвигаем вправо на 2 бита) и на 32, получаем градусы внутри микросхемы. Т.к. она очень близко к месту подключения термопары («холодному спаю»), то его температура похожа на температура «холодного спая». От сюда мы можем вычислить поправку.
А вот с «горячим спаем» возникла проблема.
1) 0 оказался сдвинут в отрицательную область. Причем величина сдвига зависит от температуры «холодного спая».
2) Коэффициент передачи не 1/32 (как сказано в документации).
Возможно все это из-за ошибок в резисторах-конденсаторах и моей термопаре.
В ходе ряда экспериментов со льдом (в качестве нуля температуры) и с кипящим чайником (100 градусов), была собрана статистика и подобраны формулы преобразования дающих хорошие данные на 0-100 градусов (+-0.5 градуса). Выше пока не проверял. Пока экспериментировал, выяснил, что мой мультиметр считает температуру кипения воды 94 градуса :(.
В итоге, я просто подобрал формулы перевода так, чтобы 0 и 100 были четко, а в промежутке надеюсь на лучшее… Хотя контрольный замер с мультиметром показал хорошее совпадение (с учетом компенсации 94 градуса в 100).
Благодаря очень ценному замечанию DeepSOIC, пересмотрел принцип работы. Написал отдельную статью и сделал пояснение:
Математическое пояснение:
Пусть:
t – измеряемая температура
vx – измеренное значение «холодного спая»
vg — измеренное значение «горячего спая»
vg0 — измеренное значение «горячего спая» если бы измерение проводилось при vx = 0
КТ – коэффициент перевода vg0 в t:
(1)	t = vg0/КТ

Но как правило, vx не равно 0, поэтому нам надо привести vx к нулю, изменив vg на некую величину, зависящую от vx:
Пусть kx – коэффициент компенсации:
(2)	vg0 = vg + vx/kx,     vx = 0

Предположим, что kx линейно зависит от vx и vg:
(3)	kx = c + k1 * vx + k2 * vg

Для нахождения c, k1 и k2, необходимо решить систему линейных уравнений с 3-мя неизвестными для известных kx, vx, vg. vx и vg мы измеряем непосредственно.
kx мы находим по формуле (1) и (2), зная температуру в точках измерения:
(4)	kx = vx/(vg0 - vg) и vg0 = t * KT 	=>	 kx = vx/(t * KT - vg)

Итого из (3) и (4):
(5)	vx/(t * KT - vg) = c + k1 * vx + k2 * vg

Но (5) это уже не линейное уравнение, поэтому проще всего подобрать КТ по минимальной ошибке решая систему линейных уравнений для каждого КТ.
Реализация:
Как всегда:
1) Я люблю решение, которое можно использовать где угодно (не только с STM32, а вообще везде, где МК потянет по скорости), с минимум доработок.
2) Решение должно быть максимально изолировано от остального кода. Вход, выход. Все.
3) Система должна быть полностью асинхронной с минимальными задержками.

Интерфейс:
Что в итоге получилось на входе (ads1118.h):
#ifndef _ADS1118_
#define _ADS1118_

#include "commontypes.h"

#define ADS_PORT_CS   GPIOC
#define ADS_PORT_CLK  GPIOD
#define ADS_PORT_DIN  GPIOA
#define ADS_PORT_DOUT GPIOA

#define ADS_PIN_CS   14
#define ADS_PIN_CLK  1
#define ADS_PIN_DIN  0
#define ADS_PIN_DOUT 1

namespace ads1118
{
	void init(uint channel = 0);
	
	void changeChannelTo(uint channel);

	bool isDataReady(uint channel = 0);
	int getTemp(uint channel = 0);

        void onTick();
};

#endif //#ifndef _ADS1118_


Модуль, обернут в namespace, чтобы четко отделить работу с устройством в коде. Причем, я сознательно нигде не пишу using, т.к. запись ads1118::XXX четко документирует что это работа с конкретным устройством и в купе с говорящими названиями методов в дополнительном документировании не нуждается.

init — кроме инициализации, задает первоначальный канал для работы.
changeChannelTo — меняет канал работы для СЛЕДУЮЩЕГО запроса.
isDataReady — возвращает true, если появились новые данные на данном канале.
getTemp — возвращает данные с канала (уже преобразованные) в 1/10 градуса Цельсия (т.е. 20 == 2 градуса).
Если данные не готовы, то вернет предыдущее значение.
onTick — основной метод. Здесь происходит переключение статусов и прием-отправка данных. Все сделано асинхронным bit-bang-ом. Этот метод необходимо вызывать регулярно, не реже 40 раз в секунду.

По реализации.
Система работает на конечном автомате.
Работа идет циклами. Каждый цикл начинается с отправки запроса на измерение внутренней температуры, потом внешней на нужном канале. Данные первого (внутреннего) и второго (внешнего) измерения сохраняются как единый результат конкретного канала. Начинается новый цикл. Как я уже говорил, в момент чтения измерения, мы передаем запрос на следующее измерение. Получается чехарда:
1) передаем запрос на внутреннее измерение — читаем внешнее.
2) ждем готовности результата.
3) передаем запрос на внешнее измерение (если необходимо, меняем канал) — читаем внутреннее.
4) ждем готовности результата.
5) GO TO 1

Код прилагаю. Если возникнут вопросы/поджелания, не стесняемся. Отвечу по мере поступления.

UPDATE: Код перезалит, по итогам изменений алгоритма компенсации «холодного спая».
Файлы в топике: ads1118.zip

Комментарии (23)

RSS свернуть / развернуть
THERMOCOUPLE MEASUREMENT WITH COLD JUNCTION COMPENSATION — измерения термопарой с компенсацией холодного спая.
Чуть подробнее: напряжение на термопаре пропорционатьно разности температур между её спаем и её разъёмом. Разъём называют «холодный спай» — cold junction — который кстати вовсе не обязательно холодный, просто так называется. Т.е. термопара измеряет не температуру, а разность температур. Чтобы получить полную температуру, нужно измеренную разность температур приплюсовать к температуре холодного спая. Микросхема считает, что температура холодного спая равна температуре её самой. Свою температуру она измеряет и приплюсовывает куда надо. Воть =)
+1
Спасибо. Это я все прочитал (ну и теорию про термопару я знал). Только я не понял, как перевести попугаев, которые выдает микросхема, в температуру. (
0
Перечитал еще раз. Понял где протупил. ) Спасибо огромное.
0
Зависимость термо ЭДС от температуры у термопар нелинейна. По хорошему надо линеаризовать сигнал табличным методом или полиномом. В качестве термометра холодного спая я бы не стал использовать внутренний датчик микросхемы. Лучше что то внешнее, да хоть тот же Ds18b20. Конечно все это надо чтобы получить хоть какую то точность.
-1
А в чем смысл внешнего сенсора? Набортный датчик температуры у ADS1118 не уступает DS18B20, а пользоваться им однозначно удобнее.
0
Смысл в том, что не всегда изотермическая колодка для подключения термопары находится рядом с корпусом микросхемы, между ними вполне может быть градиент температур. Саморазогрев кристалла микросхемы тоже может внести свой вклад. Конечно в варианте автора на все это можно забить.
-1
Я специально разместил микросхему как можно ближе к контактам. + снизу сплошная медь. Т.е. градиент температур конечно есть, но им можно пренебречь. Есть ads1118 в еще меньшем корпусе, специально, чтобы между контактами ТП поместить. В тестовой плате от TI именно так и сделано (только стоит она $50).
0
Да нормально у вас сделано. Требования к точности все равно не слишком высокие как я понял. Единственное, все же попробуйте снять характеристику термопары во всем диапазоне вашей печки.
0
Да. Я сниму обязательно. Спасибо.
0
Да, еще момент, снимать характеристику лучше при остывании печки. Греете до максимума, выключаете печь и снимаете.
-1
Почему?
0
Температурные градиенты в печи ниже.
-1
Хотя это не очень принципиально.
-1
Саморазогрев есть и у DS-ки. Что же до расстояния до колодки, то это сугубо вопрос конструктива. К тому же для того, что бы размещать микросхему поближе к разъему есть и другие причины, например минимизация наводок на входные цепи.
+1
Ну если ds не насиловать опросами чаще чем время преобразования, то сильно не разогреется.
-1
Мне кажется или там много соплей при пайке?
0
Чуть-чуть есть. ) Просто фотография крупная. Если учесть, что вся плата чуть больше спичечного коробка (обе микросхемы имеют ноги 0.3 через 0.2), то думаю размер соплей не критичный…
0
Если Вы про спаи ножек контроллера. То это не сопли. Просто фотик так сфоткал. Там часть ножек сдвоено. Чтобы не париться с тонкими дорожками…
0
ВНИМАНИЕ!!! Благодаря пояснению товарища DeepSOIC, у меня прояснилось в мозгу, и я переделал систему конвертации данных в температуру. Код скоро обновлю. Кроме того, хотел спросить: нужно ли подробно описать, что и как я сделал? З.Ы. Там много математики и совсем нет картинок )
0
Пишите, интересно.
0
Обязательно нужно подробно. Кому не интересно — поскипают.
0
Написал отдельную статью. ) А то, больно много получилось. Да и там результаты вообще про термопару? а не про ads1118.
0
Добавил математические пояснение по переводу данных в температуру.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.