Контроллер версия 2

У меня уже была версия 1, но недавно с испытаний вернулась более новая и улучшенная версия. Теперь она выглядит так:

Это по-прежнему шилд для Arduino Nano, но функционал немного поменялся.

Во-первых, эта версия умеет “распознавать” на входе сигналы уровнем как 220В, так и 24В. При этом без разницы, постоянное или переменное напряжение. Бонусом добавил световую индикацию состояния входов.

Во-вторых, плата теперь имеет два выхода 0-10В. Мелочь, но это очень приятная мелочь.

И наконец, сделал “закладку” для универсального порта. Это маленькая плата-расширение на снимке снизу

Сам порт с испытаний не вернулся, но если все будет хорошо, он обеспечит вход/выход 0-10В, вход-выход 0-20мА и измерение сопротивлений от 100Ом до 100кОм. Разрабатывался в расчете на “закрытие” всех потребностей обычных автоматизаторов. Или говоря другими словами, для тех кому не нужны защиты в 5кВ, гальванические развязки и прочее. Благодаря этому конечная цена данной версии колеблется в районе 2000 рублей в зависимости от объемов.

Но нет пределов совершенству. Скоро будет 3я версия платы, которая закроет найденные недочеты. Например, можно и нужно выровнять реле. И добавить еще немного индикации. И … в общем, все некритично, но практично.

Контроллер версия 1

Тут можно и подлиннее, чем в фейсбучике.

Как-то раз ко мне пришел заказчик. И захотел он странного для меня: шилд для ардуины. Вернее, первоначально задача была поставлена еще проще: вот есть нцать шилдов, собери их в одну кучу, что бы без проводов. И что бы arduino uno управляла всем этим. После выяснения, где и что будет этим управлять, я немного офигел. Оказывается, в вентиляции и теплоснабжении есть куча мест, которые требуют простого и тупого управления в духе “вон там сигнал появился? Ну и щелкни вот тут”. И ничего больше не надо. Вообще.

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

Поговорили, почесали макушки и ударили по рукам. Я пошел делать, заказчик пошел переводить денег.

Долго ли, коротко, но я сделал первую версию контроллера. 4 входа 220в, 4 переключающих реле, выход 0-10В, и развязанный rs-485. Плюс пара подтянутых выходов для 1-Wire и все это под управлением ардуинки, запрограммированной в fl-prog. Ну и питаться может от 7В до 40В.

photo_2017-02-03_09-44-40

photo_2017-02-03_09-44-33

В общем-то ничего особенного от слова вообще. Обычный навороченный шилд для ардуины и особенного в нем только то, что он у меня первый. Ну не делал я раньше шилдов для ардуинок 🙂

Первым же неприятным фактом стало то, что ардуинка отказывалась прошиваться, когда она стоит в контроллере. Это 100% мой косяк, ибо “сделай как там” привело к тому, что я использовал драйвер rs-485 “как у китайцев”. А этот ADM485 при отсутствии сигнала на линии тупо подтягивает линию RX в 1. Ну ладно, прошить можно и вытащив ардуинку из платы.

Второй проблемой стало то, что при “автономной” работе rs-485 не работал. То есть питаешь плату через ардуинку – все ок. Питаешь через внешний источник – rs-485 мертв. Проблема осложнялась тем, что у меня все работало, а у заказчика – нет. На одном и том же коде.

Опуская подробности, оказалось, что в мире существует две версии arduino nano. “Дешевая” версия и “дорогая”. Естественно, что “дешевой” версией забиты все магазины Москвы и алиэкспресса. Разница только в одном – чипы мостов usart-usb. На дешевой версии стоит CH340, на дорогой – FTDI.

И этот дешевый CH340 при питании “снаружи” видя, что на входе usb у него ничего нет, тупо подтягивает RX/TX в ноль. А дорогой FTDI – ставит в Z.

Вышли из положения, заиспользовав Software Serial, откусили ножки на самой ардуине и тупо бросили два проводка на плате. То, что у меня заинвертирован RE/DE сигнал, решили ручной правкой кода. Ну не умеет flprog в такое. У них все прямо …

Следующей претензией стало, что “даю 220в, датчик показывает 0, но это ок. Не ок, когда секунд через 40 он переходит в 1, и еще через 40 обратно”. Тут я офигел мрачно.

После разбирательств стало понятно, что ардуинка насколько перегружена кодом от flprog, что тупо не успевает реагировать на полуимпульсы от опторазвязки. А в прерывания оно не умеет. Мы взяли 20МГц чип и тормознули его до 50Гц. Ну почти до 50 …

Не, я и там дунул-плюнул и поправил.

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

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

В качестве главного процессора будет STM32F072. Мощный, умеет программироваться через USB и имеет кучку ножек. А ардуинка … ну дам ей “кроватку”. Пусть используют те, кто привык 🙂

Развлекалочка. Или контроллер сварочного аппарата

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

Так как задача класса “быстро и не гламурно”, то было решено взять из закромов макетную плату, ардуинку и из этого сделать всю положенную обвязку для одной-единственной релюшки.

Для проверки “а пойдет” рисую схему

schema

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

pcb_example

Потом посмотрел, как оно будет выглядеть “в реале”

3dmodel

Вроде ничего, гламурно даже. Правда, в процессе пайки мне стало лень паять столько резисторов и я взял резисторную сборку. Ну и немного поменял расположение компонентов, ибо макетная плата у меня “не ровная”. В общем, получилось вот так

overview

Теперь добавляем щепотку “магии”

#include <EEPROM.h>
int pinAch = 2; //Энкодер вывод А включен на interrupt 0 (D2)
int pinBch = 3; // Энкодер вывод Б - D3

int pinled = 5; // Куда включен первый сетодиодик
int ledcount = 8; // Сколько светодиодиков

int pinButton = A5; // куда включена кнопка "пуск"
int pinRelay = A0; // Куда подключено реле

int impulsTime = 50; // Время включения реле по умолчанию (50 мс)
int relayShift = 2; // Время реакции реле
unsigned long bl; // переменная для хранения состояния "мигающего" светодиода
unsigned long dr; // переменная для антидребезга энкодера
int out=0; // мы бабахнули?

void setup() {                
  Serial.begin(9600);

  pinMode(pinAch, INPUT_PULLUP);  
  pinMode(pinBch, INPUT_PULLUP);

  pinMode(pinled, OUTPUT);
  pinMode(pinled+1, OUTPUT);
  pinMode(pinled+2, OUTPUT);
  pinMode(pinled+3, OUTPUT);
  pinMode(pinled+4, OUTPUT);
  pinMode(pinled+5, OUTPUT);
  pinMode(pinled+6, OUTPUT);
  pinMode(pinled+7, OUTPUT);

  pinMode(13, OUTPUT); // светодиодик, впаянный в ардуинку

  pinMode(pinButton, INPUT_PULLUP);
  pinMode(pinRelay, OUTPUT);

  digitalWrite(pinled, HIGH); // все светодидиоды включить для проверки
  digitalWrite(pinled+1, HIGH);
  digitalWrite(pinled+2, HIGH);
  digitalWrite(pinled+3, HIGH);
  digitalWrite(pinled+4, HIGH);
  digitalWrite(pinled+5, HIGH);
  digitalWrite(pinled+6, HIGH);
  digitalWrite(pinled+7, HIGH);

  digitalWrite(pinRelay,LOW); // для безопасности

  dr=millis();
  bl=millis();
  if(EEPROM.read(0)==1)
    {
      impulsTime=EEPROM.read(1);
    }
  attachInterrupt(0, encoderClick, RISING);
}


void loop() {
  // Зажигаем светодиодики в зависимости от уровня impulsTime
  for(int i=pinled;i<(pinled+ledcount);i++)
  {
    int s=(i-pinled+1)*10; // время импульса для зажигания светодиода
    // то есть для 1 светодиода 10, для 2 - 20 и так далее

    if((impulsTime-s)<0 && (impulsTime-s)>-10) // этот светодиодик промежуточный, поэтому пусть мигает.
    {
      if((millis()-bl)<500)
      {
        digitalWrite(i,HIGH);
      }
      else
      {
        digitalWrite(i,LOW);
        if((millis()-bl)>1000)
        {
          bl=millis();
        }
      }
    }
    else
    {
      if(s<=impulsTime)
      {
        digitalWrite(i,HIGH);
      }
      else
      {
        digitalWrite(i,LOW);
      }
    } 
  }
  
  if(digitalRead(pinButton)==LOW && out==0) // оппа, кнопку нажали и еще не бабахали
    {
      delay(100); // ждем 100мс - вдруг рука дрогнула
      if(digitalRead(pinButton)==LOW) // точно нажата?
        {
          digitalWrite(13,HIGH);
          digitalWrite(pinRelay,HIGH);
          out=1;
          delay(impulsTime+relayShift);
          digitalWrite(13,LOW);
          digitalWrite(pinRelay,LOW);
          out=1;
          EEPROM.write(0, 1); // ставим флаг
          EEPROM.write(1, impulsTime);
        }
    }
    
  if(digitalRead(pinButton)==HIGH && out==1) // Кнопку отпустили после бабханья
    {
      delay(100); // ждем 100мс - вдруг рука дрогнула
      if(digitalRead(pinButton)==HIGH) // до сих пор отпущена?
        {
          out=0; // Хорошо, можно еще раз бабахнуть
        }
    }


//  Serial.println(impulsTime);
//  delay(300);
}


// обработка энкодера
void encoderClick(){
  if((millis()-dr)>10) // От предидущего прерывания прошло больше 10мс? если нет, то это дребезг
  {
    dr=millis();
    int valA = digitalRead(pinAch);
    int valB = digitalRead(pinBch);

    if (valA != valB){
      impulsTime--;
    }
    else{
      impulsTime++;
    }

    if(impulsTime < 5) {
      impulsTime=5;
    }

    if(impulsTime > 100) {
      impulsTime=100;
    }
  Serial.println(impulsTime); 
  }
}

И проверяем, насколько полученное устройство соответствует заявленным мной же спецификациям

Импульс в 20 миллисекунд

20ms

В 50

50ms

и 75, что бы не было ровных чисел.

75ms

Теперь осталось только устроить ресурсные испытания релюшке и узнать, сколько времени она выдержит в таком режиме. Но это уже не так интересно, ибо стоимость релюшки около 100 рублей и перепаять ее – дело буквально пяти минут.

Задача выполнена быстро и с перерасходом ресурсов, как и положено для такого класса задач 🙂

Мега-часы. 1. Просто часы, но с WiFi

Занимаясь на выходных разборкой и сортировкой скопившегося имущества, практически внезапно обнаружил давно забытые детали. Ну как забытые … я конечно помнил, что они где-то есть, но уж очень давно не держал их в руках. Ну и плюс ко мне в руки попали платы на 8051 микроконтроллере, со встроенным приемопередатчиком. Просто так баловаться не интересно, поэтому необходимо совместить приятное с полезным.

Скажу сразу, просто еще одни часы делать мне не интересно – один раз я уже делал аж 2 года назад, до сих пор работают у тещи. Внутри ардуинка и sure 3208

20131113_165232

Что у нас из приятного образуется? Во-первых, более большой и цветной индикатор.

В старых часах был одноцветный (только красный) индикатор, в котором была кучка светодиодов, расположенных 32 рядами по 8 штук. В новых будет аналогичная матрица, только умеющая уже 3 цвета и с матрицей светодиодов 64 на 16. Из-за размера светодиодов (в старой 5мм, в новой 3мм) разница в размерах не очень большая.

Во-вторых, использованная в первых часах микросхема DS1307 обладает небольшим уводом времени. Примерно минуту в неделю. Нет, я там приделал кнопки для коррекции времени, но это же несерьезно в 21м-то веке. Ловить всякие радиосигналы точного времени для меня безсмысленно, ибо лень. А вот подцепиться к WiFi и сбегать за точным временем в интернет к специализированному серверу – вот это да, уже стоит подумать.

И наконец, просто часы – это не интересно. Так как основное их место жительство будет кухня, то надо заставить их что-нибудь делать полезное. Курс доллара там показывать и радио играть. Или еще какую полезную работу выполнять. Список полезной работы будет уточняться по мере того, как я буду откапывать запчасти 🙂

Итак, пока часы. Схемы рисовать не буду, потому что 99% всего будет состоять из соединения проводками.

Для начала берем 2 индикатора sure 3216 и соединяем их приложенными к ним же проводами. Потом берем arduino leonardo и wifi shield. Все родное, итальянское, поэтому соединяем все в кучу. Достаем из пакетика мастеркитовский MP1095 и опять же соединяем проводками с ардуинкой. Все подписанное к подписанному (SDA к SDA, SCL к SCL и так далее).

И наконец соединяем полученный бутерброд из 3216 к ардуинке. На пины А0-А3. В нижеприведенном куске кода любой ардуинщик разберется, что куда подключается.

//Analog In 0 as PF7
//Analog In 1 as PF6
//Analog In 2 as PF5
//Analog In 3 as PF4
ht1632c ledMatrix = ht1632c(&PORTF, 7, 6, 5, 4, GEOM_32x16, 2);
ht1632c::ht1632c(const uint8_t data, const uint8_t wr, const uint8_t clk, const uint8_t cs, const uint8_t geometry, const uint8_t number)

И теперь добавляем магию, сиречь программирование. Я не стал сильно раздумывать, поэтому тупо собрал в одну кучу примеры общения с RTC, с NTP, c WiFi и с индикатором.

#include <ht1632c.h>
#include <Wire.h>
#include <RealTimeClockDS1307.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiUdp.h>

ht1632c dm = ht1632c(&PORTF, 7, 6, 5, 4, GEOM_32x16, 2);

int status = WL_IDLE_STATUS;

char ssid[20] = "multik";  //  your network SSID (name)
char pass[20] = "MyVerySecretPassword";       // your network password
int timeshift=3; // GMT offset

unsigned int localPort = 2390;      // local port to listen for UDP packets

IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP Udp;

char clock[4];
int clockset=0; // does need to set clock from NTP?
int clockprocess=0; // we start asking clock server?

void setup() {  
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  // check for the presence of the shield:

  while (WiFi.status() == WL_NO_SHIELD)
    Serial.println("NOWIFI");

  String fv = WiFi.firmwareVersion();
  if ( fv != "1.1.0" )
    Serial.println("FIRM");

  status = WiFi.begin(ssid, pass); // start connecting

  dm.clear();
  dm.setfont(FONT_8x16B);

  RTC.switchTo24h();
}

void loop()
{
  if(clockset==0)
  {
    //Serial.println("Setting clock");
    if(status==WL_CONNECTED) // ok, we online
    {
      //Serial.println("WiFi con");
      if(clockprocess==0)
      {
        Udp.begin(localPort);
        sendNTPpacket(timeServer); // send an NTP packet to a time server
        clockprocess=1;
      }
      else
      {
        if ( Udp.parsePacket() ) 
        {
          //Serial.println("packet received");
          Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

          //the timestamp starts at byte 40 of the received packet and is four bytes,
          // or two words, long. First, esxtract the two words:

          unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
          unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
          // combine the four bytes (two words) into a long integer
          // this is NTP time (seconds since Jan 1 1900):
          unsigned long secsSince1900 = highWord << 16 | lowWord;
          //Serial.print("Seconds since Jan 1 1900 = " );
          //Serial.println(secsSince1900);

          // now convert NTP time into everyday time:
          // Serial.print("Unix time = ");
          // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
          const unsigned long seventyYears = 2208988800UL;
          // subtract seventy years:
          unsigned long epoch = secsSince1900 - seventyYears;
          // print Unix time:
          //Serial.println(epoch);


          // print the hour, minute and second:
          //Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
          //Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
          int h=(epoch  % 86400L) / 3600;

          RTC.setHours(h+timeshift);
          //Serial.print(h);Serial.print(':');
          //if ( ((epoch % 3600) / 60) < 10 ) {
          // In the first 10 minutes of each hour, we'll want a leading '0'
          //Serial.print('0');
          //}
          int m=(epoch  % 3600) / 60;

          //Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
          RTC.setMinutes(m);
          //Serial.print(m);Serial.print(':');
          //if ( (epoch % 60) < 10 ) {
          // In the first 10 seconds of each minute, we'll want a leading '0'
          //Serial.print('0');
          //}
          int s=epoch % 60;
          //Serial.println(s);
          //Serial.println(epoch % 60); // print the second
          RTC.setSeconds(s);
          RTC.setClock();
          clockset=1;
        }
      }
    }
    //    else
    //    {
    //      Serial.println("WiFi NOT con");
    //    }
  }
  else
  {
    // Serial.println("Clock already setted");
  }
  int x=0;
  int clr=GREEN;
  delay(1000);
  RTC.readClock();
  sprintf(clock, "%02d", RTC.getHours()); // RTC.getMinutes(),RTC.getSeconds());

  dm.setfont(FONT_8x16B);
  dm.putchar(x,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+9,0,clock[1],GREEN,0,BLACK);  

  // first :
  dm.plot(x+18,4,clr);
  dm.plot(x+18,4+1,clr);
  dm.plot(x+18+1,4,clr);
  dm.plot(x+18+1,4+1,clr);
  dm.plot(x+18,4+4,clr);
  dm.plot(x+18,4+1+4,clr);
  dm.plot(x+18+1,4+4,clr);
  dm.plot(x+18+1,4+1+4,clr);

  sprintf(clock, "%02d", RTC.getMinutes()); //,RTC.getSeconds());
  dm.putchar(x+21,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+30,0,clock[1],GREEN,0,BLACK);
  // second :
  dm.plot(x+39,4,clr);
  dm.plot(x+39,4+1,clr);
  dm.plot(x+39+1,4,clr);
  dm.plot(x+39+1,4+1,clr);
  dm.plot(x+39,4+4,clr);
  dm.plot(x+39,4+1+4,clr);
  dm.plot(x+39+1,4+4,clr);
  dm.plot(x+39+1,4+1+4,clr);

  sprintf(clock, "%02d", RTC.getSeconds());
  dm.putchar(x+42,0,clock[0],GREEN,0,BLACK);
  dm.putchar(x+51,0,clock[1],GREEN,0,BLACK);

  dm.sendframe();
  //Serial.println(clock);

}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
  //Serial.println("1");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  //Serial.println("2");
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  //Serial.println("3");

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  //Serial.println("4");
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  //Serial.println("5");
  Udp.endPacket();
  //Serial.println("6");
}

В итоге получилось вот это

015dbb46cc547788abd94e57ccc08f1ec8fa680f70

И видео

Что получилось в итоге? Часы запускаются и пытаются подключиться к WiFi с заранее заданным SSID и паролем. Как только им это удается, то они запрашивают NTP сервер о точном времени и как только получают ответ, обновляют соответствующие значения в RTC. А пока все это крутится, то часы просто берут уже сохраненное ранее время и показывают его.

Просто и дешево. Но для дальнейшей работы необходимо немного расширить функционал и научиться обрабатывать ошибки. К примеру, если WiFi есть, а интернета в нем нет и так далее. Ну и предусмотреть … скажем так, подключение внешних устройств, в качестве которых будет как минимум одна Raspberry Pi плата. Опять же, фотодиод для замера освещенности надо поставить …

В общем, есть что писать в следующем посте 🙂

Карпутер. 1. Предисловие и выбор микроконтроллера

Хотел начать с обычного отсыла к классике (“этим постом я начинаю …”), но потом передумал. Начну обыкновенно.

Итак, после продажи машины и разборок с банком у меня появилось немного свободного времени, которое тут же начали заполнять мысли о не сделанном и недоделанном. А недоделанного у меня оказалось прилично: от дописывания диагностики машины через OBD и заканчивая переводом “из соплей на скрутках” различных доделок для машины.

С одной стороны, можно просто взять и забить на все это, ибо машины уже нет и тестировать особо не на чем. А с другой стороны, машина потом появится и снова вспоминать, чего не доделано и где это недоделанное валяется? Ну и свободное время позволяет более подробно описать тот путь, который может быть кто-то будет повторять.

Для начала я решил составить список того, что у меня недоделано (просто скопирую из форумных постов)

1) Контроллер второго аккумулятора. Штука с вольтметрами, контролирующая подключение второго аккумулятора. Есть подобная у t-max, но она вся в светодиодиках и тупая. Алгоритм грубо говоря такой: смотрим на напряжение на первом аккумуляторе, как превысило 13В (значит завелись), подключаем второй (для зарядки и теде). Если на первом долгое время 12В, значит можно отключить второй — мы на стоянке. Если ниже, то не отключать — мы лебедимся и второй аккумулятор нужен. Ну и кнопочка принудительного подключения второго, если первый на стоянке разрядился по каким-то причинам. У водителя индикатор с вольтами и кнопочка.

2) “Сумматор баков”. У многих машин есть два источника горючки (скажем, бензин и газ), а у некоторых их три (два бака с бензином и газ). Суть — выводить на штатный указатель уровня сумму уровней баков. Например, от нуля до четверти — первый бак, от четверти до половины — второй и от половины до полного — газ. Или еще как. Дополнительно выравнивание нелинейности датчиков уровня, когда то стрелка не двигается, то начинает падать. И специальная фича для патриотоводов — обработка уровня “приборка пикнула” так, что бы она не пищала при каждом торможении-разгоне.

3) “Корректор спидометра”. Меня напрягает 10-15% погрешность спидометра даже на штатных колесах. Меня напрягает нелинейность этого вранья. И стоит сменить размерность колес, как все привычки типа “раз стрелка на 90, значит я еду 85” приходится “перепривычивать”. Дополнительно была мысль сделать вывод “поехали — включи ближний свет/ДХО”, что бы не дергать переключатели руками и не зажигать фары перед заводом зимой.

4) “удлинитель выключателей”. Грубо говоря — управляемые удаленное релюшки. Например поставил свет вокруг, и к нему вместо кучи силовых проводов тащишь один силовой потолще и один управляющий. Или для компрессора сзади или для усилителя … в общем, везде, где надо управлять чем-то мощным и это мощное далеко от водителя.

Поначалу я хотел сделать все это по принципу “одна задача – один контроллер”. Причем навскидку получалось, что с любой из этих задач в одиночку справится практически любой микроконтроллер, доступный к покупке в магазине.

Но после прикидывания схем внезапно выяснилось, что очень многие функции дублируются. Скажем, для многих применений полезно знать напряжение в бортовой сети. И как индикатор для ответа на вопрос “заведен ли двигатель?” и как точку отсчета для некоторых корректировок. И если собрать 5 схем, то что, к аккумулятору тянуть 5 проводов от разных точек? А как настраивать все это безобразие? Городить кучу кнопок и выключателей и обрамлять все это светодиодами?

В общем, постепенно я пришел к идее “один микроконтроллер на всё”. Поначалу я опасался того, что эта электронника нежная, не любит автомобильных условий … Но годовые испытания в режиме “вот счас доделаю” показали, что обычный микроконтроллер “из магазина” спокойно переносит условия в машине и вообще, его поведение очень сильно отличается от моих первоначальных представлений.

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

Attiny, они же tinyAVR контроллеры. Основной их плюс – в распространенности. Они существуют в разных корпусах, с разным числом выводов, есть куча примеров схем и кода. Из минусов можно отметить то, что из-за широкой номенклатуры нет решений “все в одном”. И в итоге получается, что даже для изготовления одной схемы надо покупать отдельный программатор, разбираться с фьюзами и прочим. А это все стоит денег (скажем один более-менее приличный программатор стоит на порядок дороже контроллера) и окупается только на десятках собранных схем. Но для простых задач им нет равных.

AtMega или Adrduino. Те же самые плюсы, что и у “тинек”, но добавляется то, что все решения уже идут готовыми. Подключил плату к компьютеру, настучал в окошке код и он уже выполняется. Главный минус же состоит в том, что все эти решения рассчитаны на “сделал максимально быстро, не считаясь с затратами”. Отсюда совершенно дикие цены как на сами платы, так и на модули расширения (shield, шилд). Скажем, самая дешевая плата в классическом (для ардуинки) формате стоит от тысячи рублей, при этом функционала на ней – кот наплакал. А цены на самые дорогие, с чуть-чуть большим набором портов легко упрыгивают за сотню баксов.

Ну и есть у этих плат общие недостатки. Скажем, отлаживать (пошаговое исполнение программ и прочее) можно только с помощью специальных средств (jtag) и то на старших моделях контроллеров. Нет ЦАП, нет всяких околопромышленных интерфейсов (скажем, узнайте, во что встанет подключить к ардуинке контроллер CAN) и так далее и тому подобное.

Следующими идут миниатюрные компьютеры. Paspberry Pi, OLinuXino и так далее. Основной плюс – внутри них работает привычный многим Linux, код можно писать практически на чем угодно и они подключаются к монитору или обычному телевизору. Так как основное предназначение у них тоже самое, что и у ардуинок, то и минусы те же самые – дикие цены как на сами компьютеры, так и на платы расширения к ним. Плюс все эти компьютеры совершенно (ну по сравнению с вышеперечисленным) не имеют портов ввода вывода и обладают диким энергопотреблением.

К этому описанию можно смело добавить разные платы с Android внутри. Плюсы и минусы абсолютно те же самые. Для планшетов еще пойдет, а для встраиваемой электроники – никак.

И наконец, моя нынешняя вершина – микроконтроллеры stm32. У есть один, но очень большой и жирный минус: в них совершенно невозможно разобраться “с наскоку”. Даже простая задача “помигать светодиодиком” для начинающего легко может вылиться в многочасовое копание в документации, которая к тому же вся на английском. Все остальное – сплошные плюсы: от широчайшего арсенала портов и интерфейсов до дикой производительности. Как дополнение – архитектуру stm32 для своих микроконтроллеров используют несколько производителей, поэтому средств разработки – дикие горы, на любой вкус, любую задачу и на любой кошелек.

Скажем, возьму самую дешевую плату для разработки на stm32 – STM32L100C-DISCO. 256 килобайт памяти для программ (ардуинки и прочие уже рыдают), 16 килобайт ОЗУ, работает на 32Мгц, кроме АЦП (16 каналов!) и ЦАП имеет кучу аппаратных(!) интерфейсов типа I2C и 40 с лишним портов ввода-вывода. По меркам stm – это микроконтроллер начального (нет, начальнейшего) уровня. И вот эта плата, с уже готовым программатором и парочкой светодиодиков стоит в два раза дешевле самой дешевой ардуинки.

А стоит сравнять стоимость, как уже можно получить STM32F3DISCOVERY, где добавят гироскоп с компасом, кучку светодиодиков, поднимут объем ОЗУ до 48килобайт и частоту процессора до 72Мгц. После этого увеличат число портов ввода-ввывода практически в два раза и добавят горку аппаратных интерфейсов.

А за цену какой-нибудь arduino mega можно взять офигенный набор разработчика с LCD экраном и дичайшими возможностями.

Более того, подлая st обеспечила более-менее приличную совместимость снизу-вверх. То есть написав программу для “младшего” микроконтроллера, потом с минимальными переделками ее можно перенести на более “старший” микроконтроллер. Нет ни путаницы с обозначениями портов, ни с их функционалом.

Думаю, после этого вам станет понятным, какие микроконтроллеры я буду использовать в дальнейших разработках 🙂

Если вам стало мало avr …

… или добро пожаловать в ад, ардуинщик!

Любой человек, который использует в своих проекта arduino, рано или поздно сталкивается с тем, что ему перестает хватать возможностей имеющегося микроконтроллера. Те, кому просто не хватает ножек, пытаются мигрировать на mega, остальные ищут шилды с необходимым функционалом или начинают изобретать свои. Но любому дрыгоножеству рано или поздно наступает предел: не хватает! Наиболее малодушные спрыгивают на raspberry или другие подобные платы, а считающие что они сильны духом начинают смотреть на другие платформы.

У меня есть и ардуинки и “малинки” и кучка других плат, поэтому я вполне резонно считал, что в микроконтроллерах я далеко не чайник и знаю, с какой стороны пины дергать … В общем, через друзей ко мне поступило предложение сделать довольно простенькую (на фоне уже сделанных проектов) систему. Но на stm32. В памяти сразу всплыла отладочная плата stm32f3discovery, купленная “на сдачу” при очередной закупке и пылящаяся в закромах. Даже не обсудив условия, соглашаюсь: форсированное обучение новой платформе, да еще и на живом проекте, всегда лучше “ну вот посижу вечерком, побалуюсь проектиками …”. Дополнительным бонусом стало изучение микроконтроллеров, на которых построено управление раздаточной коробкой и климатом в моем УАЗе.

Лирическая завлекалка: (из описания) … плата на базе микроконтроллера STM32F303VCT6 включает в себя встроенный отладчик ST-LINK/V2, акселерометр, гироскоп, электронный компас STMEMS, разъем MiniUSB, светодиоды и пользовательские кнопки. Процессор работает на частоте до 72МГц и предоставляет пользователю 90 каналов ввода-вывода. И все это продается за 600 рублей. В одном из самых дорогих магазинов. Аналогичный функционал (ну почти аналогичный: таких скоростей и объемов памяти нет) на ардуине будет стоить около 7-8 тысяч рублей. А отладчиков с аналогичными возможностями на ардуинках вообще нет как класса.

И тут я совершил первую ошибку, впрочем вполне понятную в моем положении: не посмотрел на поддержку микроконтроллера производителем. Ну в самом деле, ведь они делают микропроцессор и плату на нем, а значит должна быть и документация и примеры … Как же я ошибался … В общем, выбрали микроконтроллер и плату просто по необходимому функционалу. Ей оказалась плата STM32L100C-DISCO.

Пока собирали макет, я, уверенный в своих знаниях, полез по интернету почитать про то, как же все-таки программируются эти микроконтроллеры. И тут я немного припух.

Судите сами. Вот кусочек кода:

RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
GPIOC->CRH |=0x33;
GPIOC->CRH &= ~0xCC;
GPIOC->ODR |= (GPIO_ODR_ODR9 | GPIO_ODR_ODR8);
GPIOC->BSRR=(GPIO_BSRR_BS8|GPIO_BSRR_BS9);

Это всего-лишь простейшее зажигание двух светодиодиков.

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

Второе отступление: в отличии от ардуинки, для stm32 есть много вариантов сред для разработки. В результате любая тема “а что выбрать?” довольно быстро превращается в обычный срач. Лично я выбрал бесплатную CooCox. Не без глюков, но все что надо есть.

Вторая ошибка: я считал, что раз микроконтроллеры принадлежат к одной архитектуре, то и программироваться они должны одинаково. Ну грубо говоря если на SMT32F0 какой-то код включает ножку А0 на чтение, то тот же код должен включать ту же ножку и на STM32L1. Хренушки. Все разное даже для микроконтроллеров одной серии, но различающихся разными буковками на конце. Особенно для дополнительного функционала типа таймеров. В результате примеры из интернета в лучшем случае просто не работали (вы когда-нибудь видели неработающий blink на ардуинке? А тут легко!), а в худшем просто не компилировались.

Я было впал в печаль, но решил что я не первый в таком положении, а значит должен быть какой-то выход. И выход был найден практически сразу: SPL. Фирменная библиотека от STmicrosystems.

Еще одно отступление: для других процессоров у ST есть более навороченные и новые библиотеки. Но вот для L – нету. Смотреть на мою первую ошибку.

Радости моей не было предела. Код сразу преобразился в почти читаемый:

GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOC , &GPIO_InitStructure);
GPIO_SetBits(GPIOC, GPIO_Pin_8|GPIO_Pin_9);

Это уже было прорывом. Хоть и были проблемы со всякими константами, но они были вполне понятными: не стоит ожидать скорости работы ножки в 50МГц от контроллера работающего на 32х.

Описаний, как включать-выключать порты в интернете куча с маленькой тележкой, поэтому я довольно быстро научился мигать светодиодиками и читать состояние переключателей. Уверенность в себе росла как на дрожжах, добавляя радости от обладания таким быстрым, функциональным и дешевым микроконтроллером.

На одном из шагов потребовалось прицепиться к контроллеру по обычному ком-порту (USART по импортному). Уже вполне привычные заклинания, аналогичные вышеприведенным и честно спертым из интернета и … и ничего. В смысле вообще ничего.

Попробовал один пример: пусто. Второй: пусто. Начитался про DMA (в stm32 можно сделать так, что бы внутренности микроконтроллера сами отсылали данные, не требуя постоянного присмотра из программы), написал: пусто. Я начал метаться в разные стороны: то пример попробую, то документацию почитаю. Ничего. Не работает.

Тогда я вспомнил, что я вообще-то работаю с железной штукой, а не с обычной программой. Сажусь логическим пробником на порты микроконтроллера и вижу, что что-то он там все-таки передает. Обрадованно кричу “это проблемы на другой стороне, железка по rs232 не хочет работать!”. Тут же сам себя урезониваю: с другими штуками эта железка вполне себе работает. А с моей нет. Значит проблема все-таки у меня …

Достаю осциллограф и смотрю на ножки. Сигналы есть, всякие нолики-единички я вижу очень отчетливо.

Нахожу переходник для компьютера и подключаю. Запускаю терминалку. С контроллера передаю 00, в компьютер приходит 00. Передаю с контроллера 55, в компьютер приходит два байта. Передаю АА, компьютер получает аж три байта! Офигеваю. Даже мрачно.

Еще одно отступление: в принципе всем необходимым арсеналом настоящий ардуинщик обзаводится еще в процессе, но многие обходятся дешевым китайским мультиметром (а то и вовсе живут без него). Тут же очень желательно заиметь осциллограф, даже самый простенький. Более-менее приличные, подключаемые к компу стоят 7-8 тысяч и очень сильно облегчат отладку устройств. Даже хвалимый многими Keil (он умеет рисовать рисуночки происходящего на портах) в некоторых случаях очень сильно лажает.

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

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

Через некоторое время я обнаружил себя тупо играющимся частотой развертки осциллографа. Рисуночек передачи раздвигается-сжимается … а внизу бегают циферки разрешения. Постепенно в мой мозг прямо-таки просачивается мысль “какого хрена этот пик шириной 20мс? он же должен быть 10!”. Лезу в программу, ставлю вместо положенных 115200 скорость 230400, заливаю и вуаля! Все работает! Попрыгав горным козлом по комнате, я стал разбираться, в чем же причина такого поведения.

Тут вскрылась очередная моя ошибка: я был уверен, что часто используемые функции вылизаны. Но в библиотеках от производителя есть ошибки. И это даже не индусские ошибки, а какие-то пакистанские или уругвайские. И в документации тоже есть ошибки. И там и там они глупейшие. Но зарыты так, что новичок ни в жизнь не догадается.

Кратко говоря, все дело было в неправильном указании частоты процессора. Библиотекописатели были уверены, что он работает на 32МГц, а в реальности он работает на 16МГц. А так как в STM32 все (в смысле вообще все) рассчитывается исходя из этого значения, то и usart в реальности работал на скорости 57600 вместо требуемых мне 115200.

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

Прослезившись, я пошел реализовывать следующий по плану функционал: простой АЦП. Программе надо замерить уровень сигнала на одной из ножек и потом согласно этому уровню начать делать свое черное дело. И тут я опять встрял: программа либо висла, либо в ответ возвращала 0. Уже наученный горьким опытом, я посмотрел реальные данные на ножке. Как легко догадаться, они отличались от 0 …

В общем, я мучался. Я страдал и рыдал. Я даже помощи на форумах попросил. Но судя по обилию аналогичных вопросов, я был не одинок. Я даже чуть не смалодушничал и не решил попробовать бэйсик для stm (есть закрытый проект, который позволяет программировать на бейсике, С или паскале. Со своими библиотеками и ни с чем не совместимый).

И снова я решил поразмыслить. Раз я не один такой, кому не нравится шариться вместо документации по заголовочным файлам в поиске ответов, то наверняка же уже есть решения? В принципе да, есть. Любой производитель софта по stm имеет по своей библиотечке, но как-то они вяло их поддерживают …

И тут я натыкаюсь на библиотеку opencm3. С открытыми исходниками, поддерживает мой процессор, как и кучку других и даже примеры есть. Уже наученный горьким опытом, полез в исходники. Библиотека сделана по принципу SPL, но как-то более изящней что ли. То есть библиотека по сути является набором функций-оберток вокруг трахтибидохов с регистрами. Но с документацией. Пусть скудной, пусть не всегда полной, но весь есть что почитать! На фоне остального бардака это выглядело просто изумительно.

В итоге код практически приблизился по внятности к ардуинному:

rcc_periph_clock_enable(RCC_GPIOС);
gpio_mode_setup(GPIOС, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO8 | GPIO9);
gpio_set(GPIOС, GPIO8|GPIO9);

Попробовал на этой библиотеке сделать АЦП – работает. ЦАП – работает. USART – работает. Даже таймеры удалось сконфигурировать без шаманства!

В общем, пока я категорически рекомендую эту библиотеку. Стало сильно проще работать.

Прочитавший это “ардуинщик” легко может ужаснуться и подумать “нафиг-нафиг. лучше я уж потихоньку, полегоньку на старом добром avr переживу”. Зря. Да, ST еще те редиски и специально делают все так, что бы их продукция была доступна только профессионалам, имеющим возможность потратить кучу времени и денег на изучение платформы. Зато в ответ вы получаете доступ к дешевым и мощным микроконтроллерам, которые могут делать столько интересных вещей, которых на avr попросту нет. И поверьте, даже прочитав описание какого-нибудь контроллера типа “имеет 5 таймеров”, вы никогда не узнаете, что с этими таймерами можно делать, пока не попробуете и не залезете в документацию. Ведь архитектура stm позволяет скрещивать функционал внутри самым причудливым образом. И к примеру, можно сделать так, что бы один и тот же таймер в одной прошивке мигал светодиодиком, а в другой читал показания енкодера моторчика …

Как говорится, продолжение следует.

Часы для любимой тещи.

Жили-были часы. Обычные, стрелочные. И постоянно с ними что-то случалось. То они отставали, то наоборот вперед убегали. И тикали. И ночью их было не видно.

Но у нас же на дворе 21 век! Меняем часы на цифровые. С большими цифрами! Пробег по магазинам показал, что хоть часы с большими цифрами и присутствуют на прилавках, но все поголовно имеют один большой минус: они светятся одной и той же яркостью. В итоге солнечным днем их не видно, а ночью они подобно прожектору слепят всех. Не годится.

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

В общем, было решено взять и сделать часы самому.

Из закромов был извлечен большой индикатор, с 256 красными светодиодами, расположенными в 32 ряда по 8 штук. Ссылки на магазин давать не буду, но ищется по ключевому слову HT1632.

Далее была взята Arduino Leonardo (можно взять абсолютно любую ардуинку, её возможности будут использованы процентов на 5). Почему леонарда? Просто потому что  была под рукой.

В качестве хранилки времени был взят наборчик от мастеркита, рядом нашлось пара кнопок и фотодиод. Как их подключать, можно прочитать в предидущем тут https://blog.kiltum.tech/2013/09/22/Многофункциональный-термометропока/

Внимание! Индикатор подключается абсолютно точно так же, как и тут: http://www.lucadentella.it/en/category/ledmatrix_ht1632c/ Расположенные в сети библиотеки с похожими названиями не работают, так как рассчитаны на другую схему подключения.

Самым сложным оказалось найти подходящий корпус. В результате я просто купил подходящий пластмассовый, долго его резал-штукатурил-красил, но так до гламурного состояния и не довел. Опыта мало, ошибок много …

Все это долгое время валялось у меня на столе разобранным, пока я не взял себя за шкирку и не собрал все в одну кучу. Так как схема не просто простая, а очень простая, то я тупо собрал все навесным монтажем.

20131113_141210

Заодно опробовал и клеевой пистолет: вещь! Особенно для монтажа такого уровня.

Итак, что и как соединено по фотографии. Слева – направо.

Снаружи подходит обычный micro-usb кабель. Лично мне оказалось проще всего  обеспечить схему питанием +5В – у меня валяется много зарядок от телефонов.

Далее две кнопки, посаженные на analog in и фотодиод, выведенный на верхнюю поверхность часов.

С батарейкой – это часы реального времени от мастер-кита.

Далее сама ардуинка и провода, уходящие на индикатор.

Внимание! Индикатор в режиме “все включено на максимум” потребляет примерно 300мА. Встроенный в ардуинку стабилизатор попросту не выдержит. В данной схеме я воспользовался грязным хаком и подцепил питание индикатора на VIN. По схеме он зацеплен прямо на +5В от USB, а там предел 500мА. В общем, не повторяйте этого, если не знаете что делаете.

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

Ну и как же без кода.

#include “Wire.h”
#include “RTClib.h”
#include <avr/pgmspace.h>

// HT1632C PINs

#define DISPLAY_CS 5
#define DISPLAY_WR 6
#define DISPLAY_DATA 7

// HT1632C Commands

#define HT1632C_READ B00000110
#define HT1632C_WRITE B00000101
#define HT1632C_CMD B00000100

#define HT1632_CMD_SYSON 0x01
#define HT1632_CMD_LEDON 0x03
#define HT1632_CMD_PWM 0xA0

// where PIN button is
#define BUTT_H A8
#define BUTT_M A10

int photoRPin = 0; // where photo is
int minLight;
int maxLight;
int lightLevel;
int adjustedLightLevel;
byte display_buffer[32];
long previous_millis;

RTC_DS1307 RTC;

// 7 width 8 – height
PROGMEM char font[] = {
B01111110, // 0
B11111111,
B10000001,
B10000001,
B11111111,
B01111110,

// 1
B01000001,
B11111111,
B11111111,
B00000001,
B00000000,
B00000000,

B01100001,
B11100011, // 2
B10000111,
B10001101,
B11111001,
B01110001,

B01100110, // 3
B11100111,
B10000001,
B10010001,
B11111111,
B01101110,

B11110000,
B11111000, // 4
B00001000,
B00001000,
B11111111,
B11111111,

B11110010,
B11110011, // 5
B10010001,
B10010001,
B10011111,
B10001110,

B01111110,
B11111111, // 6
B10010001,
B10010001,
B11011111,
B01001110,

B10000000, // 7
B10000111,
B10001111,
B10011000,
B11110000,
B11100000,
B01101110,
B11111111, // 8
B10010001,
B10010001,
B11111111,
B01101110,
B01110010,
B11111011, // 9
B10001001,
B10001001,
B11111111,
B01111110,
};

PROGMEM int fontwidth[] = {
6,4,6,6,6,6,6,6,6,6
};

void show_string(String st)
{
ht1632c_clear_display();

int middle=15-1;

show_digit(middle-pgm_read_byte_near(fontwidth+st.charAt(1)-‘0’)-pgm_read_byte_near(fontwidth+st.charAt(0)-‘0’)-1,st.charAt(0)-‘0’);

show_digit(middle-pgm_read_byte_near(fontwidth+st.charAt(1)-‘0’),st.charAt(1)-‘0’);

middle=17+1;

show_digit(middle,st.charAt(2)-‘0’);

show_digit(middle+pgm_read_byte_near(fontwidth+st.charAt(2)-‘0’)+1,st.charAt(3)-‘0’);

}

 

void show_digit(int pos, int digit)
{
for(int i=0;i<pgm_read_byte_near(fontwidth+digit);i++) display_buffer[pos+i]=pgm_read_byte_near(font+digit*6 + i);
}

void show_dots(int on)
{
if(on==1)
{
display_buffer[15]=B01100110;
display_buffer[16]=B01100110;
}
else
{
display_buffer[15]=0;
display_buffer[16]=0;
}
}
// —————————————-
// HT1632C functions
// —————————————-

void ht1632c_clear_display() {
for(int i = 0; i < 32; i++) display_buffer[i] = 0x00;
//ht1632c_display_buffer();
}

void ht1632c_display_buffer() {

digitalWrite(DISPLAY_CS, LOW);
ht1632c_send_bits(HT1632C_WRITE, 1 << 2);
ht1632c_send_bits(0x00, 1 << 6);
for(int i = 0; i < 32; i++) ht1632c_send_bits(display_buffer[i], 1<<7);
digitalWrite(DISPLAY_CS, HIGH);
}

void ht1632c_send_command(byte command) {

digitalWrite(DISPLAY_CS, LOW);
ht1632c_send_bits(HT1632C_CMD, 1 << 2);
ht1632c_send_bits(command, 1 << 7);
ht1632c_send_bits(0, 1);
digitalWrite(DISPLAY_CS, HIGH);
}

void ht1632c_send_bits(byte bits, byte firstbit) {

while(firstbit) {
digitalWrite(DISPLAY_WR, LOW);
if (bits & firstbit) digitalWrite(DISPLAY_DATA, HIGH);
else digitalWrite(DISPLAY_DATA, LOW);
digitalWrite(DISPLAY_WR, HIGH);
firstbit >>= 1;
}
}

void setup()
{
Serial.begin(9600);

pinMode(DISPLAY_CS, OUTPUT);
pinMode(DISPLAY_WR, OUTPUT);
pinMode(DISPLAY_DATA, OUTPUT);

pinMode(BUTT_H,INPUT);
pinMode(BUTT_M,INPUT);
digitalWrite(BUTT_H,HIGH);
digitalWrite(BUTT_M,HIGH);

// enable System oscillator and LED duty cycle generator
ht1632c_send_command(HT1632_CMD_SYSON);
ht1632c_send_command(HT1632_CMD_LEDON);
ht1632c_send_command(HT1632_CMD_PWM+15); // full light!

ht1632c_clear_display();
previous_millis=0;
Wire.begin();
RTC.begin();
RTC.adjust(DateTime(__DATE__, __TIME__));

minLight=200;
maxLight=800;
}

int dig=1000;

String c_to(int dig)
{
String ret(dig);
if(dig<10)
return “0”+ret;
else
return ret;
}

char s;
String txtMsg;

void loop ()
{

while (Serial.available() > 0) {
s=(char)Serial.read();
if (s == ‘n’) {
if(txtMsg.charAt(2)==’:’)
{
DateTime nw = RTC.now();
DateTime nq = DateTime(nw.year(),nw.month(),nw.day(),String(txtMsg.substring(0,2)).toInt(),String(txtMsg.substring(3,5)).toInt(),0);
RTC.adjust(nq);
Serial.println(“Time is changed”);
}
else
{
Serial.println(“To change time, just print current time like ’12:24′”);
}
txtMsg=””;
} else {
txtMsg +=s;
}
}

if(analogRead(BUTT_H)<500)
{
delay(150);
if(analogRead(BUTT_H)<500) // if still pressed
{
DateTime nw = RTC.now();
DateTime nq = DateTime(nw.year(),nw.month(),nw.day(),nw.hour()+1,nw.minute(),nw.second());
RTC.adjust(nq);
previous_millis=millis()-1100;
//delay(100);

}}

if(analogRead(BUTT_M)<500)
{
delay(150);
if(analogRead(BUTT_M)<500) // if still pressed
{
DateTime nw = RTC.now();
DateTime nq = DateTime(nw.year(),nw.month(),nw.day(),nw.hour(),nw.minute()+1,nw.second());
RTC.adjust(nq);
previous_millis=millis()-1100;
//delay(100);

}}

long current_millis = millis();

if(current_millis – previous_millis > 1000) {
previous_millis = current_millis;
// do it

DateTime now = RTC.now();

show_string(String(c_to(now.hour())+c_to(now.minute())));

if((now.second() % 2) == 0)
show_dots(1);
else
show_dots(0);

Serial.print(now.day());
Serial.print(“/”);
Serial.print(now.month(), DEC);
Serial.print(“/”);
Serial.print(now.year(), DEC);
Serial.print(” “);

Serial.print(now.hour(), DEC);
Serial.print(“:”);
Serial.print(now.minute(), DEC);
Serial.print(“:”);
Serial.print(now.second(), DEC);
Serial.print(” l:”);

ht1632c_display_buffer();

// now ajust brightness

lightLevel=analogRead(photoRPin);
// Serial.println(lightLevel);
if(minLight>lightLevel)
{
minLight=lightLevel;
}
if(maxLight<lightLevel)
{
maxLight=lightLevel;
}

//Serial.println(maxLight-minLight);
adjustedLightLevel = map(lightLevel, minLight, maxLight, 0, 15);
Serial.println(adjustedLightLevel);

ht1632c_send_command(HT1632_CMD_PWM+adjustedLightLevel);

}
}

На мой взгляд, ничего ранее не описанного в нем нет (но если что непонятно, то спрашивайте), поэтому я сразу перехожу к готовому результату.

20131113_165232

 

Здесь часы собраны и расположены на испытательном стенде – обычной маркерной доске.

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

Многофункциональный термометропоказометр

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

– Иметь вход для ACC с замка зажигания.
Пока зажигание выключено: мигать светодиодом, как будто это сигналка.
При включении зажигания: первые 10 секунд показывать  показания заранее выбранного датчика температуры. Потом показывать время.
– Иметь вход для CarPC. Плата должна уметь рассказывать о себе, сообщать что к ней подключено и показывать то, что попросят.

– Иметь возможность обрабатывать 4 “ветки” датчиков. Скорость обработки 1 секунда на один датчик на каждой ветке.
– Иметь возможность регулировки яркости.
– Все светящееся должно светить зеленым светом. За синее – расстрел на месте.
– Работать должно на Arduino Micro.

В общем, ничего сложного, все описано в интернете не раз и не два, но по кусочкам.  Моя задача собрать все эти кусочки в один и “смазать” работой с CarPC. Ну и как обещал, параллельно выкладываю пошаговую инструкцию с краткими пояснениями.

Для начала решил выяснить, чем отличается Arduino Micro от CarDuino

– Первый итальянский, второй наш. Без разницы.
– Первый дороже второго почти на 500 рублей. 1200 супротив 700. Плохо, но терпимо.
– У micro по другому сделано общение с компьютером. Драйвера стандартные, при подключении компьютера сброса по умолчанию нет, можно спокойно пользовать RX и TX ножки. Это очень хорошо.
– У micro стандартные значения питания. Его, в отличии от CarDuino, напрямую от бортсети не запитаешь. Мелочь, но потребует как минимум одного лишнего элемента в схеме.
– У micro нет ножек SPK и HV12. Беда, печаль и огорчения. Абсолютно несущественно.

Теперь к индикаторам. Беглый поиск показал, что хотя у меня есть зеленый светодиодик, но вот индикатора с таким светом нет. Гугл дал ссылку на http://pacpac.ru/product/com-11440-7-segment-serial-display-green/ , он же COM-11440. Зелененький, 4 цифры, умеет дофига всего. Беру.

Для определения освещенности нужен фоторезистор. Опять же, у меня есть в закромах, но пойдет любой. Вот ссылка на тот же ПАКПАК http://pacpac.ru/product/sen-09088-mini-photocell/

Про часы и прочее можно прочитать в предыдущем посту.

Итак, собираем схему.

Первыми идут часы. Подключаем GND к GND, VCC к +5V, SDA к D2, SCL к D3.

Проверяем работоспособность

#include "Wire.h"
#include "RTClib.h"

RTC_DS1307 RTC;

void setup () {
Serial.begin(57600);
Wire.begin();
RTC.begin();

if (! RTC.isrunning()) {
Serial.println(“RTC is NOT running!”);
}
//RTC.adjust(DateTime(__DATE__, __TIME__));
}

void loop () {
DateTime now = RTC.now();

Serial.print(now.day());
Serial.print(‘/’);
Serial.print(now.month(), DEC);
Serial.print(‘/’);
Serial.print(now.year(), DEC);
Serial.print(‘ ‘);

Serial.print(now.hour(), DEC);
Serial.print(‘:’);
Serial.print(now.minute(), DEC);
Serial.print(‘:’);
Serial.print(now.second(), DEC);
Serial.println();

delay(1000);
}

В выводе serial monitor должны увидеть строчки с текущим временем.

Теперь подключаем светодиод.  К 5й ножке, затем резистор 1кОм, затем земля. Почему 5я ножка? Просто это первый PWM выход на микре. Проще будет красиво мигать светодиодиком. Проверяем


int led = 5;
int brightness = 0;
int fadeAmount = 10;

void setup() {
pinMode(led, OUTPUT);
}

void loop() {
analogWrite(led, brightness);

brightness = brightness + fadeAmount;

if (brightness == 0 || brightness == 255) {
fadeAmount = -fadeAmount ;
}
delay(30);
}

Светодиодик должен начать “взмаргивать”.

Теперь следующий шаг: фотодиод. Подключаем одной ножкой к А0, второй к +5В. И “опускаем” А0 через резистор в 10кОм на землю. То есть должно получиться А0-фотодиод-5В и А0-резистор-GND.

Проверяем


int photoRPin = 0;
int minLight;
int maxLight;
int lightLevel;
int adjustedLightLevel;

int led = 5;

void setup() {
Serial.begin(9600);
pinMode(led, OUTPUT);

lightLevel=analogRead(photoRPin);
minLight=lightLevel-20;
maxLight=lightLevel;
}

void loop(){
lightLevel=analogRead(photoRPin);
if(minLight>lightLevel){
minLight=lightLevel;
}
if(maxLight

В результате у нас должена получиться супер-пупер адаптивная подсветка из одного светодиода. Чем больше падает света на фотодиод, тем сильнее светится светодиод. При этом яркость подсветки адаптируется и самое яркое свечение светодиода будет при самом ярком свете. Вывод текущего значения подсветки в serial monitor - маленький бонус.

Следующим идет "дисплей". Он умеет общаться по разным протоколам, но я выбираю обычный последовательный, тем более что на micro аппаратный порт свободен.

Соединяем VCC с +5В, GND с GND, а rx индикатора с tx ардуинки. Всё, всего 3 проводка (сравните с предидущим индикатором). Если просто так все включить, то индикатор должен загореться с 0000 на экране. Проверяем остальное.


#include "SoftwareSerial.h"

int ar_tx=1;
int ar_rx=0;

SoftwareSerial sp(ar_rx,ar_tx);

void setup() {
pinMode(ar_tx,OUTPUT);
pinMode(ar_rx,INPUT);
sp.begin(9600);
sp.print("v");
sp.print("8888");
}

void loop() {
// put your main code here, to run repeatedly:
sp.print("1234");
sp.print("w");
char s=16;
sp.write(s);
delay(500);
sp.print("4321");
sp.print("w");
s=0;
sp.write(s);
delay(500);
}

На экране должны меняться 1234 и 4321 с мигающим двоеточием. Более подробное объяснение команд дисплейчика можно найти в его документации. Или тут http://www.arunet.co.uk/tkboyd/ec/ec1led4x7ser.htm

Следующим сделаем вход для сигнала ACC. Так как в машине напряжение может скакать от 9 до 15-16 вольт (в крайних случаях), а ардуинка понимает только 5В, то я не долго думая, сделал обычный делитель из двух сопротивлений: 1кОм и 4,7кОм.

Что бы не мучаться, воспользуемся сервисом http://www.bezkz.su/index/delitel/0-9. R1=4700, R2=1000, U1 - то, что "входит", U2 - то, что выходит.

Сопротивление 1кОм включаем между GND и A1, а сопротивление 4,7кОм, между А1 и "+" измеряемого. "-" измеряемого соединяем с GND.

Теперь по появлению чего-либо на A1 можно судить, включено ли зажигание. Но это как-то не функционально. В общем, надо превратить этот вход еще и в вольметр. Судя по вышеприведенному сервису, с данными номиналами можно будет измерять напряжение в диапазоне от 0 до 28 вольт. Для машины более чем достаточно. Но тут есть одна большая проблема: резисторы вообще-то не идеального номинала и поэтому ожидать от полученного вольтметра точности прямо с нуля не стоит.

Берем вот такой вот маленький скетч

void setup()
{
Serial.begin(115200);
}
void loop()
{

Serial.println(analogRead(1));
delay(100);
}

Он просто выводит значения измеренного с порта А1. Значения могут колебаться от 0 (ноль) до 1023 (5В). Я подключился к лабораторному блоку питания и при напряжении 12В оно мне выдало 430, 9В - 320, 6В - 215, 5В - 176. То есть теоретическая точность (и шаг измерения) данного показометра будет ...

12/430 = 0.027
9/320 = 0.028
6/215 = 0.027
5/176 = 0.028

Ну, цельных 3 сотых вольта. Можно в принципе до 15 тысячных догнать, (снизив диапазон измеряемого напряжения) но зачем? Нам и десятых хватит за глаза.

Модифицируем программу вольметра


void setup()
{
Serial.begin(115200);
}
void loop()
{

Serial.println(analogRead(1)/35.8); // 430/12=
delay(100);
}

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


#include "SoftwareSerial.h"

int ar_tx=1;
int ar_rx=0;

SoftwareSerial sp(ar_rx,ar_tx);

void setup() {
Serial.begin(115200);
pinMode(ar_tx,OUTPUT);
pinMode(ar_rx,INPUT);
sp.begin(9600);
sp.print("v");
sp.print("8888");
sp.print("w");
char t=2;
sp.print(t);
}

void loop() {
int napr=int(analogRead(1)/35.8*100); // 430/12=
Serial.println(napr);
if(napr<1000) { sp.print(" "); } sp.print(napr); delay(100); }

При этом уже полученная штука может мерять напряжения еще в куче мест, ибо ножек свободных у нас дофига, даже если учесть, что нам надо куда-то подключить еще 4 ветки для датчиков температур. Ну разве это не прекрасно?

Тем, кто будет воплощать все это "в металл", еще раз следует учесть, что показания этого "вольметра" будут очень сильно плавать в зависимости от величины сопротивления используемых резисторов, проводов и соединений. Поэтому готовое изделие надо будет откалиборовать, просто подогнав выдаваемые им значения под измеренное в точке подключения каким-либо сторонним вольтметром. При этом учитывайте, что обычно вольтметры в бортовых компьютерах врут примерно на 0.3-0.5 вольт (так было на всех "ощупанных" мной машинах).

И наконец, почти последний шаг: измерение температуры. Я использую в своих проектах DS18B20. Это совершенно шикарный цифровой датчик, который с точностью 0,5 градуса измеряет свою температуру.

У этого датчика есть две схемы подключения: нормальная и с паразитным питанием. Если коротко, то нормальная использует 3 провода до датчика, а с паразитным питанием 2. Но (как обычно, без НО не обойтись) схема с паразитным питанием нормально работает только на короткие расстояния и в обычном, уличном, диапазоне температур. При увеличении расстояния от датчика до контроллера начинаются "глюки" и чем дальше, тем больше. И более того, ни одна схема с паразитным питанием и расстоянием по проводу до датчика порядка 5м (обычный домашний термометр) не выживала более года-полутора: попросту дохли "контроллеры". А трехпроводные живут. В общем, решать вам.

С картинками разницу между схемами можно прочитать тут http://openenergymonitor.org/emon/buildingblocks/DS18B20-temperature-sensing

Я лишь сопру картинку с нормальным питанием

normal power conection diagram

И схему подключения

temp sensors connection diagram 3 wire

Как видно, датчики подключаются гирляндой, один за одним, к одним и тем же проводам. Максимальное количество датчиков на одной гирлянде по-моему 127. Или 64. В общем, дофига. Каждый датчик имеет свой уникальный адрес, поэтому вероятность, что они "перепутаются", нет. Правда, совершенно не факт, что первый физически по проводу будет первым и по адресу ...

Для сборки датчиков надо следующее: 3 провода (желательно разных цветов, многожильных), сам датчик и 2 разных термоусадочных трубочки. Одна чуть больше диаметром, чем сам провод, другая на 4,3мм.

IMG_0107

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

Для начала подключаем один датчик. VCC +5В, GND-GND, DQ-D6 и DQ-R4K7-+5В


#include "OneWire.h"
#include "DallasTemperature.h"

// Data wire is plugged into pin 6 on the Arduino
#define ONE_WIRE_BUS 6

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

void setup(void)
{
// start serial port
Serial.begin(9600);

// Start up the library
sensors.begin(); // IC Default 9 bit.
}

void loop(void)
{
// call sensors.requestTemperatures() to issue a global temperature
// request to all devices on the bus
Serial.print("Requesting temperatures...");
sensors.requestTemperatures(); // Send the command to get temperatures
Serial.println("DONE");

Serial.print("Temperature for Device 1 is: ");
Serial.println(sensors.getTempCByIndex(0));

}

И в Serial Monitor вы увидите похожее на следующее

Requesting temperatures...DONE
Temperature for Device 1 is: 21.69

Зажмите сенсор между пальцами и увидите, как температура начнет медленно расти. Отпустите - точно так же медленно падать. Медленно - потому что у датчика довольно большая тепловая инерция, он попросту не успевает остывать или нагреваться так быстро. Но для наших целей более чем достаточно.

Ну и совершенно аналогично поступаем, подключая датчики в D7, D8 и D9.

Да, опять не могу удержаться

#include "SoftwareSerial.h"
#include "OneWire.h"
#include "DallasTemperature.h"

// Data wire is plugged into pin 6 on the Arduino
#define ONE_WIRE_BUS 6

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

int ar_tx=1;
int ar_rx=0;

SoftwareSerial sp(ar_rx,ar_tx);

void setup() {
Serial.begin(115200);
pinMode(ar_tx,OUTPUT);
pinMode(ar_rx,INPUT);
sp.begin(9600);
sp.print("v");
sp.print("8888");
sp.print("w");
char t=2;
sp.print(t);
sensors.begin(); // IC Default 9 bit.
}

void loop() {
sensors.requestTemperatures();
int temp=sensors.getTempCByIndex(0)*100;
Serial.println(temp);
sp.print(temp);
}

И у нас готовый термометр с цифровой индикацией температуры.

IMG_0134

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

Часовой показометр

Итак, хватит разговоров, надо сделать что-либо полезное и приятное. А начнем мы с часов, совмещенных с показометром чего-нибудь. Чего именно и конкретно – пока я не определился.

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

Проверка для перехода к следующему шагу – стандартная “мигалка” компилируется, заливается в ардуинку и мигает светодиодиком, как положено.

Теперь подсоединяем индикатор к ардуинке. Ножку индикатора VCC цепляем к выводу +3.3V ардуинки. Не стоит занимать 0 и 1 ножку (там работа с компьютером) и 13й – там светодиодик.

IMG_0061

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

int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;
int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

Затем я делаю определение, в каком режиме должны работать порты ардуинки (у нас все работают на вывод)

pinMode(pinA, OUTPUT); //A
pinMode(pinB, OUTPUT); //B
pinMode(pinC, OUTPUT); //C
pinMode(pinD, OUTPUT); //D
pinMode(pinE, OUTPUT); //E
pinMode(pinF, OUTPUT); //F
pinMode(pinG, OUTPUT); // G
pinMode(pinDP, OUTPUT); // DP
pinMode(pinPP, OUTPUT); // PP

pinMode(pinDIG1, OUTPUT); // DIG 1
pinMode(pinDIG2, OUTPUT);
pinMode(pinDIG3, OUTPUT);
pinMode(pinDIG4, OUTPUT); // DIG 4

И переключаю эти порты в соответствующее состояние. По нашей схеме, что бы светодиод загорелся, надо порт включить в режим LOW.
Что бы погас – в HIGH.

Ну и что бы добавить “оживляжа”, зажгем первую и третью цифру

digitalWrite(pinDIG1,LOW);
digitalWrite(pinDIG2,HIGH);
digitalWrite(pinDIG3,LOW);
digitalWrite(pinDIG4,HIGH);

… восьмерками
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
digitalWrite(pinDP,HIGH);
digitalWrite(pinPP,HIGH);

и начнем включать/выключать точки с интервалом в одну секунду
digitalWrite(pinPP,LOW);
delay(1000);
digitalWrite(pinPP,HIGH);
delay(1000);

В итоге в ардуинке должен залиться вот такой вот скетч
int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;

int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

void setup() {
Serial.begin(9600);
pinMode(pinA, OUTPUT); //A
pinMode(pinB, OUTPUT); //B
pinMode(pinC, OUTPUT); //C
pinMode(pinD, OUTPUT); //D
pinMode(pinE, OUTPUT); //E
pinMode(pinF, OUTPUT); //F
pinMode(pinG, OUTPUT); // G
pinMode(pinDP, OUTPUT); // DP
pinMode(pinPP, OUTPUT); // PP

pinMode(pinDIG1, OUTPUT); // DIG 1
pinMode(pinDIG2, OUTPUT);
pinMode(pinDIG3, OUTPUT);
pinMode(pinDIG4, OUTPUT); // DIG 4

digitalWrite(pinDIG1,LOW);
digitalWrite(pinDIG2,HIGH);
digitalWrite(pinDIG3,LOW);
digitalWrite(pinDIG4,HIGH);

digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
digitalWrite(pinDP,HIGH);
digitalWrite(pinPP,HIGH);
}

// the loop routine runs over and over again forever:
void loop() {

digitalWrite(pinPP,LOW);
delay(1000);
digitalWrite(pinPP,HIGH);
delay(1000);
}


И этот скетч должен произвести такой результат:

Если получилось, можно радостно ухмыльнуться и перейти к следующему шагу – сделаем другие циферки.

Для начала добавим функцию, которая будет рисовать нам циферки
void showDigit(int digit)
{
switch(digit) {
case 0:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,HIGH);
break;
case 1:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 2:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 3:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 4:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 5:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 6:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 7:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 8:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 9:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
}
}

Суть её простая: просто зажигаем или гасим нужные сегменты в зависимости от того, какую циферку нам надо нарисовать.

И просто перебираем циферки по порядку:

void loop() {
showDigit(count);
count++;
if(count>9)
{
count=0;
}
digitalWrite(pinPP,LOW);
delay(500);
digitalWrite(pinPP,HIGH);
delay(500);
}

Все просто: зажигаем циферку, затем увеличиваем ее и если она превышает 9, сбрасываем на 0. И затем с меньшим интервалом мигаем. В результате должен получиться вот такой вот скетч
int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;

int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

void showDigit(int digit)
{
switch(digit) {
case 0:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,HIGH);
break;
case 1:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 2:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 3:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 4:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 5:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 6:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 7:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 8:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 9:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
}
}

void setup() {
Serial.begin(9600);
pinMode(pinA, OUTPUT); //A
pinMode(pinB, OUTPUT); //B
pinMode(pinC, OUTPUT); //C
pinMode(pinD, OUTPUT); //D
pinMode(pinE, OUTPUT); //E
pinMode(pinF, OUTPUT); //F
pinMode(pinG, OUTPUT); // G
pinMode(pinDP, OUTPUT); // DP
pinMode(pinPP, OUTPUT); // PP

pinMode(pinDIG1, OUTPUT); // DIG 1
pinMode(pinDIG2, OUTPUT);
pinMode(pinDIG3, OUTPUT);
pinMode(pinDIG4, OUTPUT); // DIG 4

digitalWrite(pinDIG1,LOW);
digitalWrite(pinDIG2,HIGH);
digitalWrite(pinDIG3,LOW);
digitalWrite(pinDIG4,HIGH);

digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
digitalWrite(pinDP,HIGH);
digitalWrite(pinPP,HIGH);
}

int count=0;

// the loop routine runs over and over again forever:
void loop() {
showDigit(count);
count++;
if(count>9)
{
count=0;
}
digitalWrite(pinPP,LOW);
delay(500);
digitalWrite(pinPP,HIGH);
delay(500);
}

С вот таким вот результатом

Ура? Ура конечно. Следующим шагом надо вывести какое-нибудь число. Я взял 1234.

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

Добавляем функцию, которая будет разбивать число на циферки и показывать их по порядку
void showNumber(int num)
{
int divide=0;
for(int c=1;c<5;c++) { switch(c) { case 1: digitalWrite(pinDIG1,LOW); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=1000; break; case 2: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,LOW); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=100; break; case 3: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,LOW); digitalWrite(pinDIG4,HIGH); divide=10; break; case 4: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,LOW); divide=1; break; } // switch String s(int(num/divide)); char c=s.charAt(s.length()-1); showDigit(c-'0'); delay(100); } }

Самым сложным тут является кусок кода
String s(int(num/divide));
char c=s.charAt(s.length()-1);
showDigit(c-'0');

Вся его функция - "выкусить" нужную цифру из числа и затем показать.

скажем, нам нужна цифра 2 из 1234.

int(num/divide) - это "взять целую часть из деления num на divide". В нашем случае 1234/100=12

String(s - это "превратить данное число в строку"

s.length() - узнать длину строки в символах

s.charAt - взять Нный символ из строки

В итоге s.charAt(s.length()-1) значит "взять последний символ из строки". -1 нужен из-за того, что ардуинка считает, что 1й символ имеет "адрес" 0

c-'0' - превращаем символ назад в число. Отнеситесь к этому как к магии, иначе мне надо будет рассказывать вам про то, что такое ASCII

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

void loop() {
showNumber(1234);
}

Как обычно, скетч целиком

int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;

int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

void showDigit(int digit)
{
switch(digit) {
case 0:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,HIGH);
break;
case 1:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 2:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 3:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 4:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 5:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 6:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 7:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 8:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 9:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
}
}

void setup() {
Serial.begin(9600);
pinMode(pinA, OUTPUT); //A
pinMode(pinB, OUTPUT); //B
pinMode(pinC, OUTPUT); //C
pinMode(pinD, OUTPUT); //D
pinMode(pinE, OUTPUT); //E
pinMode(pinF, OUTPUT); //F
pinMode(pinG, OUTPUT); // G
pinMode(pinDP, OUTPUT); // DP
pinMode(pinPP, OUTPUT); // PP

pinMode(pinDIG1, OUTPUT); // DIG 1
pinMode(pinDIG2, OUTPUT);
pinMode(pinDIG3, OUTPUT);
pinMode(pinDIG4, OUTPUT); // DIG 4

digitalWrite(pinDIG1,HIGH);
digitalWrite(pinDIG2,HIGH);
digitalWrite(pinDIG3,HIGH);
digitalWrite(pinDIG4,HIGH);

digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
digitalWrite(pinDP,HIGH);
digitalWrite(pinPP,HIGH);
}

void showNumber(int num)
{
int divide=0;
for(int c=1;c<5;c++) { switch(c) { case 1: digitalWrite(pinDIG1,LOW); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=1000; break; case 2: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,LOW); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=100; break; case 3: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,LOW); digitalWrite(pinDIG4,HIGH); divide=10; break; case 4: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,LOW); divide=1; break; } // switch String s(int(num/divide)); char c=s.charAt(s.length()-1); showDigit(c-'0'); delay(100); } } // the loop routine runs over and over again forever: void loop() { showNumber(1234); }

Превращается в ...

А теперь уменьшаем задержку, меняя delay(100) на delay(1) и получаем ...

... циферку! Такую, какую я попросил! Очередное ура!

Но если вы приглядитесь к результату ...

IMG_0070

Какая-то фигня, не правда ли? Почему-то вокруг циферок светятся лишние сегменты. Хоть и тускло, но некрасиво же. Почему так?

Дело в скорости работы ардуинки. Скажем, откуда появились лишние сегменты у цифры 1?

Если посмотрите на логику работы скетча, у нас получится следующее

(много пропущено)
1. Включить 4й индикатор
2. Показать цифру 4
3. Включить 1й индикатор
4. Показать цифру 1
(опять пропускаем)

И вот обратите внимание, во время между шагами 3 и 4 на индикаторе горит 4! Да, это время невелико, но хватает, что бы мы заметили это.

Вывод? Надо между шагами 2 и 3 выключить показ циферок.

Добавляем вывод спец-цифры "10"

case 10:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;

Она просто будет гасить все сегменты. И вставим ее вызов самое начало ShowNumber

for(int c=1;c<5;c++) { showDigit(10);

IMG_0073

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

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

GND к GND,
VCC к +5V (НЕ к 3.3!)
SDA к A4
SCL к A5

И снова включаем, что бы удостовериться, что ничего не поломали и не оторвали.

IMG_0077

Теперь для проверки уже блока часов скачиваем с https://github.com/adafruit/RTClib библиотечку RTC, добавляем ее в проект и заливаем вот такой вот скетч:

#include
#include "RTClib.h"

RTC_DS1307 RTC;

void setup () {
Serial.begin(57600);
Wire.begin();
RTC.begin();

if (! RTC.isrunning()) {
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled
//RTC.adjust(DateTime(__DATE__, __TIME__));
}

}

void loop () {
DateTime now = RTC.now();

Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(' ');
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();

Serial.print(" since 1970 = ");
Serial.print(now.unixtime());
Serial.print("s = ");
Serial.print(now.unixtime() / 86400L);
Serial.println("d");

// calculate a date which is 7 days and 30 seconds into the future
DateTime future (now.unixtime() + 7 * 86400L + 30);

Serial.print(" now + 7d + 30s: ");
Serial.print(future.year(), DEC);
Serial.print('/');
Serial.print(future.month(), DEC);
Serial.print('/');
Serial.print(future.day(), DEC);
Serial.print(' ');
Serial.print(future.hour(), DEC);
Serial.print(':');
Serial.print(future.minute(), DEC);
Serial.print(':');
Serial.print(future.second(), DEC);
Serial.println();

Serial.println();
delay(3000);
}

Если все в порядке, то в выводе Serial Monitor вы должны увидеть ужас, похожий на

2000/61/120 0:81:4
since 1970 = 1023758464s = 11849d
now + 7d + 30s: 2002/6/18 1:21:34

Теперь меняем кусочек скетча в начале на

if (! RTC.isrunning()) {
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled

}
RTC.adjust(DateTime(__DATE__, __TIME__));
}

(если кто не понял, строчку одну перенес) И снова заливаем.

Теперь в выводе должно появиться реальное время. Ибо компьютер подставил их вместо __DATE__ и __TIME__, а функция
RTC.ajust "поставила часы" как надо.

Теперь выдыхаем и модифицируем скетч вот так:

#include

#include

int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;

int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

RTC_DS1307 RTC;

void showDigit(int digit)
{
switch(digit) {
case 0:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,HIGH);
break;
case 1:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 2:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 3:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 4:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 5:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 6:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 7:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 8:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 9:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 10:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
}
}

void showNumber(int num)
{
int divide=0;
float z;
for(int c=1;c<5;c++) { showDigit(10); switch(c) { case 1: digitalWrite(pinDIG1,LOW); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=1000; break; case 2: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,LOW); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=100; break; case 3: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,LOW); digitalWrite(pinDIG4,HIGH); divide=10; break; case 4: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,LOW); divide=1; break; } // switch String s(int(num/divide)); char b=s.charAt(s.length()-1); showDigit(b-'0'); delay(5); } } void setup() { Serial.begin(115200); Wire.begin(); RTC.begin(); pinMode(pinA, OUTPUT); //A pinMode(pinB, OUTPUT); //B pinMode(pinC, OUTPUT); //C pinMode(pinD, OUTPUT); //D pinMode(pinE, OUTPUT); //E pinMode(pinF, OUTPUT); //F pinMode(pinG, OUTPUT); // G pinMode(pinDP, OUTPUT); // DP pinMode(pinPP, OUTPUT); // PP pinMode(pinDIG1, OUTPUT); // DIG 1 pinMode(pinDIG2, OUTPUT); pinMode(pinDIG3, OUTPUT); pinMode(pinDIG4, OUTPUT); // DIG 4 digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); digitalWrite(pinA,LOW); digitalWrite(pinB,LOW); digitalWrite(pinC,LOW); digitalWrite(pinD,LOW); digitalWrite(pinE,LOW); digitalWrite(pinF,LOW); digitalWrite(pinG,LOW); digitalWrite(pinDP,HIGH); digitalWrite(pinPP,HIGH); } long previousMillis = 0; long interval = 1000; DateTime now; // the loop routine runs over and over again forever: void loop() { unsigned long currentMillis = millis(); if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
now = RTC.now();
if((now.second() % 2) == 0)
{
digitalWrite(pinPP,LOW);
}
else
{
digitalWrite(pinPP,HIGH);
}
}

showNumber(now.hour()*100+now.minute());

}

Кака работает код, в принципе понятно из него самого. Но все-таки прокомментирую.

Система просто смотрит, не прошел ли уже заданный интервал и если прошел, то считывает время из часов точного времени и мигает двоеточием.

Почему я просто не поставил посередине что-нибудь типа delay(995)? Ответ опять же в том, что разные ардуинки имеют разную скорость работы. И код, который выполняется между delay,
выполняется за разный промежуток времени. А значит, в реальности между delay проходит разное количество времени. И чего, мне после каждого изменения кода или логики подбирать
задержку? Бессмысленное занятие при наличии под боком нормальных часов.

Код "now.second() % 2" означает "дробная часть при делении на два". Попросту говоря, я зажигаю светодиод каждую четную секунду (14/2=7.0), а гашу - нечетную (15/2=7.5).

Ну и now.hour()*100+now.minute() из часов и минут делает большое число.

Запускаем!

Ура! Работает! Теперь у нас есть часики. Свои, персональные! Можем танцевать и бросать в воздух чепчики!

Но мне же надо еще и показометр ...

Добавляем в loop

while (Serial.available() > 0) {
s=(char)Serial.read();
if (s == 'n') {
char ca[5];
txtMsg.toCharArray(ca, 5);
number_to_show = atoi(ca);
time_to_show=2;
digitalWrite(pinPP,HIGH); // we do to need dots
txtMsg = "";
} else {
txtMsg +=s;
}
}

if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
if(time_to_show > 0)
{
time_to_show--;
}
else
{
now = RTC.now();

Суть кода простая: если компьютер послал что-либо в ардуинку, то тупо собираем все, пока не попадется спецсимвол n - это Enter или Return.
Затем превращаем полученное в число и на две секунды показываем вместо часов. Думаю, что если вы одолели предидущее, то этот будет просто понять.

Вот он целиком (для тех, кто не просто копипастит, внутри сюрприз):

#include

#include

int pinA=2;
int pinB=3;
int pinC=4;
int pinD=5;
int pinE=6;
int pinF=7;
int pinG=8;
int pinDP=9;
int pinPP=10;

int pinDIG1=11;
int pinDIG2=12;
int pinDIG3=A0;
int pinDIG4=A1;

RTC_DS1307 RTC;

void showDigit(int digit)
{
switch(digit) {
case 0:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,HIGH);
break;
case 1:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 2:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 3:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,LOW);
break;
case 4:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 5:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 6:
digitalWrite(pinA,LOW);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 7:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
case 8:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,LOW);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 9:
digitalWrite(pinA,LOW);
digitalWrite(pinB,LOW);
digitalWrite(pinC,LOW);
digitalWrite(pinD,LOW);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,LOW);
digitalWrite(pinG,LOW);
break;
case 10:
digitalWrite(pinA,HIGH);
digitalWrite(pinB,HIGH);
digitalWrite(pinC,HIGH);
digitalWrite(pinD,HIGH);
digitalWrite(pinE,HIGH);
digitalWrite(pinF,HIGH);
digitalWrite(pinG,HIGH);
break;
}
}

void showNumber(int num)
{
int divide=0;
float z;
for(int c=1;c<5;c++) { showDigit(10); switch(c) { case 1: digitalWrite(pinDIG1,LOW); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=1000; break; case 2: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,LOW); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); divide=100; break; case 3: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,LOW); digitalWrite(pinDIG4,HIGH); divide=10; break; case 4: digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,LOW); divide=1; break; } // switch String s(int(num/divide)); char b=s.charAt(s.length()-1); showDigit(b-'0'); delay(5); } } void setup() { Serial.begin(115200); Wire.begin(); RTC.begin(); pinMode(pinA, OUTPUT); //A pinMode(pinB, OUTPUT); //B pinMode(pinC, OUTPUT); //C pinMode(pinD, OUTPUT); //D pinMode(pinE, OUTPUT); //E pinMode(pinF, OUTPUT); //F pinMode(pinG, OUTPUT); // G pinMode(pinDP, OUTPUT); // DP pinMode(pinPP, OUTPUT); // PP pinMode(pinDIG1, OUTPUT); // DIG 1 pinMode(pinDIG2, OUTPUT); pinMode(pinDIG3, OUTPUT); pinMode(pinDIG4, OUTPUT); // DIG 4 digitalWrite(pinDIG1,HIGH); digitalWrite(pinDIG2,HIGH); digitalWrite(pinDIG3,HIGH); digitalWrite(pinDIG4,HIGH); digitalWrite(pinA,LOW); digitalWrite(pinB,LOW); digitalWrite(pinC,LOW); digitalWrite(pinD,LOW); digitalWrite(pinE,LOW); digitalWrite(pinF,LOW); digitalWrite(pinG,LOW); digitalWrite(pinDP,HIGH); digitalWrite(pinPP,HIGH); } long previousMillis = 0; long interval = 1000; DateTime now; String txtMsg = ""; char s; int number_to_show=0; int time_to_show=0; // the loop routine runs over and over again forever: void loop() { unsigned long currentMillis = millis(); while (Serial.available() > 0) {
s=(char)Serial.read();
if (s == 'n') {
//if(txtMsg=="HIGH") { digitalWrite(13, HIGH); }
//if(txtMsg=="LOW") { digitalWrite(13, LOW); }
//Serial.println(txtMsg.charAt(0));

char ca[5];
txtMsg.toCharArray(ca, 5);
number_to_show = atoi(ca);
time_to_show=2;
digitalWrite(pinPP,HIGH); // we do to need dots
txtMsg = "";
} else {
txtMsg +=s;
}
}

if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
if(time_to_show > 0)
{
time_to_show--;
}
else
{
now = RTC.now();
if((now.second() % 2) == 0)
{
digitalWrite(pinPP,LOW);
}
else
{
digitalWrite(pinPP,HIGH);
}
number_to_show=now.hour()*100+now.minute();
}
}

showNumber(number_to_show);

}

И в реальности:

Все закадровые звуки принадлежат игрушке на айпадике, в который играет сын.

Ну вот и все. Первый этап завершен. Часики тикают и показывают то, что в них плюнет компьютер.

Но это еще не финал, поэтому паять проводки и собирать полученное в одну кучку я бы не спешил. Хотя дело ваше 🙂

Что и для чего надо взять.

Итак, основа получения результата – это отсутствие заморочек.

Всё, что я использую, можно легко купить в любом магазине, занимающимся электроникой. ЧипИДип, Терраэлектроника, Вольтмастер, Амперка, Электронщик … в общем, поищите, наверняка рядом с вами есть фирмы, которые обеспечивают радиолюбителей полезными штуками.

Повторюсь, для повторения не надо будет даже паяльника. Но лучше с ним.

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

Wisher_WB-102_J_big

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

Главная цель макетной платы это позволить быстро и более-менее надежно собрать схему для проверки работоспособности. И так же быстро ее поправить или разобрать.

Вторым пунктом идет сам микроконтроллер. Я выбрал наиболее широко распространенный: arduino. Этих контроллеров и их клонов в магазинах как грязи по самым разным ценам. Для наших целей подойдет абсолютно любой.

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

Итак, в качестве микроконтроллера я взял CarDuino. Он же Arduino Nano или Nano Duo. Процессор ATmega328, документации очень мало, но есть русскоязычный форум. Использовать всякие “фишки carduino”, типа порта HV12 или SPK не буду (нет документации, не совместимы с другими ардуинками).

carduinov7_mid1

Для показометра я взял набор MP1091 от МастерКит. Четыре семисегментных индикатора, объединенных в один блок. Из документации только одна схема и черезжопу написанная  библиотека.

mp1091

Ну и венцом всего будет MP1095, в который спрятали уже готовую схему часов с батарейкой.

DOC000867115

Отмечу, что хотя везде написано, что это наборы для Arduino, они прекрасно подойдут для других микроконтроллеров и компов.

Думаю, что обычный USB-miniUSB кабель, как и компьютер, вы найдете уж как-нибудь без моих подсказок.

Примерная стоимость набора в самом дорогом магазине Москвы:

Макетная плата с набором проводов – 1400 рублей.

CarDuino – 1180 рублей.

Индикатор MP1091 – 450 рублей

Часы MP1095 – 440 рублей

Итого 3500 рублей, из которых половину можно сэкономить, просто поспрашивав на форумах: многие отдают “макетки” и остальное за пиво-соки.

В дальнейшем в качестве “центрального компьютера” я буду использовать Raspberry Pi. Он точно так же доступен, обладает приличными характеристиками и при желании на нем можно собрать что угодно – от новогодней гирлянды до мультимедийного центра. Стоит ли вам его покупать сейчас, решать вам, но “часики/показометр” можно повторить за пару-тройку вечеров под пиво.

pi1l

 

Опять же, к нему (хотя бы на начальном этапе) надо USB-клавиатуру, мышку, монитор с HDMI или RCA входом и еще один USB-miniUSB шнурок.

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

Микроконтроллеры или компьютеры?

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

Собрать стандартную схему “музыку играем, навигатор показываем, в интернет ходим” достаточно легко. Обычный CarPC обходится примерно в 15-20 тысяч рублей и по своему функционалу не напрягаясь переплевывает то, что производители машин предлагают автолюбителям.

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

С другой стороны, микроконтроллеры совершенно не имеют проблем с кнопками, индикаторами и датчиками. А на морозы большинству вообще чихать. Но одновременно они имеют дикие проблемы с высокоуровневым железом типа интернет-свистков или дисплеев. И почему-то обладают налетом этакой элитарности, дескать что бы что-либо на них сделать, надо очень сильно дружить в паяльником и  обладать как минимум кандидатской в области электроники.

Вот, к примеру, моя первая хотелка: обычные маленькие часики в машину. Давайте прикинем, как можно их сделать.

Берем 4 индикатора для циферок и двоеточие для мигания секунд. Берем самое простое, а значит у каждого индикатора 7 ножек (по числу светящихся сегментов), итого 28 ножек. Еще по ножке для “двоеточия” и питания. Итого 30 ножек. Ладно, я знаю как упростить схему до 13 ножек (знание будущего, так сказать).

И? Куда такое количество ножек втыкать в компьютер? Значит надо сидеть придумывать и разрабатывать свою плату расширения.

А затем? Представляете, что будет твориться в компе? “так, надо проиграть следующие 5 секунд музыки, зажечь вон тот индикатор, а вон тот погасить, а там кнопку не нажали? так, надо снова зажечь индикатор, оп-па, GPS чего-то хочет нам рассказать” … Ничего не напоминает? Правильно, мысли новичка за рулем, когда мозг одновременно решает, насколько надо повернуть руль, какую педаль нажать и попытаться вспомнить пункт правил, регламентирующий разъезд на нерегулируемом перекрестке. В большинстве случаев спасает то, что компьютеры все-таки умеют делать это быстро и не свихнувшись при этом.

Но для микроконтроллера задачи позажигать индикторы вообще не стоит. У навороченных контроллеров число ножек для подобных целей измеряется десятками. А с платами расширения их число может легко измеряться сотнями. Зато задачи типа “проиграть mp3” или “показать пользователю картинку” в рамках одного контроллера не решаются никак. Для каждого действия вне установленных рамок требуется свой, отдельный микроконтроллер. В итоге музыку играет один контроллер, голоса типа “поверните налево” обеспечивает второй, а картинки рисует вообще третьий. А все это между собой связано какой-нибудь хитрой шиной … В общем, ужас. И этот ужас старательно поддерживается всеми, кто кормится с этой отрасли. А пользователи недоумевают: как же так, навигатор за 5 тысяч круче и навороченней встроенного в машину, за который отдали 100?

В итоге в реальности нет никакого противостояния между контроллерами и компьютерами. Как и опытный водитель просто не думает, какую педаль и насколько надо нажать, что бы машина поехала или остановилась. Как у него и не возникает мысли вроде “куда и насколько надо повернуть руль, что бы машина повернула?”

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

А вот узнать положение машины по GPS, распознать код ошибки или сходить в интернет – этими задачами пусть занимается компьютер.

Мир, дружба и жевачка в одном флаконе.