Содержание статьи
В последнее время все чаще можно видеть, как люди в своих проектах используют Arduino, поскольку там есть, например, Ethernet-шилд или Wi-Fi-шилд. А целый компьютер в этом плане обычно совершенно избыточен. В этой статье я покажу, что использовать Wi-Fi в своем проекте можно и без Arduino. Мы сделаем часы с Wi-Fi и монохромной матрицей, которые будут еще и показывать погоду в нужном городе.
Ключ на старт
В первую очередь перечислю то, что использовал:
- Отладочная плата STM32F3DISCOVERY.
- Экран на базе контроллера KS0108 (в моем случае это MT-12864A российского производства).
- Wi-Fi-модуль WizFi220.
Разрабатывать прошивку можно как минимум в двух IDE: Keil Embedded Development Tools for ARM и IAR Embedded Workbench. Я использую первую, а если тебя заинтересует вторая, то из-за специфики IAR тебе нужна будет IAR Embedded Workbench for ARM.
Само собой, не стоит забывать о бесплатных средствах разработки под ARM, например о связке Eclipse + GNU ARM Eclipse Plug-In + GCC + GDB + OpenOCD. Мануалы по настройке данной связки можно без труда найти в интернетах.
Итак, почему же именно STM? Потому что у них есть такая замечательная вещь, как Standard Peripherals Library (для STM32F303XX качать тут). Это библиотека, которая позволяет работать с периферией, не касаясь регистров. Вот пример настройки USART1.
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
Все очень просто и понятно, не так ли? Не надо лазить по Reference Manual’у (у STM32F303XX он занимает почти тысячу страниц). Сразу видно, что ребята из STMicroelectronics хорошо поработали.
Однако использование данной библиотеки — повод для холивара (одни говорят, что она генерирует кучу лишнего кода, другие — что она делает из программиста ленивца, и так далее), поэтому я не буду призывать тебя, читатель, использовать ее. Делай, как тебе удобнее.
Начать разработку с использованием этой библиотеки очень просто по двум причинам:
- В ней уже есть проекты, пригодные для открытия в IDE (для Keil он лежит в Projects/STM32F30x_StdPeriph_Templates/MDK-ARM/Project.uvproj).
- Начальный код для работы с периферией можно добавить в проект, заменив все файлы из Template на файлы нужного нам примера.
Что же будет делать контроллер в нашей задаче?
- Инициализировать и настраивать Wi-Fi и дисплей.
- Запрашивать погоду с OpenWeatherMap.
- Выводить на экран текущее время, дату и прогноз на несколько дней.
Начинаем. OpenWeatherMap
OpenWeatherMap был запущен в 2012 году группой энтузиастов, загоревшихся целью обеспечить свободную и доступную информацию о погоде для любой точки земного шара. С тех пор этот сайт непрерывно развивается, и о его возможностях можно судить по предоставляемому API: прогнозы на пять дней с расчетом метеоусловий на каждые три часа, прогнозы на срок до 16 дней, получение исторических данных, получение карт, например облачности.
Мы будем использовать прогноз на семь дней с частотой обновления один раз в сутки. В случае большого числа запросов API OpenWeatherMap может требовать ключа, который можно получить после регистрации. Но нам это не страшно. Сомневаюсь, что у нас будет больше сотни запросов в 24 часа.
Для получения прогноза необходимо послать GET-запрос на специально сформированный адрес, который для Москвы выглядит так:
http://api.openweathermap.org/data/2.5/forecast/daily?q=Moscow,ru&units=metric&cnt=7
Итак, мы хотим от API OpenWeatherMap получить прогноз для города Москвы q=Moscow,ru
, в метрической системе мер units=metric
и на семь дней cnt=7
. Если же необходимо получить ответ в JSON, то нужно добавить еще один параметр: mode=json
. Но для нас XML удобнее.
{"cod":"200","message":0.2284,"city":{"id":524901,"name":"Moscow","coord":{"lon":
37.615555,"lat":55.75222},"country":"RU","population":0,"sys":{"population":0}},"cnt":7,
"list":[{"dt":1407142800,
"temp":{"day":28.06,"min":23.9,"max":28.06,"night":23.9,"eve":28.06,"morn":28.06},
"pressure":1011.4,
"humidity":38,
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"speed":4.61,"deg":38,"clouds":64},...]}
OpenWeatherMap вернет нам для каждого дня следующее:
- температуру днем, ночью, вечером и утром, а еще минимальную и максимальную;
- давление в гигапаскалях;
- влажность воздуха;
- текстовое описание погоды и даже имя иконки для отображения
"icon":"04d"
;
- скорость и направление ветра;
- процент облачности.
Нам остается только правильно распарсить ответ сервера.
Продолжаем: WizFi220
Различают два варианта подобных модулей: WizFi220 и WizFi210. Они похожи во всем, кроме одного пункта: у первого модуля выше мощность передатчика и, как следствие, выше потребление.
Обладают они следующими свойствами:
- Размеры 32 x 23,5 x 3 мм.
- Поддержка только 802.11b.
- Шифрование: WEP, WPA/WPA2-PSK, Enterprise (EAP-FAST, EAP-TLS, EAP-TTLS, PEAP).
- Протоколы: UDP, TCP/IP (IPv4), DHCP, ARP, DNS, HTTP/HTTPS Client and Server.
- Напряжение питания: 3,3 В.
- Потребление:
- Все управление происходит через UART.
Если обратиться к распиновке, то можно увидеть, что там присутствуют пины ALARM, ADC, GPIO. Для того чтобы заставить их работать, WIZnet, производитель модулей, рекомендует изменять их прошивку самостоятельно.
Включить модуль очень просто: пины 1, 18, 31 и 48 (все GND) подключаем к земле, 32, 33 и 34 (VIN_3V3, EN_1V8, VDIO) — к +3,3 В, а 40 и 42 (UART0_RX и UART0_TX) — к контроллеру или к ПК через конвертер UART <-> USB.
Чтобы этот модуль правильно инициализировать, ему надо послать данную последовательность команд (каждая команда должна заканчиваться символом возврата каретки CR, 0x0D):
AT
ATE0
AT+WD
AT+NDHCP=1
AT+WPAPSK=SSID,passphrase
AT+WA=SSID
AT+NCLOSEALL
AT+NCTCP=144.76.102.166,80
AT
— команда нужна для проверки правильности работы модуля. В первый раз после включения модуль должен вернутьAT\r\r\n[OK]\r\n
.
ATE0
отключает эхо команд. Теперь модуль больше не возвращает только что принятую команду, а присылает только ответ.
AT+WD
заставляет модуль отключиться от всех Wi-Fi-сетей. Нужно в том случае, если по какой-то причине приходится инициализировать WizFi220 заново без сброса.
AT+NDHCP=1
включаем DHCP-клиент. Я думаю, не надо объяснять, что это.
AT+WPAPSK=SSID,passphrase
требует от WizFi220 посчитать PSK (Pre-Shared Key) для сети и ключа.
AT+WA=SSID
запускает процесс ассоциации с сетью.
AT+NCLOSEALL
закрывает все соединения.
AT+NCTCP=144.76.102.166,80
подключает TCP-клиент к IP-адресу и порту TCP-сервера. В данном случае это адрес openweathermap.org.
Итак, мы подключились к серверу и готовы качать погоду терабайтами. Но как это сделать? А вот тут необходимо вспомнить, что наш WizFi220 служит лишь мостом между нашим контроллером и сервером. Затем надо осознать всю тленность ситуации и пойти в Википедию читать про GET-запросы. Да, все верно. Получать погоду мы будем с помощью отправки запроса серверу.
GET /data/2.5/forecast/daily?q=Moscow&units=metric&cnt=7 HTTP/1.1\n"
"Host: openweathermap.org\n"
"Connection: keep-alive\n"
"\n"
Пустая строка в конце должна быть обязательно!
Осталось совсем немного — понять, как заставить WizFi220 обработать данный запрос корректно. Для этого существуют escape-последовательности. Их всего три, но использовать мы будем наиболее простую:
<ESC>S<CID>data<ESC>E
Здесь <ESC>
— 0x1B
, S
и E
— сокращения от Start и End, CID
— номер соединения Connection ID, а data
— данные для передачи.
Если мы в качестве данных подставим описанный выше GET-запрос, в качестве CID — 0, потому что соединение у нас всего одно, с OpenWeatherMap, и пошлем последовательность в модуль, то этот запрос будет отослан серверу и в ответ вернется запрошенный прогноз вместе с HTTP-заголовком.
Основная часть. Экран
Экран представляет собой ЖК-панель с разрешением 128 х 64 точки и два контроллера управления К145ВГ10, произведенные ОАО «Ангстрем», аналогичные KS0108 фирмы Samsung. Почему два? Потому что данный контроллер может управлять панелью размером лишь 64 х 64 точки. ОЗУ контроллера подразделяется на страницы, колонки и строки. Страница — область памяти размерностью 128 х 8 бит. Экраны могут требовать как +5 В, так и +3 В, и это явно указано в маркировке. В моем случае напряжение питания +5 В.
Разъем для подключения экрана содержит в себе 20 пинов, описание представлено в списке по следующей маске: <номер> — <название из даташита> — <описание из даташита> — <куда подключается>.
- 1 — Ucc — питание — к 5V на Discovery.
- 2 — GND — земля — к GND на Discovery.
- 3 — Uo — вход питания ЖК-панели для управления контрастностью — к подстроечному резистору.
- 4..11 — DB0..DB7 — шина данных — к PD0..PD7 на Discovery.
- 12, 13 — E1, E2 — выбор контроллера — к PD8,PD9 на Discovery.
- 14 — RES — сброс — к PD10 на Discovery.
- 15 — R/W — выбор: чтение/запись — к PD11 на Discovery.
- 16 — A0 — выбор: команда/данные — к PD12 на Discovery.
- 17 — E — стробирование данных — к к PD13 на Discovery.
- 18 — Uee — выход DC – DC преобразователя — к подстроечному резистору.
Кстати, будь очень внимателен при поиске документации на подобные экраны: например, у MT-12864J и MT-12864A изменены распиновки разъемов: если у первого 1 — Ucc, а 2 — GND, то у второго наоборот!
В плане программирования данного контроллера нет ничего сложного: МЭЛТ предоставляет для своих экранов примеры. Например, вот процедура ожидания готовности экрана, предлагаемая производителем.
void WaitReady(bit l, bit r) {
Delay(>140ns);
LCD.E=1;
Delay(>450ns);
}
А вот та же самая процедура, но уже написанная мной.
#define LCD_SET_RESET_LINE() GPIO_WriteBit(GPIOD, GPIO_Pin_10, Bit_RESET)
#define LCD_RESET_RESET_LINE() GPIO_WriteBit(GPIOD, GPIO_Pin_10, Bit_SET)
#define LCD_READ_DATA() GPIO_WriteBit(GPIOD, GPIO_Pin_11, Bit_SET)
#define LCD_WRITE_DATA() GPIO_WriteBit(GPIOD, GPIO_Pin_11, Bit_RESET)
#define LCD_SEND_DATA() GPIO_WriteBit(GPIOD, GPIO_Pin_12, Bit_SET)
#define LCD_SEND_COMMAND() GPIO_WriteBit(GPIOD, GPIO_Pin_12, Bit_RESET)
#define LCD_SET_STROBE_LINE() GPIO_WriteBit(GPIOD, GPIO_Pin_13, Bit_SET)
#define LCD_RESET_STROBE_LINE() GPIO_WriteBit(GPIOD, GPIO_Pin_13, Bit_RESET)
#define LCD_CHOOSE_CRYSTAL_0() GPIO_WriteBit(GPIOD, GPIO_Pin_8, Bit_SET); GPIO_WriteBit(GPIOD, GPIO_Pin_9, Bit_RESET)
#define LCD_CHOOSE_CRYSTAL_1() GPIO_WriteBit(GPIOD, GPIO_Pin_8, Bit_RESET); GPIO_WriteBit(GPIOD, GPIO_Pin_9, Bit_SET)
void waitForLCDReady(uint8_t crystalId) {
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_5 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOD, &GPIO_InitStructure);
LCD_READ_DATA();
LCD_SEND_COMMAND();
if (crystalId == 0) {
LCD_CHOOSE_CRYSTAL_0();
} else {
LCD_CHOOSE_CRYSTAL_1();
}
Delay(1);
LCD_SET_STROBE_LINE();
Delay(1);
while (GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_7) == Bit_SET) {
;
}
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_5 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOD, &GPIO_InitStructure);
LCD_RESET_STROBE_LINE();
}
Еще один важный момент: все символы, выводимые на экран, необходимо рисовать самостоятельно. Для этого я воспользовался замечательной программой от Петра Высочанского KS0108_4_0_1 (последняя версия от января 2010 года). Скачать ее можно вот тут.
Ты можешь легко увидеть в правой части экрана под изображением редактируемого символа его шестнадцатеричный код, который можно скопировать и вставить в код.
Коды почти всех символов и всех пиктограмм находятся в файле symbols.h
.
const uint8_t asciiTable[128][8] = {
...
{0x00,0x00,0x7E,0x4A,0x4A,0x34,0x00,0x00}, //66, B
{0x00,0x00,0x3C,0x42,0x42,0x24,0x00,0x00}, //67, C
{0x00,0x00,0x7E,0x42,0x42,0x3C,0x00,0x00}, //68, D
{0x00,0x00,0x7E,0x4A,0x4A,0x42,0x00,0x00}, //69, E
{0x00,0x00,0x7E,0x0A,0x0A,0x02,0x00,0x00}, //70, F
...
В своей программе я объявил массив uint8_t displayArray[8][128] = {0x00};
. Сначала все, что должно появиться на экране, пишется в этот массив, а лишь затем обновляется изображение.
Подъем! Алгоритм работы
Теперь собственно алгоритм работы:
- Настроить тактирование портов ввода-вывода, USART’ов и RTC —
enableClocks();
.
- Настроить режимы работы портов ввода-вывода —
setUpGPIO();
.
- Настроить USART'ы —
setUpUsart();
.
- Настроить часы реального времени —
setUpRTC();
.
- Настроить экран —
initLCD();
.
- Проинициализировать WizFi220 —
initWizFi220(...);
.
- Запросить погоду в первый раз —
callWeather(...);
.
- Распарсить ее и вывести на экран —
parseAndSetDateTime(...);
иparseAndSetWeather(...);
.
- Каждую минуту обновлять показания часов на экране.
- Каждые 30 минут запрашивать новую погоду.
Заключение
Возможные варианты расширения проекта:
- Использовать цветной экран и выводить на него, например, графики погоды. Как ты помнишь, мы не использовали трехчасовые прогнозы.
- Сделать полноценную метеостанцию и посылать свои измерения в OpenWeatherMap.
- Разработать печатную плату и корпус (без него устройство выглядит страшно).
Как ты теперь понял, использование Wi-Fi в своих проектах — задача совершенно не сложная. Единственное ограничение — довольно высокая стоимость модуля (в Москве, например, около 3000 рублей). Также я надеюсь, что вдохновил тебя на создание чего-нибудь своего, причем необязательно с Wi-Fi :).
Если возникли какие-нибудь вопросы, пиши мне на email, который можно найти в начале статьи.
DVD
Весь код ты можешь найти на диске. Просто скопируй с заменой файлы в папку Template из Standard Peripheral Library.
WWW
WARNING!
Не забывай заземляться! Помни, что разряд статического электричества может убить и модуль Wi-Fi, и экран, и контроллер!