Измеритель УФ-индекса и температуры

Rate this item
(4 votes)

Содержание[Показать]

Прежде чем начать

Прежде чем читать эту статью, рекомендуем вам ознакомится со следующей теорией:

Индекс ультрафиолетового излучения или как правильно загорать

Вы собрались в жаркие страны, отдохнуть на море. Задумывались ли вы о том, насколько вредно наше солнце? Солнечное излучение - это весь свет и энергия, которые исходят от солнца, и есть много различных его форм. В электромагнитном спектре выделяют различные типы световых волн, излучаемых солнцем. Они похожи на волны, которые вы видите в океане: они перемещаются вверх и вниз и из одного места в другое. Спектр солнечного изучения может иметь разную интенсивность. Различают ультрафиолетовое, видимое и инфракрасное излучение. От самого опасного вид излучения - гамма-лучей, нас защищает озоновый слой. Инфракрасное излучение даёт тепло, видимое — свет, а вот ультрафиолетовое излучение в больших дозах может быть опасно для здоровья человека. Оно способно проникать глубоко под кожу и повреждать клетки, вносить изменения в ДНК, приводить к такому заболеванию как РАК. На следующей картинке можно видеть все виды солнечного излучения и те виды, которые достигают земной поверхности.




В небольших дозах УФ излучение полезно — под его воздействием в организме вырабатывается витамин Д. Однако в больших дозах оно приводит к ожогам кожи, эритеме(подробнее про вред и пользу УФ излучения можно прочитать в этой статье).

Уровень УФ излучения на поверхности нашей планеты принято измерять УФ индексом (UV Index). Этот показатель отражает степень ультрафиолетового излучения, он варьируется от 0 до 11. Чем выше его величина, тем больше потенциальная угроза для кожных покровов и глаз человека, и, соответственно, тем меньше времени можно находиться на солнце. Чтобы рассчитать данный индекс необходимы знания об интенсивности УФ излучения. Причём для корректного подсчёта нужно учитывать весь спектр УФ излучения - от 100 до 400 нм.


Мощность излучения в каждой длине волны необходимо интегрировать и умножить на определённый коэффициент. Дополнительно необходимо учитывать угол падения лучей на поверхность кожи. Полученная доза излучения подчиняется закону ламберта — пропорциональна косинусу угла падения лучей.

Учёными была установлена максимальная суточная доза УФ излучения (1 МЭД — минимальная эритемная доза), которая не приводит к отрицательным последствиям. Она зависит от типа кожи. Данные приведены в следующей таблице.

Тип кожи

Загар

Солнечный ожог

Цвет волос

Цвет глаз

1 МЭД, Дж/м2

I

никогда

всегда

рыжий / соломенный

голубой

200

II

иногда

иногда

светло русый

голубой / зелёный

250

III

всегда

редко

тёмно русый / каштановый

серый / карий

300

IV

всегда

никогда

чёрный

карий / чёрный

450



Индекс УФ напрямую влияет на время проведения на солнце. На следующем графике видна зависимость времени пребывания на солнце до покраснения кожи от индекса УФ и типа кожи.


Это время нахождения под прямыми солнечными лучами без средств защиты — то есть загораем в полдень. Если же вы занимаетесь каким-то видом спорта на солнце, то время необходимо умножить на следующий коэффициент.



Вид деятельности

Коэффициент C

Плавание:

море

2

пресноводные водоёмы

4

Спортивные игры

4

Пешеходные прогулки

3

Гребля, прогулки на лодке

3

Работа на приусадебном участке

6



Итак, теперь мы владеем всей информацией, чтобы правильно проводить время на солнце в отпуске. Нам нужен датчик, который позволит рассчитать индекс УФ излучения. А дальше мы сможем получить безопасное время нахождения на солнце.

Измеряем температуру воды и воздуха

Как правило, все едут в отпуск на море — понежиться на солнце и поплавать в ласковом теплом море. Чтобы рассчитать сколько времени можно находится в воде нужно знать её температуру. Наиболее комфортной считается температура 24-28 градусов. Вторая полезная функция прибора для отдыха — точное и быстрое измерение температуры воды.

В качестве датчика температуры воды идеально подходит термопара, например К типа (она самая распространённая). Во первых, такой датчик является достаточно точным, а во вторых он не боится влаги. Ну и самое главное — термопара имеет очень маленькую теплоёмкость, а это значит, что значения температуры воды вы получите практически мгновенно — 1-2 секунды достаточно для замера. Длина датчика может быть больше 1 метра, что позволит измерить температуру воды не только на поверхности, но и на глубине.

Для точного измерения температуры с помощью термопары необходим высокоточный вольтметр, который может измерять очень малые значения напряжения, а также датчик температуры платы, для измерения температуры холодного спая (разъем подключения термопары). Этот же датчик позволит измерять и температуру воздуха.

Вот теперь можно смело переходить к требованиям к прибору «отпускника».

Прочие полезные функции

Для измерения термопары нужен будет высокоточный ADC. Его можно использовать и для других полезных функций. Можно сделать вольтметр, амперметр, миллиомметр, можно измерять внутреннее сопротивление аккумуляторов. Для реализации этих функций потребуется сделать мини плату. Но зато прибор можно будет использовать не только летом, но и зимой!

Для работы с датчиками используется протокол I2C, а это значит, что если вывести выводы SDA SCL на внешний разъем, то можно будет подключать дополнительные датчики по этой шине, а также может сделать I2C сканер.

{product id=35}

Постановка задачи, требования к прибору

Основные требования к прибору.

  • Компактный размер

  • Питание от аккумулятора, длительная работа от одного заряда

  • Измерения индекса УФ излучения в двух диапазонах UVA и UVB

  • Измерение температуры с помощью термопары К типа, подключение ее через разъем

  • Измерение температуры воздуха

  • Индикация измеренных данных — миниатюрный ЖК (на солнце он читается лучше других индикаторов) дисплей

  • Возможность индикации звуком о различных событиях

  • Широкий набор функций и возможность доработки.

Подбор компонентов

Выбираем датчик УФ излучения

В качестве датчика УФ излучения будем использовать один из самых современных и миниатюрных датчиков уф излучения — VEML6075 от компании Vishay.




Данный датчик имеет очень маленький размер 2х2 мм, низкое энергопотребление 500 мкА в режиме работы и 800 нА в режиме сна. И самое главное - он умеет измерять UVA и UVB излучение одновременно. К датчику идёт подробный datasheet, а также дополнительная документация, в которой подробно рассмотрено как его калибровать и использовать в проектах. Датчик подключается по шине I2C, поэтому микроконтроллер должен уметь работать с этой шиной.

Датчик измерения термопары

Термопара — датчик температуры, принцип работы которого основывается на термоэлектрическом эффекте. Точность такого датчика очень высока — до 0.01 градуса, он имеет очень большой диапазон измерения — от -250 до 2000 градусов. Однако получить данные с него не так-то просто. Основным показателем температуры является напряжение на концах термопары (ТЭДС), по специальным формулам по нему можно определить температуру спая.

На показания датчика оказывают влияние:

  • Температура свободных концов (которые подключаются к разъему). Ее необходимо добавить к вычисленной температуре спая.

  • В момент снятия показаний необходимо исключить протекание тока через термопару, так как ток, протекающий через неё, охлаждает горячий спай и разогревает холодный

  • Зависимость ТЭДС от температуры существенно не линейна. Это требует сложных вычислений значения температуры по ТЭДС.

  • На большой длине термопарных и удлинительных проводов может возникать эффект «антенны» для существующих электромагнитных полей.

  • На один градус температуры для термопары типа К — ТЭДС составляет всего лишь 40 мкВ. Нужен очень точный ADC, чтобы измерить такое напряжение, или малошумящий операционный усилитель для усиления данного сигнала.

Для измерения температуры существуют специальные готовые микросхемы, которые выдают линейный сигнал на выходе. Однако, стоят они не недешево. Традиционный подход — использование операционного усилителя — требует начальной настройки, как минимум по двум точкам.

Самый современный путь для измерения температуры термопары - использовать внешний очень точный сигма дельта ADC — mcp3421. Данный ADC умеет измерять напряжение с разрешением до 2 мкВ (что дает разрешение в 0.05 градусов), при этом для его работы практически не нужны внешние компоненты. И что более важно, он не требует никакой калибровки, настройки и т. д. Таким образом, мы сразу получим очень точные данные с термопары. Такой подход сильно упрощает схему. Все вычисления произведёт микроконтроллер с помощью таблицы коэффициентов для многочленов. Скорость вычислений тут не нужна, поэтому даже слабый микроконтроллер справится с данной задачей. Датчик подключается также по шине I2C.

Наличие в данном приборе такого точного вольтметра позволяет использовать его (с помощью небольшой внешней платы) как прибор для следующих измерений:

  • напряжение

  • ток

  • сопротивление в диапазоне от 1 мОм и выше

  • ESR аккумуляторов

  • емкости конденсаторов

Датчик измерения температуры воздуха

К сожалению, ТЭДС отражает разницу температуры между точкой подключения датчика и спаем. Поэтому для получения реальной температуры в месте спая необходимо знать температуру разъёма, куда подключена термопара. Будем использовать цифровой интегральный датчик температуры NCT75. Он имеет разрешение до 0.06 градусов и погрешность в один градус. Так что возможная погрешность всего измерения составит 1 градус. В настоящее время есть интегральные датчики с погрешностью до 0.1 градуса, но применение такого датчика здесь не оправдано.

Этот датчик опять же работает по шине I2C, что очень удобно.

Индикатор

Индекс УФ излучения имеет смысл измерять в погожий солнечный день. А это означает, что индикатор должно быть хорошо видно на ярком солнце. Лучше всего для этого подойдёт LCD индикатор, например Gdc0209 — 4 цифры.


Он имеет всего 13 выводов на все сегменты. Используется динамическая индикация. Управляющее напряжение 2.5В. Сегменты подключены следующим образом.




Управление таким индикатором без специализированной периферии довольно сложное. Но в этом приборе микроконтроллеру будет скучно — пусть им и управляет.

Пищалка

Чтобы иметь возможность предупредить о высоком уровне излучения или низкой температуре нужна пищалка. Используем традиционно — HC0903A.

Микроконтроллер

Нам обязательно нужна периферия I2C, но она есть практически везде. Так что подойдёт относительно любой микроконтроллер. Возьмём уже знакомый по другим приборам — STM8S003F3.

Аккумулятор

Питаться прибор будет от аккумулятора. Потребление за счёт экономичного индикатора в рабочем режиме составляет около 1-2 мА. Достаточно будет самого маленького аккумулятора, например LIR2032. Плату будем делать как можно меньше, поэтому заряжать будем через внешний разъем с помощью небольшой отдельной платы зарядки. По всем расчётам это понадобиться делать раз в месяц при активном использовании. Для защиты аккумулятора от разряда можно использовать внешнюю мини платку. Вполне можно обойтись и без неё, достаточно только внимательно следить за прибором.

Регулятор питания

Так как на аккумуляторе максимально может быть 4.2В, а для датчиков максимальное напряжение составляет 3.6В, то необходим регулятор питания. Козьмем серию NCP551. Эти недорогие регуляторы имеют очень низкий ток собственного потребления — всего 4мкА, что позволяет питать устройства, которые будут большую часть времени спать.

Подбираем корпус и разъёмы

Корпус возьмём аналогично кухонному таймеру «Sanhe 20-31». В качестве разъёма используем самые дешёвые угловые PIN, сделаем так, чтобы они были утоплены в корпус и не торчали наружу. На разъем выведем:

  • 2 вывода для подключения термопары

  • 2 вывода — контакты батарейки для подключения внешней зарядки

  • I2C и 3.3в - для подключения внешних датчиков (в случае расширения функционала)

  • SWIM — для программирования

Наличие разъёма позволит подключать к прибору внешние датчики, что позволит расширить его функционал.

Составляем схему

Общая часть - питание, МК, пищалка, разъемы.




И датчики.


Все подключения согласно datasheet, ничего особенного нет. Печатную платы вы сможете найти в проекте на github.

Программа

Программа для STM8 написана в среде ST Visual develop IDE. Полный проект вы можете скачать с github данного прибора, папка uv. В статье мы разберём ключевые моменты работы программы.

Общая логика работы программы

  1. При включении производится настройка всей периферии, тактирования процессора, прерываний, выводов и система переходит в спящий режим

  2. При однократном нажатии на кнопку система выходит из спящего режима, а при дальнейших нажатиях циклически меняет режим работы.

  3. После смены режима работы в течение 2с отображается текущий режим в виде символов режима

  4. Предусмотрены следующие режимы работы:

    1. Измерение температуры (символ 1хх)

      1. воздуха, с помощью цифрового датчика (символ режима 101)

      2. с датчика термопары (символ режима 102)

    2. измерение UV индекса (символ режима 201)

  5. На экране отображаются измеряемые значения

  6. Через 30с неактивности прибор переходит в спящий режим

Инициализация

Установим частоту работы микроконтроллера в 2 МГц и отключим всю периферию, кроме I2C и TIM2 и TIM4.

  1. CLK->PCKENR1 = CLK_PCKENR1_TIM2+CLK_PCKENR1_I2C; CLK->PCKENR2= 0b11110011;
  2. CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);//2mgh
  3. //internal clock
  4. GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_OUT_PP_LOW_SLOW);
  5. GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_OUT_PP_LOW_SLOW);
  6. GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_OUT_PP_LOW_SLOW);
  7.  

Настроим внешние прерывания для возможности выхода из сна по кнопке.

  1. EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOA,EXTI_SENSITIVITY_FALL_ONLY);
  2. GPIO_Init(GPIOA,GPIO_PIN_2,GPIO_MODE_IN_PU_IT);
  3.  

Инициализируем I2C.

  1. I2C_Init(I2C_MAX_STANDARD_FREQ, (uint8_t)0xA0, I2C_DUTYCYCLE_2,I2C_ACK_CURR, I2C_ADDMODE_7BIT, 7);
  2.  

Настроим таймеры на 1000 Гц (основной миллисекундный таймер) и 2000 Гц (для индикатора).

  1. TIM2_TimeBaseInit(TIM2_PRESCALER_8, 124);//2000Hz
  2. TIM2_ClearFlag(TIM2_FLAG_UPDATE);
  3. TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);
  4. TIM2->IER |= (uint8_t)TIM2_IT_UPDATE;
  5. TIM2_Cmd(ENABLE);
  6.  
  7. TIM4_TimeBaseInit(TIM4_PRESCALER_8, 249);//1000Hz
  8. TIM4_ClearFlag(TIM4_FLAG_UPDATE);
  9. TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
  10. TIM4->IER |= (uint8_t)TIM4_IT_UPDATE;
  11. TIM4_Cmd(ENABLE);
  12.  

Дальше переходим к запуску периферии и инициализации датчиков.

  1. enableInterrupts();
  2. Delay(100);
  3. bmeT=0;
  4. I2C_Cmd( ENABLE);
  5. mcpinit();
  6. uvinit();
  7. nctinit();
  8.  

На этом все. Процедуры инициализации датчиков рассмотрим ниже.

I2C драйвер

Компания ST не предоставляет готовой библиотеки для работы с I2C, только низкоуровневые функции для настройки периферии и доступа к регистрам. В их примерах есть куски кода обмена данными по шине I2C. На их основе напишем необходимые функции передачи и получения данных по шине I2C. Обмен будет работать без использования прерываний. Для записи данных используется функция I2C_writenbyte, а для чтения I2C_readnbyte. Начнём с первой, она проще.

  1. int I2C_writenbyte(uint8_t addr, uint8_t* buff, int nbyte, intnostop)
  2. {
  3. uint32_t timeout;
  4. timeout = current_millis + 1000;
  5.  
  6. while (I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))
  7. {
  8. if (current_millis>timeout) return 0;
  9. }
  10.  
  11. I2C->CR2 |= I2C_CR2_START;//I2C_GenerateSTART(ENABLE);
  12. while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
  13. {
  14. if (current_millis>timeout) return 0;
  15. }
  16.  

Перед записью необходимо убедиться, что шина I2C не занята, за это отвечает флаг I2C_FLAG_BUSBUSY в регистре I2C. Подождём пока шина не освободиться. Если этого не произойдёт за разумное время, то вернём состояние ошибки. Далее сгенерируем событие START, запишем соответствующий бит в регистр CR2. Проверим, что шина перешла в режим мастер. Можно передавать данные. В функцию передаётся адрес массива с байтами buff и количество байт для передачи, а также флаг — nostop, который отвечает за передачу в конце сообщения сигнала STOP. Некоторые датчики требуют, чтобы за командой записи следовала сразу команда чтения, без передачи сигнала STOP.

  1. I2C_Send7bitAddress((uint8_t)addr << 1, I2C_DIRECTION_TX);
  2. while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  3. {
  4. if (current_millis>timeout) return 0;
  5. }
  6.  
  7. while (nbyte>0)
  8. {
  9. I2C->DR =(uint8_t)*buff;//I2C_SendData((uint8_t)*buff);//ctrl meas
  10. while(!I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED))
  11. {
  12. if (current_millis>timeout) return 0;
  13. }
  14. *buff++;
  15. nbyte--;
  16. }
  17.  
  18. if(nostop==0)
  19. {
  20. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  21. }
  22.  
  23. return 1;
  24.  

Передаём адрес датчика, с которым будет производиться общение, и признак записи — первый бит. Ожидаем подтверждения, что режим мастера никто не отобрал. Далее, пока не закончатся все байты, записываем их в регистр DR периферии I2C и ждём пока очередной байт будет передан (I2C_FLAG_TRANSFERFINISHED). Остаётся только передать сигнал STOP, если он необходим. В случае возникновения ошибки, байт не будет считаться переданным, и через таймаут будет возвращён статус ошибки. Как видно, процедура совсем несложная.

Разберём теперь процедуру чтения данных.

  1. int I2C_readnbyte(uint8_t addr, uint8_t * buff, int nbyte,intnocheckbusy)
  2. {
  3. uint32_t timeout;
  4. timeout = current_millis + 1000;
  5.  
  6. if (nocheckbusy==0)
  7. {
  8. while (I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))
  9. {
  10. if (current_millis>timeout) return 0;
  11. }
  12. }
  13.  
  14. I2C->CR2 |= I2C_CR2_START;//I2C_GenerateSTART(ENABLE);
  15. while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
  16. {
  17. if (current_millis>timeout) return 0;
  18. }
  19.  
  20. I2C_Send7bitAddress((uint8_t)addr << 1, I2C_DIRECTION_RX);
  21.  

Начало примерно такое же. Ожидаем освобождения линии (если это необходимо). Далее передаём команду START, и ждём подтверждения перехода в мастер режим. Передаём адрес с битом чтения и начинаем передавать данные. И вот тут необходимо разобрать три варианта чтения данных. Первый - когда байт для чтения больше трёх. Второй - когда всего два байта и третий когда один. В каждом случае необходимо по-разному отслеживать события чтения, потому, что шина I2C имеет теневой регистр, в котором могут уже находиться данные, когда заполнен основной регистра данных. То есть, когда мы разбираемся с полученным байтом, периферия может получить уже второй байт. Случай больше трёх байт.

  1. if (nbyte >= 3)
  2. {
  3. while (I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
  4. {
  5. if (current_millis>timeout) return 0;
  6. }
  7. disableInterrupts();
  8. (void)I2C->SR3;
  9. enableInterrupts();
  10.  
  11. while (nbyte > 3) {
  12.  
  13. while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
  14. {
  15. if (current_millis>timeout) return 0;
  16. }
  17.  
  18. *buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
  19. *buff++;
  20. nbyte--;
  21. }
  22. while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
  23. {
  24. if (current_millis>timeout) return 0;
  25. }
  26. I2C->CR2 &=(uint8_t)(~I2C_CR2_ACK);//I2C_AcknowledgeConfig(I2C_ACK_NONE);
  27. disableInterrupts();
  28. *buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
  29. *buff++;
  30. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  31. *buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
  32. enableInterrupts();
  33. *buff++;
  34. while (I2C_GetFlagStatus( I2C_FLAG_RXNOTEMPTY) == RESET)
  35. {
  36. if (current_millis>timeout) return 0;
  37. }
  38.  
  39. *buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
  40. nbyte=0;
  41.  
  42. while(I2C->CR2 & I2C_CR2_STOP)
  43. {
  44. if (current_millis>timeout) return 0;
  45. }
  46.  
  47. /* Re-Enable Acknowledgement to be ready for another reception*/
  48. I2C->CR2 |= I2C_CR2_ACK;
  49. I2C->CR2 &=(uint8_t)(~I2C_CR2_POS);//I2C_AcknowledgeConfig( I2C_ACK_CURR);
  50. }
  51.  

Проверяем, что датчик откликнулся на свой адрес. Теперь необходимо прочитать регистр SR3, и сделать это нужно так, чтобы нас никто не прервал. Запрещаем прерывания и читаем регистр. В этот момент периферия получает байт данных. Ждем окончания получения байта и записываем его в буфер. Так делаем, пока количество оставшихся байт не станет равно 3. Отключаем автоматическое подтверждение ACK при получении байте, чтобы обработать байт. Запрещаем прерывания и получаем байт. Далее сразу генерируем STOP сигнал. Ждем пока придет еще один байт и записываем его в буфер. Ждём пока будет передан стоп сигнал и генерируем ACK сигнал. Таким хитрым образом получаем все оставшиеся три байта.

Когда надо получить два байта.

  1. if (nbyte == 2)
  2. {
  3. I2C_AcknowledgeConfig(I2C_ACK_NEXT);
  4. while (I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
  5. {
  6. if (current_millis>timeout) return 0;
  7. }
  8. (void)I2C->SR3;
  9. I2C_AcknowledgeConfig(I2C_ACK_NONE);
  10.  
  11. while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
  12. {
  13. if (current_millis>timeout) return 0;
  14. }
  15.  
  16. disableInterrupts();
  17. I2C_GenerateSTOP(ENABLE);
  18. *buff= I2C_ReceiveData();
  19. enableInterrupts();
  20. // Point to the next location where the byte read will be saved
  21. *buff++;
  22. *buff= I2C_ReceiveData();
  23. nbyte=0;
  24.  
  25. while(I2C->CR2 & I2C_CR2_STOP)
  26. {
  27. if (current_millis>timeout) return 0;
  28. }
  29.  
  30. // Re-Enable Acknowledgement to be ready for another reception
  31. I2C_AcknowledgeConfig( I2C_ACK_CURR);
  32. }
  33.  

Включаем автогенерацию ACK бита, проверяем что адрес опознан. Читаем регистр SR3. Отключаем генерацию ACK. Ждем флага байт получен. Запрещаем прерывания. И уже генерируем STOP сигнал. Байт уже получен. Быстро его читаем. И периферия получает в теневой регистр еще один байт. Записываем его в буфер и ждём пока выставится STOP на линии. Возвращаем назад автогенерацию ACK сигнала. Остался случай одного байта.

  1. if (nbyte == 1)
  2. {
  3. I2C->CR2 &=(uint8_t)(~I2C_CR2_ACK);//I2C_AcknowledgeConfig(I2C_ACK_NONE);
  4. while(I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
  5. {
  6. if (current_millis>timeout) return 0;
  7. }
  8. disableInterrupts();
  9. (void)I2C->SR3;
  10. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP( ENABLE);
  11. enableInterrupts();
  12. while(I2C_GetFlagStatus( I2C_FLAG_RXNOTEMPTY) == RESET)
  13. {
  14. if (current_millis>timeout) return 0;
  15. }
  16. *buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
  17. nbyte=0;
  18. while(I2C->CR2 & I2C_CR2_STOP)
  19. {
  20. if (current_millis>timeout) return 0;
  21. }
  22. I2C->CR2 |= I2C_CR2_ACK;
  23. I2C->CR2 &=(uint8_t)(~I2C_CR2_POS);//I2C_AcknowledgeConfig( I2C_ACK_CURR);
  24. }
  25.  

Сразу отключаем генерацию ACK. Ждем подтверждения адреса. Запрещаем прерывания и сразу генерируем STOP сигнал. Ждем получения байта и передачи STOP сигнала. Включаем обратно автогенерацию ACK.

Вот так хитро работает получение данных по шине I2C.

Управление LCD индикатором с 4-мя общими выводами

Для управления индикатором будет использоваться метод цифровой корреляции. Чтобы понять как работает этот метод, рассмотрим как нужно управлять ЖК индикаторами.

Во первых, ЖК индикатором необходимо управлять строго переменным напряжением. Чтобы сегмент стал чёрным (то есть видимым) необходимо приложить переменное напряжение больше рабочего (у нас это 2.5В), в случае если переменное напряжение будет меньше заданного, то сегмент будет невидим (или слабо видим). Это свойство ЖК и используется для управления. В простых индикаторах каждый сегмент выведен на свои выводы. Управлять одним сегментом легко, но нужно очень много выводов у микроконтроллера. Для уменьшения количества выводов, так же как и в светодиодных индикаторах, в выбранном ЖК индикаторе используются общие выводы, которые подключены ко всем сегментам. Их в данной модели четыре. В таком режиме управлять уже сложнее. Нужен ступенчатый аналоговый сигнал. На момент показа одного сегмента необходимо на другие подать переменное напряжение меньше 2.5В. Вот диаграмма управления выбранным индикатором в аналоговом режиме.




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

Для полностью цифрового управления индикатором данные ступеньки необходимо эмулировать ШИМ сигналом. Такая методика гораздо проще для МК без специализированной периферии. Вот её график.


Для управления используется ШИМ сигнал с 50% наполнением. Активный общий вывод задаётся ШИМ сигналом с вдвое меньшей частой, а не активный с вдвое большей. Для управления сегментами используется ШИМ сигнал меньшей частоты, при этом полярность ШИМ сигнала меняется для сегментов которые видимы — сигнал начинается с 0 и заканчивается 1, а для невидимых наоборот. Данная схема должна работать на довольно высокой частоте и с самым высоким приоритетом, иначе могут быть отклонения от схемы и будут появляться участки времени с постоянным напряжением, что будет приводить к деградации индикатора.

К сожалению, традиционным путём, расчётом и вычислением состояния выводов в прерывании, пойти не получится. У микроконтроллера не хватает скорости. Тем более, желательно работать на самой низкой частоте 2МГц для снижения энергопотребления.

Для управления показываемой информацией служит массив ind[]. Там находятся данные, которые необходимо отобразить на месте первой, второй, третьей цифры. А массив tchk[] содержит отображаемые точки. После изменения данных в этих массивах необходимо вызвать функцию calсlcd, которая рассчитает необходимое состояние выводов согласно схеме. Рассмотрим эту функцию построчно.

  1. if (timeshowrezhim) {
  2. //0- temp 1-termopara 2 - uv
  3. if (rezhim==0) {
  4. ind[0]=1;
  5. ind[1]=0;
  6. ind[2]=1;
  7. }
  8.  

В начале процедуры переписываем массив, чтобы показать какой режим выбран. А далее переходим к формированию сигналов по состоянию массива.

Рассмотрим предварительно логику вывода информации на экран (ориентируемся на предыдущую картинку). Вся информация на экран выводится фреймами. Каждый фрейм состоит из сегментов активных в общем выводе com1, потом в выводе com2, com3 и com4, а потом несколько пустых состояний (для управления яркостью свечения сегментов). Выводятся фреймы в прерывании таймера по кругу. В каждый момент срабатывания таймера выводится одна часть фрейма, которая в свою очередь состоит из четырёх возможных комбинаций общих битов и битов сегментов. В итоге на один фрейм нужен массив из 4*4 возможных состояний общих выводов и выводов сегментов. Так как сегменты цифр разбросаны по общим выводам не очень удобно, то для начала заполним отдельный массив, отражающий какие биты сегментов должны быть активированы в каждом общем выводе.

  1. for (i=0;i<4;i++) com[i]=0;
  2. //7-8
  3. addnum(0, GPIO_PIN_1,GPIO_PIN_2);
  4. //9-10
  5. addnum(1, GPIO_PIN_3,GPIO_PIN_4);
  6. //11-12
  7. addnum(2, GPIO_PIN_5,GPIO_PIN_6);
  8.  

Массив com содержит 4 байта, каждый бит которого отвечает за светящийся сегмент в соответствующем общем выводе. Всего сегментов в одном общем выводе у нас не более 8, так как у нас светиться могут не все сегменты (мало выводов у выбранного микроконтроллера). Очистим массив с общим выводами, а потом добавим три цифры. Заметим, что на одну любую цифру приходится два каких то вывода и 4 общих вывода. Например на цифру 1 — это выводы 5 и 6, на цифру 2 — выводы 7,8 и так далее. Передаём в процедуру номера сегментов и включаем нужные биты в массиве com в зависимости от выводимого символа.


  1. void addnum(u8 index, u8 pin1,u8 pin2) {
  2. u8 num;
  3. num = ind[index];
  4.  
  5. if (tchk[index]) {
  6. com[3] |= pin2;
  7. }
  8.  
  9. switch (num) {
  10. case 0:
  11. //p1 - com0 com2 com3
  12. //p2 - com0 com2 com1
  13.  
  14. com[0] |= pin1|pin2;
  15. com[1] |= pin2;
  16. com[2] |= pin1|pin2;
  17. com[3] |= pin1;
  18. break;
  19. case 1:
  20. //p2 - com1 com2
  21. com[1] |= pin2;
  22. com[2] |= pin2;
  23. break;
  24.  

И так делаем для всех возможных символов. В итоге получаем в массиве com для каждого общего вывода какие сегменты должны светится. Осталось все это перевести в 0 и 1 на нужных выводах МК.

  1. for (i=0;i<16;i++) {
  2. lcdm[16+i].porta_odr = GPIOA->ODR & (~(ALL_PORTA));
  3. lcdm[16+i].portd_odr = GPIOD->ODR & (~(ALL_PORTD));
  4. lcdm[16+i].portc_odr = GPIOC->ODR & (~(ALL_PORTC));
  5. }
  6.  

Так как выводы разбросаны по трём портам A, D, C, храним состояние ODR регистра каждого порта. При этом, используем режим двойного буфера, так как эти вычисления занимают много процессорного времени. Все расчёты сначала ведём в последних 16 значениях массива, а потом быстро копируем их в первые 16 элементов массива. Готовим состояния портов — все биты выводов индикатора равны 0.

  1. for (i=0;i<4;i++) {
  2. lcdm[16+i*4].porta_odr |= lcdpins[i].porta;
  3. lcdm[16+i*4].portd_odr |= lcdpins[i].portd;
  4. lcdm[16+i*4].portc_odr |= lcdpins[i].portc;
  5.  
  6. lcdm[16+i*4+1].porta_odr |= lcdpins[i].porta;
  7. lcdm[16+i*4+1].portd_odr |= lcdpins[i].portd;
  8. lcdm[16+i*4+1].portc_odr |= lcdpins[i].portc;
  9.  
  10. for (j=0;j<4;j++) {
  11. if (i==j) continue;
  12.  
  13. lcdm[16+i*4].porta_odr |= lcdpins[j].porta;
  14. lcdm[16+i*4].portd_odr |= lcdpins[j].portd;
  15. lcdm[16+i*4].portc_odr |= lcdpins[j].portc;
  16.  
  17. lcdm[16+i*4+2].porta_odr |= lcdpins[j].porta;
  18. lcdm[16+i*4+2].portd_odr |= lcdpins[j].portd;
  19. lcdm[16+i*4+2].portc_odr |= lcdpins[j].portc;
  20. }
  21.  
  22. for (j=4;j<11;j++) {
  23. //1100 //all seg off
  24. lcdm[16+i*4].porta_odr |= lcdpins[j].porta;
  25. lcdm[16+i*4].portd_odr |= lcdpins[j].portd;
  26. lcdm[16+i*4].portc_odr |= lcdpins[j].portc;
  27.  
  28. lcdm[16+i*4+1].porta_odr |= lcdpins[j].porta;
  29. lcdm[16+i*4+1].portd_odr |= lcdpins[j].portd;
  30. lcdm[16+i*4+1].portc_odr |= lcdpins[j].portc;
  31. }
  32. }
  33.  

Предварительно установим все выводы по com сигналам. Например, для com0 будет так «1100 1010 1010 1010».


А потом установим выводы сегментов согласно массиву com[].

  1. for (i=0;i<4;i++) {
  2. if (com[i]) {
  3. u8 j,pin;
  4. pin = 1;
  5.  
  6. for (j=0;j<7;j++) {
  7. if (com[i] & pin) {
  8. lcdm[16+i*4+0].porta_odr &= ~lcdpins[j+4].porta;
  9. lcdm[16+i*4+1].porta_odr &= ~lcdpins[j+4].porta;
  10. lcdm[16+i*4+2].porta_odr |= lcdpins[j+4].porta;
  11. lcdm[16+i*4+3].porta_odr |= lcdpins[j+4].porta;
  12.  
  13. lcdm[16+i*4+0].portd_odr &= ~lcdpins[j+4].portd;
  14. lcdm[16+i*4+1].portd_odr &= ~lcdpins[j+4].portd;
  15. lcdm[16+i*4+2].portd_odr |= lcdpins[j+4].portd;
  16. lcdm[16+i*4+3].portd_odr |= lcdpins[j+4].portd;
  17.  
  18. lcdm[16+i*4+0].portc_odr &= ~lcdpins[j+4].portc;
  19. lcdm[16+i*4+1].portc_odr &= ~lcdpins[j+4].portc;
  20. lcdm[16+i*4+2].portc_odr |= lcdpins[j+4].portc;
  21. lcdm[16+i*4+3].portc_odr |= lcdpins[j+4].portc;
  22. }
  23. pin = pin << 1;
  24. }
  25. }
  26. }
  27.  

В переменной pin сдвигаем 1 бит влево, пока не пройдём по всем битам. В самом конце скопируем данные в первые ячейки массива.

  1. blockcopylcd=1;
  2. for (i=0;i<16;i++) {
  3. lcdm[i].porta_odr = lcdm[16+i].porta_odr;
  4. lcdm[i].portd_odr = lcdm[16+i].portd_odr;
  5. lcdm[i].portc_odr = lcdm[16+i].portc_odr;
  6. }
  7. blockcopylcd=0;
  8.  

При этом, в момент копирования не будем выводим ничего на индикатор. При установленной переменной blockcopylcd на все выводы индикатора выводится 0.

Все данные готовы для вывода, осталось в прерывании вывести всю эту последовательность.

  1. lcdframe++;
  2. if (lcdframe==75) lcdframe = 0;
  3. ind = lcdframe;
  4.  
  5. //blockcopylcd=1;
  6.  
  7. if (readytosleep==0) {
  8. if ((ind < 16) && (blockcopylcd==0)) {
  9.  
  10. GPIOA->ODR = lcdm[ind].porta_odr;
  11. GPIOD->ODR = lcdm[ind].portd_odr;
  12. GPIOC->ODR = lcdm[ind].portc_odr;
  13. }
  14. else {
  15. GPIOA->ODR &= ~(ALL_PORTA);
  16. GPIOD->ODR &= ~(ALL_PORTD);
  17. GPIOC->ODR &= ~(ALL_PORTC);
  18. }
  19. }
  20.  

Таймер срабатывает 2000 раз в секунду. 16 мс выводим все общие выводы, которые программировали в массиве, потом 59мс не выводим ничего. В итоге 75мс на один проход. То есть обновление индикатора происходит с частотой около 100 Гц. Это более чем достаточно.

Обработка кнопок

Обработку кнопок возьмём из проекта кухонный таймер.

Обработка датчиков

Ntc75 — датчик температуры

Для работы с датчиком используются две процедуры: nctinit() - используется один раз и настраивает режим работы датчика, nctdata() - вызывается регулярно, считывает температуру и записывает ее в глобальную переменную bmeT.

Для передачи настроек датчику необходимо в режиме записи данных передать два байта — номер регистра и требуемое состояние регистра.

  1. #define NCTaddr (0x48)
  2. int nctinit(void) {
  3. buff[0] = (uint8_t) 0b1;
  4. buff[1] = (uint8_t) 0b100000;
  5. if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
  6. {
  7. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  8. return 0;
  9. };
  10. }
  11.  

Адрес датчика задаётся с помощью команды #define — это общая практика использования констант в Си. Регистр настройки имеет адрес 0x01. Датчик может работать в непрерывном режиме или однокаратном. Для меньшего потребления тока и меньшего нагрева датчика лучше использовать однократный режим, это повысит точность измерений. За этот режим отвечает 7-ой бит. Первый бит отвечает за рабочий режим (когда передаём 0, то включаем датчик). Записываем в регистр настроек 0b100000. На этом инициализация датчика завершена.

Обработка показаний датчика выполняется также несложно.

  1. int nctdata(void)
  2. {
  3. long tmcpT;
  4.  
  5. buff[0] = (uint8_t) 0x4;
  6. buff[1] = (uint8_t) 0;
  7. if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
  8. {
  9. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  10. return 0;
  11. };
  12. Delay(90);
  13.  

Активируем разовое измерения. В регистр 0x04 записываем любые данные, например 0. Одно измерение занимает 60мс, на всякий случай подождём 90мс. После этого прочитаем данные.

  1. buff[0] = (uint8_t) 0x00;
  2. if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 1,0) )
  3. {
  4. I2C_GenerateSTOP(ENABLE);
  5. return 0;
  6. };
  7.  
  8. if (! I2C_readnbyte((uint8_t)NCTaddr, buff, 2,0) )
  9. {
  10. return 0;
  11. };
  12.  

Данные находятся по регистру 0x00. Для чтения используем две команды. Сначала в режиме записи передаём номер регистра, который будем читать. А потом в режиме чтения получаем нужное количество байт в буфер. Данные о температуре состоят из двух байт. Старший байт передаётся первым.

  1. tmcpT = (u32) (buff[0]<<8) | buff[1];
  2.  

Такой командой собираем данные. Здесь важно не забывать использовать команды приведения типов, иначе операция сдвига влево приведёт к обнулению данных. Далее согласно документации на датчик, чтобы получить температуру, необходимо провести следующие вычисления:

  1. bmeT = (tmcpT>>4) * 625 / 100;
  2.  

В переменной получаем температуру в формате целого числа градусов умноженного на 100. То есть температура 25.65 градусов представлена числом 2565. На данном микроконтроллере в вычислениях лучше использовать целочисленную математику для меньшего расхода памяти и более быстрого выполнения вычислений.

Veml6075 — датчик УФ излучения

Самый интересный датчик. Процедура инициализации аналогична датчику температуры.

  1. buff[0] = (uint8_t) 0b0;//config reg
  2. buff[1] = (uint8_t) 0b01000000;//low byte 400ms
  3. buff[2] = (uint8_t) 0b0;//high byte
  4. if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 3,0) )
  5. {
  6. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  7. return 0;
  8. };
  9.  

Передаем адрес регистра настройки — 0x0, и значение настроек в виде двух байт.


Включаем датчик (бит 0), используем нормальный режим работы, нормальные установки динамического режима, задаем режим интеграции 800ms — в этом режиме датчик менее подвержен боковой засветке, которую трудно убрать без тефлонового фильтра.

Для более удобного получения данных с датчика напишем функцию readUVword(u8 addr). Функция читает сразу по два байта по переданному адресу и собирает из них беззнаковое целое размером 16 бит.

  1. u16 readUVword(u8 addr) {
  2. buff[0] = addr;
  3. if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 1,1) )
  4. {
  5. I2C_GenerateSTOP(ENABLE);
  6. return 0;
  7. };
  8.  
  9. if (! I2C_readnbyte((uint8_t)UVaddr, buff, 2,1) )
  10. {
  11. return 0;
  12. };
  13. return (u16) ((buff[1]<<8) | buff[0]);
  14. }
  15.  

Отметим, что в данном датчике первым передаётся младший байт! Разберем процедуру uvdata(), в которой производятся все вычисления.

  1. int uvdata(void)
  2. {
  3. float uvia,uvib;
  4. int i;
  5. long tmcpT;
  6.  
  7. uva = readUVword(0x07);
  8. uvb = readUVword(0x09);
  9. uvcomp1 = readUVword(0x0A);
  10. uvcomp2 = readUVword(0x0B);
  11.  

Для работы с данным датчиком необходимо использовать вычисления с плавающей точкой. Точности float будет достаточно. Получаем с датчика основные данные — uva (интенсивность уф излучения А типа), uvb (интенсивность уф излучения B типа), uvcomp1 (коэффициент компенсации 1), uvcomp2 (коэффициент компенсации 2). Используя эти коэффициенты, а также шесть констант можно вычислить необходимый индекс УФ излучения. К датчику идёт документ «Designing the VEML6075 into an Application», в котором подробно рассмотрена методика рассчета констант и проведения вычислений.

  1. uvia = ((uva-UKa*uvcomp1-UKb*uvcomp2)*0.001461);//for 100ms
  2. uvib = ((uvb-Ukc*uvcomp1-Ukd*uvcomp2)*0.002591);
  3.  
  4. if (uvia<0 || uvib<0) uindex=0;
  5. else uindex = (uvia+uvib)*100/2/6;//800ms!!
  6.  

VEML6075 — как правильно вычислить индекс УФ излучения

Чтобы корректно рассчитать индекс УФ излучения по показаниям датчика необходимо использовать несколько констант. Как их вычислить не имея специального оборудования? Разберёмся в этом вопросе. Вот полная формула вычисления индекса:


Константы k1 и k2 без оборудования вычислить не получится. Но они необходимы для максимально точного расчёта, так что их можно принять за единицу. Константы чувствительности датчика можно взять из таблицы:


Нам нужны константы из первой строки, так как датчик у нас не закрыт тефлоном. UVAresp и UVBresp. А вот константы a, b, c, d необходимо вычислить. Для этого проще всего использовать отладчик. Необходимо получить данные uva, uvb, uvcomp1, uvcomp2, и в Excel рассчитать константы. Показывать их на дисплее неудобно — слишком мало разрядов. Можно использовать отладчик. Поставить точку останова после получения данных и посмотреть вычисленные значения. Также можно использовать возможность отключения ST-link от STM8 в процессе отладки и обратного подключения. При этом сеанс отладки не прерывается! Ещё один вариант — использовать STM8 STUDIO. Это специальная программа позволяет получить переменные в любой момент времени. Придётся немного модифицировать программу, например по кнопке записывать данные в отдельные переменные и в STUDIO или отладчике их уже прочитать.

Солнечный свет в своём спектре имеет УФ излучение, ИК излучение и видимое излучение. Чтобы рассчитать интенсивность УФ излучения, необходимо вычесть лишние данные про ИК излучение и видимое излучение. Для этого служат данные uvcomp1 (отвечает за видимое излучение) и uvcomp2 (отвечает за ИК излучение). Чтобы рассчитать константы в используемой формуле необходимы два искусственных источника света:

  • обычная лампа накаливания — она излучает в ИК спектре и видимом излучении;

  • светодиодная лампа — она излучает только в видимом излучении.

Начинаем со светодиодной лампы. Так как УФ излучение отсутствует, то соответствующий индекс будет равен нулю.


У светодиодной лампы нет ИК излучения, поэтому UVcomp2 будет равен 0. И в этом случае можно найти коэффициенты

Получив эти коэффициенты не составляет труда найти коэффициенты b и d, при использовании обычной лампы, когда UVcomp2 не равно нулю.

С помощью такого нехитрого метода можно получить необходимые константы. Такую калибровку необходимо проводить вечером, когда нет солнечного света и других источников света.

Правильно откалиброванный датчик очень легко проверить. В солнечный день, в полдень, когда на небе нет облаков необходимо измерить интенсивность, направив датчик точно на солнце. Показания должны совпадать с передаваемыми данными на сайтах погоды.

При загаре, для точного измерения излучения, датчик необходимо держать параллельно поверхности тела. Для получения максимального текущего индекса датчик нужно направлять на солнце.

Mcp3231 — измерение температуры термопары

Данный датчик имеет один регистр, который определяет поведение датчика. При инициализации используем следующий код:

  1. buff[0] = (uint8_t) 0b00011100;
  2. if( ! I2C_writenbyte((uint8_t)MCPaddr, buff, 2,0) )
  3. {
  4. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  5. return 0;
  6. };
  7.  
  8.  
  9. return 1;
  10.  

Передавать можно один байт или два. Учитывается только первый — байт конфигурации. Первые два бита задают использование режима усиления до 8 раз. Для измерения термопары нам такой точности не нужно, поэтому не будем использовать усиление. Следующие два бита задают разрядность, используем максимальную 18бит. И следующий бит включает непрерывный режим измерений. Скорость в таком режиме составляет 270 мс на одно измерение.

Данный ADC на своем борту имеет источник референсного напряжения и имеет очень высокую стабильность результата. Можно сказать он отдаёт результат до последнего бита. При использовании 18 битного разрешения можно измерить напряжение с точностью до 15мкВ. Этого более чем достаточно для термопары.

Чтобы прочитать данные с датчика необходимо просто получить от него 3 байта.

  1. long tmcpT;
  2.  
  3. tmcpT = ((long)buff[0]<<24) + ((long)buff[1]<<16) +((long)buff[2]<<8);
  4. tmcpT/=256;
  5.  
  6. e = (double) tmcpT/64;//512; //* 15.625/8000;
  7.  

18 бит данных содержит знак результата. Датчик дублирует этот бит до 24 бита, чтобы получить 24битное знаковое число. Однако в Си нет формата знакового числа 24 бита, а есть только 32 битное — long. Поэтому, все число необходимо сместить влево на 8бит, а потом поделить на 256.

Для вычисления температуры термопары необходимо провести вычисления с плавающей точкой. Поэтому переведём полученный результат в милливольты. Переменная «e» содержит нужное значение. В этой статье есть вся информация как посчитать температуру нужной термопары (у нас используется К типа) с помощью полиномов. ГОСТ Р. 8.585-2001 даёт нужные коэффициенты для разных диапазонов температур (стр. 71). В этом приборе будем использовать три диапазона температур: от -200 до 0 градусов, от 0 до 500 градусов и от 500 до 1372 градусов.

  1. const double cK[9]={
  2. 2.508355*10,
  3. 7.860106/100,
  4. -2.503131/10,
  5. 8.315270/100,
  6. -1.228034/100,
  7. 9.804036/10000,
  8. -4.413030/100000,
  9. 1.057734/1000/1000,
  10. -1.052755/10000/10000};
  11. и т.д.
  12.  

Напишем свою функцию возведения в степень, типовая занимаем много места.

  1. double mypow(double a,int b) {
  2. double x=a;
  3. if (b==1) return a;
  4. while (b--)
  5. {
  6. x *= a;
  7. }
  8.  
  9. return x;
  10. }
  11.  

И сами вычисления

  1. t=0;
  2. if (e>20)
  3. {
  4. t=t+cKf[0];
  5. for (i=0;i<6;i++)
  6. {
  7. t=t + (cKf[i+1])*mypow(e,(i+1));
  8. }
  9. } else {
  10. for (i=0;i<((e>0)? 9 : 8);i++)
  11. {
  12. t=t + ((e>0)?cK[i]:cKm[i])*mypow(e,i+1);
  13. }
  14. }
  15. t*=100;
  16. tmcpT = (long) t + bmeT;
  17. mcpT = tmcpT;
  18.  

В самом конце необходимо добавить к полученной температуре показания датчика NCT75, которые отражают температуру соединения разъёма подключения термопары.

Спящий режим

В данном приборе используется много датчиков и ЖК экран. Все это нужно правильно «уложить» спать. Для этого используется функция sleep().

  1. u8 sleep(void) {
  2. readytosleep = 1;
  3.  
  4. //off disp! need pullup PA1 !
  5. GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_IN_PU_NO_IT);
  6. GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_IN_PU_NO_IT);
  7. GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_IN_PU_NO_IT);
  8.  

Сначала выключим все выводы управления дисплеем. Переменная readytosleep нужна для того, чтобы выводы не были случайно включены в обработчике прерывания.

  1. //nct datchik sleep
  2. buff[0] = (uint8_t) 0b1;//config reg
  3. buff[1] = (uint8_t) 0b1;//shutdown
  4. if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
  5. {
  6. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  7. return 0;
  8. };
  9.  

Усыпляем датчик температуры nct.

  1. //uv datchik
  2. buff[0] = (uint8_t) 0b0;//config reg
  3. buff[1] = (uint8_t) 0b01000001;//low byte
  4. buff[2] = (uint8_t) 0b0;//high byte - always 0
  5. if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 3,0) )
  6. {
  7. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  8. return 0;
  9. };
  10.  

Следом УФ датчик. И потом внешний ADC.

  1. //mcp adc
  2. buff[0] = (uint8_t) 0b10001100;//one shot and sleep
  3. if( ! I2C_writenbyte((uint8_t)MCPaddr, buff, 1,0) )
  4. {
  5. I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
  6. return 0;
  7. };
  8.  
  9. Delay(10);//need for end i2c!
  10.  

Так как периферия еще передает байт после выхода из функции передачи данных, то необходимо подождать некоторое время для перехода датчика в сон.

После выхода из сна, включаем все обратно.

  1. halt();
  2. Delay(100);
  3.  
  4. kn[0] = 0;
  5.  
  6. nctinit();
  7. mcpinit();
  8. uvinit();
  9.  
  10. GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_OUT_PP_LOW_SLOW);
  11. GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_OUT_PP_LOW_SLOW);
  12. GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_OUT_PP_LOW_SLOW);
  13.  
  14. //on LCD
  15. readytosleep = 0;
  16.  

Как изготовить наш прибор

Делаем плату и все запаиваем

  1. Подготовить или приобрести необходимые инструменты: все для пайки, ST-LINK (будет нужен для программирования и отладки МК)

  2. Внимательно прочитать статьи из раздела Обязательная теория.

  3. Скачать необходимые файлы по данному прибору с github.

  4. Изготовить плату для прибора самостоятельно (это совсем несложно, в нашей инструкции все подробно описано).

  5. Приобрести все необходимые комплектующие.

  6. Запаять все компоненты на плату, смотри наше видео.

  7. Плата готова!

Для программирования вам понадобятся провода мама-мама. Вы можете их сделать сами из данного набора. Для программирования, можно сразу подключить питание с ST-Link, вместо аккумулятора и SWIM вывод.

Размещение УФ датчика в корпусе

На поверхность датчика нанесено специальное покрытие, фильтрующее часть спектра. Поэтому в идеале датчик необходимо прикрывать матовым фильтром, который может пропускать УФ лучи и не даёт боковой засветки. На роль такого фильтра отлично подходит оптический тефлон. Однако купить его непросто. Если у вас такая возможность, то лучше это сделать. Согласно документации датчик можно использовать и без этого фильтра, но необходимо защитить его от боковой засветки. Так мы и будем делать. Вся медь вокруг датчика должна быть закрашена чёрным несмываемым маркером.

Ширина отверстия надо датчиком должна быть строго рассчитана по формуле:


Можно использовать как круглое так и квадратное отверстие. Круглое сделать проще — просверлить. Также необходимо исключить проникновение света внутрь корпуса.

Сам датчик можно окружить ПВХ пластиком, стенки окрасить в черный цвет. В итоге у вас должно получиться примерно так:




Запаивание датчика УФ обычным паяльником

Если вы уже сделали себе паяльную станцию с паяльным феном, то лучше запаять этот датчик с ее помощью. Но если ее нет — то не беда, запаять этот датчик не сложно и обычным паяльником. При трассировке платы дорожки к данному датчику специально сделаны длиннее нужного размера. В этом случае остаётся много места для жала паяльника.


Начинать запаивание всей платы в этом случае надо с датчика. Тогда можно будет легко проверить, что все припаяно качественно. Для начала необходимо подготовить чистое рабочее место. Лучше взять чистый лист А4, аккуратно достать датчик и положить на лист ножками вверх. Намазать флюсом ЛТИ-120 и аккуратно залудить контактные площадки датчика — оставив небольшие бугорки олова. Делать это нужно быстро, чтобы не перегреть датчик. Остатки флюса необходимо смыть спиртом. Много олова оставлять не нужно, его должно быть совсем чуть-чуть. Точно также залуживаем посадочное место датчика на плате. Олово на посадочном месте совсем не оставляем, если оно лишнее — можно его разогнать по дорожкам в стороны.

Добавляем флюс на плату (лучше TR-RMA), кладем датчик на посадочное место, внимательно смотрим на лицевую сторону, чтобы не перепутать местами выводы (отпаять его будет сложно). У датчика сверху отлично видно с одной стороны 3 контактные площадки, а с другой 2-е. Вот по ним и ориентируемся. Проще всего совмещать вывод GND.




После того как вы спозиционировали датчик, не трогая сам датчик, прогреваем паяльником одну дорожку! Олово на датчике тоже в итоге расплавится и он припаяется. После припаивания одного контакта, можно проверить что все верно (не трогая датчик, можно оторвать контактную площадку, зрительно проверить как лежит датчик). В этот момент еще можно все вернуть назад. Если все хорошо, то точно также прогреваем остальные дорожки и все — датчик припаян. Проверить качество пайки очень легко. Достаточно взять обычный мультиметр в режиме проверки диода. Плюс поместить на GND и проверить все остальные выводы — должно быть 500-600 единиц (у датчика есть защитные диоды по всем входам). Далее нужно проверить что нет замыканий между близлежащими контактами.

Вы можете использовать этот приём и с другими датчиками. Когда разрабатываете плату, то делайте дорожки более длинными.

Отладка программы

Если вы решили проводить отладку программы, то необходимо на момент отладки отключать ЖК дисплей, иначе в момент остановки МК на выводах может быть постоянное напряжение. Ориентироваться в этом случае можно по переменным в отладчике.

Если у вас есть желание освоить программирование микроконтроллеров, то рекомендуем написать программу «с нуля» самостоятельно.

Приобретённые навыки

Пайка: пайка паяльником LGA датчика микроразмера.

Схемотехника: LDO, I2C, типовая схема подключения STM8, подключение пищалки, работа с кнопками.

Программирование: I2C, изменение температуры термопары, интегральные датчики температуры, внешний ADC, датчик УФ излучения, расчёт индекса UVA UVB, сложная обработка кнопок, миллисекундный таймер, динамическая индикация ЖК индикатора, спящий режим.

Самостоятельная работа

Вы можете расширить функционал прибора в части измерения UV индекса:

  • реализовать измерение накопленного UV излучения и предупреждение о превышении норматива за день

  • рассчитать время пребывания на солнце с учётом типа кожи и крема от загара

  • сделать функцию I2C сканера — показывает с какими адресами есть датчики на шине I2C

  • сделать символьное отображение режимов

  • реализовать измерение и отображение отрицательной температуры

  • реализовать прочие расширенные функции данного прибора.

Read 14787 times