Собственно мне все это нужно для решения второстепенной утилитарной задачи: установки часового пояса на часах. Предыдущая итерация этих часов была реализована на Arduino Nano, где я реализовал выбор пояса программной кнопкой с последующей записью в EEPROM. А сейчас просто из общего интереса решил выдернуть Nano и воткнуть взамен Seeeduino XIAO, перейдя при этом на 3.3В питание. Там нет EEPROM, хотя в качестве заменителя можно использовать FLASH.
Однако мне хотелось реализовать выбор часового пояса именно "низкотехнологическим" методом (если это можно так назвать, учитывая что читать аналоговый вход будет МК), заняв при этом только один вход МК. То есть желательно пассивная и малопотребляющая реализация собственно селектора. Понятно, что для часового пояса достаточно 5-ти битов информации, а про 8 я уж загнул для ровного счета.
Отсюда и идея с микриками и резисторами.
Решил навскидку попробовать вот просто так
Моделирование "на коленке" в Экселе показывает более-менее приемлемую кривую на выходе с верхней точкой на 2.19В (при 3.3В питания)
хотя к правой части разница в уровне сигнала между соседними точками становятся маловата. Но XIAO обещает 12-битный ADC, так что должно вытягивать, если с шумом проблем не будет.
Попробовал.
В принимающем коде пытаться выстроить прямую формулу для дискретизации такого "выпуклого" аналогового значения я не стал, а вместо этого просто моделирую ситуацию "в лоб": поместил в массив значения сопротивлений и тупо итерирую по всем возможным 32 комбинациям, вычисляю финальное сопротивление и ожидаемое значение на аналоговом входе. Выбираю ту комбинацию, которая дает ближайшее смоделированное значение к фактически прочитанному с ADC.
Погонял туда-сюда, подгоняя прошитые в коде фактические значения сопротивлений и получил работоспособный вариант.
Спойлер
Код: Выделить всё
static int8_t read_time_zone_delta(void)
{
const uint16_t v_range = 1u << ADC_BITS;
static const uint16_t r_sw_10[] PROGMEM = { 13710, 6510, 3200, 1480, 815 };
const uint16_t r_bot_10 = 810;
uint16_t v = analogRead(TIME_ZONE_PIN);
uint8_t best_combination = 0;
uint16_t min_delta = UINT16_MAX;
for (uint8_t combination = 0; combination < (1u << ARRAY_N(r_sw_10)); ++combination)
{
uint16_t v_expected = 0;
if (combination > 0)
{
uint32_t conductivity_total_sw = 0;
for (unsigned sw = 0; sw < ARRAY_N(r_sw_10); ++sw)
if ((combination & (1u << sw)) != 0)
conductivity_total_sw += UINT32_MAX / pgm_read_word(&r_sw_10[sw]);
uint16_t r_total_sw_10 = (uint16_t) (UINT32_MAX / conductivity_total_sw);
v_expected = (uint32_t) v_range * r_bot_10 / (r_total_sw_10 + r_bot_10);
}
unsigned delta = v_expected > v ? v_expected - v : v - v_expected;
if (delta < min_delta)
{
best_combination = combination;
min_delta = delta;
}
}
int8_t abs_delta = best_combination & 0x0F;
if ((best_combination & 0x10) == 0)
abs_delta = -abs_delta;
abs_delta = MAX(abs_delta, TIME_ZONE_DELTA_MIN);
abs_delta = MIN(abs_delta, TIME_ZONE_DELTA_MAX);
return abs_delta;
}
Полученное 5-битное значение затем интерпретируется как sign-and-magnitude значение (с той тонкостью, что у меня 0 в знаковом бите обозначает отрицательные значения)
Хотя, надо заметить, в правой части графика дребезг уже на грани фола, с немалой вероятностью ошибки на +-1. Однако после подгонки значений сопротивлений в коде все стало работать весьма надежно. Для пущей надежности смену часового пояса делаю тогда, когда 3 раза прочитано одно и то же значение.
Но если понадобилось бы добавить шестой бит, то точности такой реализации уже бы не хватало.