<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Урок 11. Подключение к телевизору. Трансляция погоды]]></title><description><![CDATA[<h2>Цель урока</h2>
<p dir="auto">Привет! Сегодня мы научимся подключать телевизор к M5STACK и выводить на него изображения и текст по средствам видеосигнала (рис. 1).</p>
<p dir="auto"><img src="https://sun1-2.userapi.com/c840721/v840721542/79bda/_HYrFnOd4T4.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 1</p>
<p dir="auto">Необходимо сделать так, чтобы шлюз M5 black периодически получал информацию о погоде на текущий момент с сайта <a href="https://openweathermap.org" title="https://openweathermap.org" target="_blank" rel="noopener noreferrer nofollow ugc">https://openweathermap.org</a> и передавал её по радиоканалу 433 МГц, а M5 gray принимал эту информацию и выводил в графическом виде на телевизор.<br />
В M5 black установлена TF-карта с параметрами Wi-Fi сетей и парметрами сервиса <a href="http://openweathermap.org" target="_blank" rel="noopener noreferrer nofollow ugc">openweathermap.org</a>.</p>
<p dir="auto">При включении устройства пользователю будет предложено вставить карту памяти. На карте памяти будет находится следующие файлы (рис. 1.1):</p>
<ul>
<li>файл с известными Wi-Fi-сетями (system/wifi.ini);</li>
<li>файл стилей (system/openweather.ini) (рис. 1.2).</li>
</ul>
<p dir="auto"><img src="https://sun1-3.userapi.com/c840721/v840721542/79bf1/3jbfhgB4ABw.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 1.1. Содержимое папки system</p>
<p dir="auto"><img src="https://sun1-3.userapi.com/c840721/v840721542/79bfa/hly1Put6ZDQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 1.2. Содержимое файла openweather.ini</p>
<h2>Краткая справка</h2>
<p dir="auto">Под видеосигналом понимается электрический сигнал специальной формы (рис. 2), посредством которого передается телевизионное изображение.<br />
Понятие видеосигнала применимо как к аналоговому, так и к цифровому телевидению, а также к системам отображения информации компьютеров, основанных на электронно-лучевых трубках.<br />
Мгновенное значение освещенности на фоточувствительной поверхности телевизионного фотоприемника преобразуется в мгновенное значение напряжения на выходе этого фотоприемника. Таким образом, в видеосигнале напряжение пропорционально яркости в данной точке изображения. Эта часть видеосигнала носит название сигнала яркости и используется для передачи черно-белого видеосигнала или сигнала яркости в цветном видеосигнале. Диапазон передаваемых значений яркости определяется уровнем черного и уровнем белого сигналов в видеосигнале. Уровень черного является минимальным сигналом яркости и соответствует уровню синхроимпульсов. Он же одновременно является и опорным сигналом. Уровень белого соответствует максимальному уровню передаваемой яркости. Кроме сигнала яркости в видеосигнале присутствуют служебные составляющие, обеспечивающие синхронизацию сигнала между источником и приемником. Существуют два типа сигналов синхронизации – строчные и кадровые. Сигналы кадровой синхронизации обеспечивают передачу информации о времени начала каждого поля телевизионного изображения в видеосигнале, а также информацию о типе этого поля (четное или нечетное).</p>
<p dir="auto"><img src="https://sun9-1.userapi.com/c840721/v840721286/7c5ae/O1eveSL7Cio.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 2. Осциллограмма видеосигнала: 4 мкс - гасящий импульс, 8 мкс - информация о цвете, 52 мкс - сигнал яркости</p>
<h2><strong>Перечень компонентов для урока</strong></h2>
<ul>
<li>M5STACK (2 шт.);</li>
<li>кабель USB-C из стандартного набора;</li>
<li>карта памяти MicroSD на 4 ГБайт;</li>
<li>цветные провода из стандартного набора (тип розетка-вилка);</li>
<li>радиоприёмник MX-JS-05V;</li>
<li>радиопередатчик FS1000A;</li>
<li>телевизор с поддержкой видеосигнала PAL;</li>
<li>RCA-вилка;</li>
<li>паяльник и припой;</li>
<li>термоусадка.</li>
</ul>
<h2>Начнём!</h2>
<h3>Шаг 1. Зарегистрируемся на сайте <a href="http://openweathermap.org" target="_blank" rel="noopener noreferrer nofollow ugc">openweathermap.org</a></h3>
<p dir="auto">Для того, чтобы получать информацию о погоде необходимо зарегистрироваться на данном ресурсе. Регистрация абсолютно бесплатна и не займёт много врмени (рис. 3).</p>
<p dir="auto"><img src="https://pp.userapi.com/c844320/v844320178/2ec3b/pl9dY3gDgRQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 3. Регистрация на сайте</p>
<p dir="auto">После регистрации в своем личном кабинете на сайте перейдите в раздел <strong>API keys</strong> и получите <strong>Key</strong> доступа к сервису.</p>
<p dir="auto"><img src="https://pp.userapi.com/c844320/v844320178/2ec52/W1Iz5sKjFpQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 3.1. Получение API-ключа</p>
<p dir="auto">На этом регистрация на сайте завершена.</p>
<h3>Шаг 2. Сделаем переходник для подключения к ТВ</h3>
<p dir="auto">Здесь без лишних слов - просто возьмите и сделайте. Главное, помните - цветной провод припаяйте к центральному контакту RCA-штекера (рис. 4 - 4.2).</p>
<p dir="auto"><img src="https://pp.userapi.com/c844320/v844320178/2ec6e/grXTDOpOcmY.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 4.</p>
<p dir="auto"><img src="https://pp.userapi.com/c844320/v844320178/2ec81/dTxglfzEJiU.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 4.1</p>
<p dir="auto"><img src="https://pp.userapi.com/c844320/v844320178/2ec77/0-lDL2pwtaE.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 4.2</p>
<h3>Шаг 3. Напишем скетч для шлюза на m5 black</h3>
<p dir="auto">Для того, чтобы получить информацию с сайта с помощью GET-запроса напишем несложную функцию. В качестве аргумента функция принимает строку GET-запроса, и возвращает ответ сервера в качестве строки:</p>
<pre><code>String GET(String url) {
  while (true)
  {
    if ((wifiMulti.run() == WL_CONNECTED))
    {
      HTTPClient http;
      http.begin(url);
      int httpCode = http.GET();
      if (httpCode &gt; 0)
      {
          if (httpCode == HTTP_CODE_OK)
          {
            return http.getString();
          }
      }
      else
      {
        return (httpCode + "");
      }
      http.end();
    }
  }  
  return ""; 
}
</code></pre>
<p dir="auto">Вся конфигурационная информация для сервиса openweathermap, как было сказано ранее, будет храниться на TF-карте. Поэтому нам необходимо написать функцию, для формирования GET-запроса:</p>
<blockquote>
<p dir="auto">Обратите внимание - сервис <strong>nl.sxgeo.city</strong> - возвращает информацию о Вашем расположении на основании IP-адреса, который нет необходимости передавать в GET-запросе.</p>
</blockquote>
<pre><code>String configOpenWeather() {
  String file = TFReadFile("/system/openweather.ini");
  if (file != "")
  {  
    String city = "&amp;q=" + parseString(1, '\"', parseString(1, ':', parseString(5, ',', GET("http://nl.sxgeo.city/?")))); 
    String api_key = "&amp;APPID=" + parseString(0, ' ', file);
    String app_id = "&amp;id=" + parseString(1, ' ', file);
    String lang = "&amp;lang=" + parseString(2, ' ', file);
    String units = "&amp;units=" + parseString(3, ' ', file);
    String host = "http://api.openweathermap.org/data/2.5/weather?";
    String url_ = host + city + api_key + app_id + lang + units;
    return url_;
  }
  return "";
}
</code></pre>
<p dir="auto">В одном из прошлых уроков мы рассматривали функцию, настраивающую Wi-Fi с помощью параметров полученных с TF-карты. В этот раз мы несколько модифицируем тело функции. Добавим возможность сохранения конфигурационного файла под операционной системой MacOS X. Дело в том, что Mac добавляет один спец. символ в конце строки, не как Windows - два.</p>
<pre><code>bool configWifiMulti() {
  /* Get WiFi SSID &amp; password from wifi.ini from TF-card */
  String file = TFReadFile("/system/wifi.ini");
  if (file != "")
  {
    for (int i = 0; i &lt; cntChrs(file, '\n'); i++)
    {
      String wifi = parseString(i, '\n', file);
      wifi = wifi.substring(0, (wifi.length()));
      if (wifi[wifi.length() - 1] == '\r') wifi = wifi.substring(0, (wifi.length() - 1));
      String ssid = parseString(0, ' ', wifi);
      String pswd = parseString(1, ' ', wifi);
      char* ssid_ = strToChar(ssid);
      char* pswd_ = strToChar(pswd);
      if (wifiMulti.addAP(ssid_, pswd_))
      {
        return true;
      }
    }
  }
  return false;
}
</code></pre>
<p dir="auto">С помощью следующих строк кода будем извлекать необходимые нам данные из ответа сервера:</p>
<pre><code>temp = parseString(2, ':', parseString(7, ',', weather));
pres = parseString(1, ':', parseString(8, ',', weather));
hum = parseString(1, ':', parseString(9, ',', weather));
desc = parseString(1, '"', parseString(1, ':', parseString(4, ',', weather))); 
weatherIcon = parseString(1, '"', parseString(1, ':', parseString(5, ',', weather)));
</code></pre>
<p dir="auto">Далее будем разделять данные при помощи спецсимвола и отправлять в радиоэфир:</p>
<pre><code>sendString("1" + String((char)0x1d) + temp);
delay(1);
sendString("2" + String((char)0x1d) + String(round(pres.toInt() * 0.75)));
delay(1);
sendString("3" + String((char)0x1d) + hum);
delay(1);
sendString("4" + String((char)0x1d) + desc);
delay(1);
sendString("5" + String((char)0x1d) + weatherIcon);
delay(1);	
</code></pre>
<p dir="auto">На этом написание скетча для шлюза-передатчика закончено.</p>
<h3>Шаг 4. Теперь напишем скетч для приёмника на m5 gray</h3>
<p dir="auto">Для вывода изображения на ТВ мы будем использовать библиотеку автора <a href="http://bitluni.net/esp32-composite-video/" title="http://bitluni.net/esp32-composite-video/" target="_blank" rel="noopener noreferrer nofollow ugc">http://bitluni.net/esp32-composite-video/</a>. Поскольку библиотека использует оба канала ЦАП, то пришлось несколько изменить библиотку, чтобы отключить левый канал. Я упростил процедуру использования библиотеки, поместив всё самое главное в заголовочный файл <em>m5stack_tv.h</em>:</p>
<pre><code>namespace m5stack_tv
{
  #include "CompositeGraphics.h"
  #include "Image.h"
  #include "CompositeOutput.h"
  #include &lt;soc/rtc.h&gt;
  #include "font6x8.h"
  
  const int XRES = 320;
  const int YRES = 200;

  CompositeGraphics graphics(XRES, YRES);
  CompositeOutput composite(CompositeOutput::NTSC, XRES * 2, YRES * 2);
  Font&lt;CompositeGraphics&gt; font(6, 8, font6x8::pixels);

  char* strToChar(String str) {
    int len = str.length() + 1;
    char* buf = new char[len];
    strcpy(buf, str.c_str());
    return buf;
  }

  void compositeCore(void *data) {  
    while (true)
    {
      composite.sendFrameHalfResolution(&amp;graphics.frame);
    }
  }
  
  void begin() {
    rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);
    composite.init();
    graphics.init();
    graphics.setFont(font);
    xTaskCreatePinnedToCore(compositeCore, "c", 2048, NULL, 1, NULL, 0);
  }

  void setTextColor(int c) {
    graphics.setTextColor(c);
  }
  
  void setCursor(int x, int y) {
    graphics.setCursor(x, y);
  }

  void print(String str) {
    graphics.print(strToChar(str));
  }

  void drawBitmap(int x, int y, int w, int h, const unsigned char* img) {   
    Image&lt;CompositeGraphics&gt; img_(w, h, img);
    img_.draw(graphics, x, y);
  }

  void fillRect(int x, int y, int w, int h, int color = 0) {
    graphics.fillRect(x, y, w, h, color);
  }

  void drawDot(int x, int y, int color) {
    graphics.dotFast(x, y, color);
  }

  void clearScreen(int color = 0) {
    fillRect(0, 0, XRES, YRES, color);
  }
}
</code></pre>
<blockquote>
<p dir="auto">Обратите внимание: формат изображений отличается от того, что мы рассматривали в уроках по работе со встроенным дисплеем. Для того, чтобы конвертировать изображение для вывода его на ТВ необходимо воспользоваться программой конвертер из пункта "Downloads".</p>
</blockquote>
<p dir="auto">Напишем функцию, которая в зависимости от кода иконки погоды будет выводить изображение на экран:</p>
<pre><code>void drawWeatherIcon(int x, int y, String str) {
  if (str == "01d") m5stack_tv::drawBitmap(x, y, _01d::xres, _01d::yres, _01d::pixels);
  else if (str == "01n") m5stack_tv::drawBitmap(x, y, _01n::xres, _01n::yres, _01n::pixels);
  else if (str == "02d") m5stack_tv::drawBitmap(x, y, _02d::xres, _02d::yres, _02d::pixels);
  else if (str == "02n") m5stack_tv::drawBitmap(x, y, _02n::xres, _02n::yres, _02n::pixels);
  else if (str == "03d") m5stack_tv::drawBitmap(x, y, _03d::xres, _03d::yres, _03d::pixels);
  else if (str == "03n") m5stack_tv::drawBitmap(x, y, _03n::xres, _03n::yres, _03n::pixels);
  else if (str == "04d") m5stack_tv::drawBitmap(x, y, _04d::xres, _04d::yres, _04d::pixels);
  else if (str == "04n") m5stack_tv::drawBitmap(x, y, _04n::xres, _04n::yres, _04n::pixels);
  else if (str == "09d") m5stack_tv::drawBitmap(x, y, _09d::xres, _09d::yres, _09d::pixels);
  else if (str == "09n") m5stack_tv::drawBitmap(x, y, _09n::xres, _09n::yres, _09n::pixels);
  else if (str == "10d") m5stack_tv::drawBitmap(x, y, _10d::xres, _10d::yres, _10d::pixels);
  else if (str == "10n") m5stack_tv::drawBitmap(x, y, _10n::xres, _10n::yres, _10n::pixels);
  else if (str == "11d") m5stack_tv::drawBitmap(x, y, _11d::xres, _11d::yres, _11d::pixels);
  else if (str == "11n") m5stack_tv::drawBitmap(x, y, _11n::xres, _11n::yres, _11n::pixels);
  else if (str == "13d") m5stack_tv::drawBitmap(x, y, _13d::xres, _13d::yres, _13d::pixels);
  else if (str == "13n") m5stack_tv::drawBitmap(x, y, _13n::xres, _13n::yres, _13n::pixels);
  else if (str == "50d") m5stack_tv::drawBitmap(x, y, _50d::xres, _50d::yres, _50d::pixels);
  else if (str == "50n") m5stack_tv::drawBitmap(x, y, _50n::xres, _50n::yres, _50n::pixels);
}
</code></pre>
<p dir="auto">Сами изображения подключим заголовочным файлом:</p>
<pre><code>#include "weatherIcons/main.h"
</code></pre>
<p dir="auto">Запуск осуществляется вызовом метода:</p>
<pre><code>m5stack_tv::begin();
</code></pre>
<p dir="auto">Таким образом при получении информация о погоде будет немедленно отображаться на ТВ:</p>
<pre><code>void loop() { 
  if (radioRX.available(&amp;k))
  {
    message("data accepted");
    radioRX.read(&amp;j, sizeof(j));
    delay(1);
    message(j);
    int type = (parseString(0, (char)0x1d, j)).toInt();
    String data = parseString(1, (char)0x1d, j);
    if (type == 1) temp = data;
    else if (type == 2) pres = data;
    else if (type == 3) hum = data;
    else if (type == 4) desc = data; 
    else if (type == 5) icon = data;
    
    if (type &gt; 0)
    {
      m5stack_tv::setTextColor(0);
      m5stack_tv::clearScreen(54);
      message("drawing on TV");  
      m5stack_tv::setCursor(140, 60);
      m5stack_tv::print("Temperature, C: " + temp);
      m5stack_tv::setCursor(140, 80);
      m5stack_tv::print("Humidity, %: " + hum);
      m5stack_tv::setCursor(140, 100);
      m5stack_tv::print("Pressure, mm Hg: " + pres);
      m5stack_tv::setCursor(140, 120);
      m5stack_tv::print(desc);
      drawWeatherIcon(30, 45, icon);
    }
  }
}
</code></pre>
<h3>Шаг 5. Запуск!</h3>
<p dir="auto">В разделе <strong>"Download"</strong> прилагается видео с демонстрацией работы. На этом урок завершён.</p>
<h2>Downloads</h2>
<ul>
<li>Скетчи и библиотека (GitHub): <a href="https://github.com/dsiberia9s/TV-out.-Weather-broadcast" title="https://github.com/dsiberia9s/TV-out.-Weather-broadcast" target="_blank" rel="noopener noreferrer nofollow ugc">https://github.com/dsiberia9s/TV-out.-Weather-broadcast</a></li>
<li>Конвертер изображений для ТВ: <a href="https://yadi.sk/d/PzJVuAWj3UbLiv" title="https://yadi.sk/d/PzJVuAWj3UbLiv" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/PzJVuAWj3UbLiv</a></li>
<li>Видео с демонстрацией работы (YouTube): <a href="https://youtu.be/OLJlK17hkDo" target="_blank" rel="noopener noreferrer nofollow ugc">https://youtu.be/OLJlK17hkDo</a></li>
</ul>
]]></description><link>https://community.m5stack.com/topic/181/урок-11-подключение-к-телевизору-трансляция-погоды</link><generator>RSS for Node</generator><lastBuildDate>Sat, 27 Jun 2026 19:27:43 GMT</lastBuildDate><atom:link href="https://community.m5stack.com/topic/181.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 20 Apr 2018 11:25:02 GMT</pubDate><ttl>60</ttl></channel></rss>