# Входы и выходы микроконтроллера
## Управление внешним устройством на примере реле
Сейчас мы укажем какие изменения необходимо привнести в ход [лабораторной работы 1.2](https://innovationcampus.ru/lms/mod/book/view.php?id=1320),
чтобы выполнить её на имеющимся в нашем распоряжении оборудовании (Unwired Devices).
Для этого задания нам понадобятся:
- радиомодуль UNWR с платой-адаптером UMDK-RF
![ud-min2](/assets/images/iot-academy/ud-min2.png)
- модуль с двумя электромагнитными реле UMDK-2RDC
![umdk-2rdc](/assets/images/iot-academy/umdk-2rdc.png)
Обратите внимание, что модули Unwired Devices имеют ключ, показывающий какой стороной нужно подключать модули один к другому. Ключ у всех модулей **всегда** должен быть с одной стороны!
Модуль UMDK-2RDC оснащен двумя электромагнитными реле Omron G6D-1A, способными коммутировать нагрузку постоянного или переменного тока (до 250 В переменного тока и ток до 5 А).
Модуль UMDK-2RDC может несколько отличаться от приведенного на рисунке выше, так как допускает установку переключателя,
позволяющего для каждого из реле выбирать один из трёх GPIO. При отсутствии такого переключателя модуль конфигурируется перемычками на использования `GPIO16` и `GPIO17`.
В нашей лаборатории модули с переключателями.
![umdk-2rdc-var](/assets/images/iot-academy/umdk-2rdc-var.png) ![onoff](/assets/images/iot-academy/onoff.png)
Возможно сами переключатели еще заклеены защитной пленкой – удалите её, если это никто не сделал до вас.
Обратите внимание, что для каждого реле **только один** переключатель должен быть установлен в положение `on`,
все остальные должны быть в противоположном положении, то есть `off`.
Настоятельно рекомендуем использовать `GPIO17` для реле K1 и `GPIO16` для K2.
![umdk-2rdc-onoff](/assets/images/iot-academy/umdk-2rdc-onoff.png)
Соответствие используемых портов микроконтроллера STM32L151 и обозначение портов ввода-вывода на платах Unwired Devices для каждого из реле приведено в таблице.
Обратите вниамание, платы Unwired Range не поддерживают нумерацию портов в стиле Arduino, зато у ниж есть своя, и обратиться к порту можно по макроопределению вида
`UNWD_GPIO_x`, где `x` – это фактически номер контакта на плате.
| Реле | Порт/Контакт (UMDK-RF) | Порт контроллера (STM32L151) | Макроопределение |
|:------:|:-----------------------:|:-----------------------------:|:------------------:|
| K1 | `5` | `PA_5` | `UNWD_GPIO_5` |
| K1 | `7` | `PB_9` | `UNWD_GPIO_7` |
| **K1** | **`17`** | **`PA_15`** | **`UNWD_GPIO_17`** |
| K2 | `4` | `PA_4` | `UNWD_GPIO_4` |
| K2 | `6` | `PB_8` | `UNWD_GPIO_6` |
| **K2** | **`16`** | **`PB_3`** | **`UNWD_GPIO_16`** |
### Задача
* Измените программу, которую вы написали в прошлый раз (мигалка светодиодом), так, чтобы одновременно со светодиодом включалось и выключалось реле.
*Подсказка*: нужно задействовать еще один цифровой выход.
* Доработайте программу управления реле так, чтобы первое реле включалось (и отключалось) с каждым третьим включением светодиода на плате, а второе – с каждым пятым.
## Считываем нажатие кнопки
Для этого задания вам понадобится:
- радиомодуль UNWR с платой-адаптером UMDK-RF
![ud-min3](/assets/images/iot-academy/ud-min3.png)
В нашем наборе от Unwirde Devices тоже можно считывать состояние кнопки. Это кнопка `SAFE`, расположенная на плате-адаптере и
подключенная к порту `PB_1` микроконтроллера. Более того, обращаться с ней можно так же, как и в большинстве отладочных плат – через макроопределение
`USER_BUTTON`. Соответственно, код программы в нашем случае будет таким же (мы только заменили устаревшую функцию задержки на правильный код для Mbed 6):
```cpp
#include "mbed.h"
DigitalIn mybutton(USER_BUTTON);
DigitalOut myled(LED1);
int main()
{
mybutton.mode(PullUp);
while(1)
{
printf("Button state is: %d\n\r", mybutton.read());
if (mybutton == 0)
{ // Button is pressed
myled = !myled; // Toggle the LED state
ThisThread::sleep_for(200ms); // Sleep 200 ms
}
}
}
```
### Задача
Можно попробовать решить задачу посложнее. Установите модуль кнопок (соблюдая позицию ключа):
![umdk-4btn](/assets/images/iot-academy/umdk-4btn.png)
Теперь вы можете получать информацию с четырех дополнительных кнопок через соответствующие порты
| Кнопка | Порт/Контакт (UMDK-RF) | Порт контроллера (STM32L151) | Макроопределение |
|:------:|:-----------------------:|:-----------------------------:|:------------------:|
| S1 | `7` | `PB_9` | `UNWD_GPIO_7` |
| S2 | `6` | `PB_8` | `UNWD_GPIO_6` |
| S3 | `5` | `PA_5` | `UNWD_GPIO_5` |
| S4 | `4` | `PA_4` | `UNWD_GPIO_4` |
Одновременное подключение модуля кнопок UMDK-4BTN и модуля реле UMDK-2RDC,
сконфигурированного на использование портов `GPIO4` – `GPIO7`, **выведет микроконтроллер из строя!**.
Напишите программу, проверяющую нажатие каждой из четырех кнопок модуля UMDK-4BTN.
P.S. Это фактически [задание 1.2.5 основного курса](https://innovationcampus.ru/lms/mod/book/view.php?id=1320&chapterid=1118),
и на наш взгляд его целесообразнее проделать именно сейчас.
## Выдача ШИМ-сигнала с платы
Эта часть работы для нашего оборудования претерпит лишь незначительные изменения.
Нам понадобится радиомодуль UNWR с платой-адаптером UMDK-RF
![ud-min3](/assets/images/iot-academy/ud-min3.png)
И самый простой пример работы с ШИМ ничем не будет отличаться от программы, приведенной на сайте IT Академии:
```cpp
#include "mbed.h"
PwmOut PWM1(LED1);
int main()
{
PWM1.period(0.010); // set PWM period to 10 ms
PWM1 = 0.5; // set duty cycle to 50%
}
```
А вот для управления яркостью светодиода с клавиатуры компьютера рекомендуем посмотреть другой пример,
и сказать чем же он отличается:
```cpp
// host terminal LED dimmer control
#include "mbed.h"
int main()
{
PwmOut led(LED1); // PB_0
float brightness = 0.5;
const float brightness_step = 0.05f;
const int freq[] = {1, 5, 10, 25, 30, 35, 40, 50, 75, 100, 250, 500, 1000}; // Hz
int f_length = sizeof(freq) / sizeof(freq[0]);
int f_ind = 3;
printf("MultiTech xDot board\n\n\r");
printf("Control of LED dimmer by host terminal\n\r");
printf("press '+' or '-' for change duty time\n\r");
printf("press '<' or '>' for change frequency\n\r");
printf("press 'i' or 'I' for info about current mode\n\r");
printf("Frequency = %d, Duty = %1.2f\n\r", freq[f_ind], brightness);
while(1)
{
led.period_us( 1000000L / freq[f_ind] );
led = brightness;
char c = getchar();
ThisThread::sleep_for(1ms);
switch (c)
{
case '+':
brightness += brightness_step;
break;
case '-':
brightness -= brightness_step;
break;
case '<':
f_ind--;
break;
case '>':
f_ind++;
break;
}
if (brightness > 1.0f)
brightness = 1.0f;
if (brightness < 0.0f)
brightness = 0.0f;
if(f_ind >= f_length)
f_ind = 0;
if(f_ind < 0)
f_ind = f_length - 1;
switch (c)
{
case '+':
case '-':
printf("Duty = %1.2f\n\r", brightness);
break;
case '<':
case '>':
printf("Frequency = %d\n\r", freq[f_ind]);
break;
case 'i':
case 'I':
printf("Frequency = %d, Duty = %1.2f\n\r", freq[f_ind], brightness);
break;
}
}
}
```
Если скомпилировать последний пример и загрузить его в нашу плату, то управлять яркостью свечения светодиода, конечно, можно,
но вот информация в последовательный порт будет выводиться неправильно: если частоту ШИМ мы еще увидим, то вот процент заполнения (или же процент яркости) – нет.
Вместо какого-то конкретного значения у нас будет постоянно отображаться строка *Duty = %1.2f*. Происходит это потому, что в Mbed 6-й версии
по умолчанию используется "урезанная" версия функции `printf`, которая не умеет работать со значениями типа `float`, зато занимает мало места в памяти микроконтроллера.
Чтобы включить стандартную версию функции `printf` надо в корне проекта (рядом с файлом `platformio.ini`) создать новый файл с именем `mbed_app.json` и разместить в нем
следующий текст:
```
{
"target_overrides": {
"*": {
"target.printf_lib": "std"
}
}
}
```
Файл `mbed_app.json` – это файл конфигурации приложения, написанного с использованием фреймворка Mbed, и в нем могут содержаться различные настройки.
Сейчас мы как раз и указали, что библиотека функции печати должна быть стандартной, то есть `std`.
### Задача
- Выполните все то, что написано в [основной ветке курса](https://innovationcampus.ru/lms/mod/book/view.php?id=1320&chapterid=1116)
- Поэкспериментируйте с различными вариантами частоты ШИМ.
- Оцените насколько регулирование яркости свечения светодиода в каждом из случаев комфортно для человеческого глаза (незаметно мерцание).
## Считывание аналогового сигнала [на примере датчика влажности почвы]
С аналоговыми датчиками влажности почвы прямо беда! На ввсех точно не хватит. Но это не повод отчаиваться и оставлять [материал](https://innovationcampus.ru/lms/mod/book/view.php?id=1320&chapterid=1117) без внимания.
У нас есть несколько таких датчиков влажности почвы:
![soil](/assets/images/iot-academy/ao_soil_moisture_sensor.png)
Особенностью аналоговых датчиков является то, что на их выходах генерируется непрерывный (*аналоговый*) сигнал, значение уровня которого является функцией времени.
Этим аналоговый датчик и отличается от цифрового, где на выходе присутствует некоторая последовательность бит данных, кодирующих результаты измерения,
и в самом минимальном варианте имеющих всего два значения `0` или `1`.
В рамках данного практикума вместо приведенного выше датчика влажности почвы можно воспользоваться практически любым датчиком с аналоговым выходом.
Пожалуй, самым простым аналоговым датчиком является переменный резистор:
![wh148-1](/assets/images/iot-academy/wh148-1.png)
Напомним, что переменный резистор фактически представляет собой делитель напряжения
![Rvar](/assets/images/iot-academy/Rvar.png)
и при подключении крайних выводов к источнику 3.3 В, напряжение на среднем выводе (движок переменного резистора) относительно общего сигнала будет определяться по формуле:
Uвых = 3.3 * R2/(R1 + R2),
где R1 и R2 – это, соответственно, сопротивления между средним и верхним, и средним и нижним выводами.
Общее сопротивление при этом не меняется, в каком бы положении ни находился движок этого резистора, R1 + R2 = Rном = const.
Сгодится практически любой переменный резистор, с номиналом от 1 кОм и выше.
Для обработки аналоговых величин в микроконтроллерах используется специальный модуль – АЦП (Аналогово-Цифровой Преобразователь).
На платах Unwired Devices аналоговые значения можно подавать на следующие выводы:
| GPIO (UMDK-RF) | Порт STM32L151 |
|:-----------------:|:---------------:|
| `GPIO4` | `PA_4` |
| `GPIO5` | `PA_5` |
| `GPIO24` | `PA_1` |
| `GPIO25` | `PA_2` |
| `GPIO26` | `PA_3` |
| `GPIO27` | `PA_6` |
| `GPIO28` | `PA_7` |
Для подключения переменного резистора можно воспользоваться беспаечной макетной платой и тремя проводками.
![unwr_r](/assets/images/iot-academy/unwr_r.png)
![sltwV](/assets/images/iot-academy/sltwV.png)
Только вспомните как соединены контактные площадки на макетной плате! Средние точки – поперек, а точки между красными и синими линиями – вдоль!
**Неправильное подключение может привести к короткому замыканию и выходу платы из строя!**
Можно вообще не использовать макетную плату, благо к такому переменному резистору провода можно подключить непосредственно. Только клеммник нужно повернуть на 90 градусов:
![r_wire](/assets/images/iot-academy/r_wire.png)
Если подключим левую ножку переменного резистора к плате UMDK-RF к контакту, обозначенному `GND`, правую – к контакту `3V3`, а среднюю – к контакту `4`,
то кодом для работы с АЦП будет следующим:
```cpp
#include "mbed.h"
AnalogIn my_adc(PA_4); // GPIO4
DigitalOut led(LED1);
int main()
{
printf("\nSTM32 ADC example\n\r");
while(1)
{
printf("ADC read = %f\n\r", (my_adc.read()*100));
led = !led;
ThisThread::sleep_for(1s);
}
}
```
Кстати, а что нужно сделать, чтобы увидеть вещественное значение?
Если вы справились со всеми задачами достаточно быстро, то мы можем вам предложить самостоятельно подключить аналоговый датчик освещенности
![pht_module](/assets/images/iot-academy/pht_module.jpg)
У нас в лаборатории таких есть две штуки.
Схема подключения – все те же три провода, к тем же контактам платы UMDK-RF
| Модуль | UMDK-RF |
|--------|---------|
| `VCC` | `3V3` |
| `AO` | `4` |
| `DO` | - |
| `GND` | `GND` |
Возможно, модуль UNWR-SOUND подойдет в качестве аналогового датчика, ведь на `GPIO25` (он же `PA_2`) выводится прошедший через училитель аналоговый сигнал с микрофона.
Или можно исследовать сигнал с `GPIO26` (он же `PA_3`). Наверное, это мощность. По крайней мере так можно понять из обозначений в даташитах.
Но вот какой диапазон у всего этого хозяйства – непонятно.
В целом – работает. Но это не точно!
![unwr_sound](/assets/images/iot-academy/unwr_sound.png)