Интерфейс DALI и Arduino. Реальный сюрреализм

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

    • DALI является открытым протоколом;
    • DALI- децентрализованная шина, то есть не имеет центрального контроллера и допускает любую топологию.

    Всё это показалось очень привлекательным и задача показалась совершенно не сложной. На первый взгляд. Решили сделать мастера DALI на Arduino.

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

    Поэтому решили сделать свои скетчи. Сделали два. Первый для назначения коротких адресов всем “участникам” сети.
    Посмотреть
    #define DALI_TX_PIN   3
    #define DALI_RX_PIN   A0
    #define LED_PIN       13
    #define RESET               0b00100000
    #define INITIALISE          0xA5
    #define RANDOMISE           0xA7
    #define SEARCHADDRH         0xB1
    #define SEARCHADDRM         0xB3
    #define SEARCHADDRL         0xB5
    #define PRG_SHORT_ADDR      0xB7
    #define COMPARE             0xA9
    #define WITHDRAW            0xAB
    #define TERMINATE           0xA1
    
    #define START_SHORT_ADDR    2
    
    #define DALI_ANALOG_LEVEL   650
    
    #define DALI_HALF_BIT_TIME         416 //microseconds
    #define DALI_TWO_PACKET_DELAY      10 //miliseconds
    #define DALI_RESPONSE_DELAY_COUNT  15 //максимальное число полубитов
                                          //до ответа
    uint8_t ShortAddr = START_SHORT_ADDR;
    void setup()
    {
      pinMode(LED_PIN, OUTPUT);
      digitalWrite(LED_PIN, LOW);
      pinMode(DALI_TX_PIN, OUTPUT);
      digitalWrite(DALI_TX_PIN, HIGH);
        Serial.begin(115200);
      DaliInit();
    }
    //-----------------------------------------------------
    void loop()
    {
      
    }
    //-----------------------------------------------------
    void DaliInit()
    {
      Serial.println("Initialization...");
      DaliTransmitCMD(RESET, 0x00);
      delay(2*DALI_TWO_PACKET_DELAY);
      DaliTransmitCMD(RESET, 0x00);
      delay(2*DALI_TWO_PACKET_DELAY);
      delay(100);
      DaliTransmitCMD(INITIALISE, 0x00); 
      delay(DALI_TWO_PACKET_DELAY);
      DaliTransmitCMD(INITIALISE, 0x00);
      delay(DALI_TWO_PACKET_DELAY);
      DaliTransmitCMD(INITIALISE, 0x00);
      delay(DALI_TWO_PACKET_DELAY);
      delay(100);
      DaliTransmitCMD(RANDOMISE, 0x00);
      delay(DALI_TWO_PACKET_DELAY);
      DaliTransmitCMD(RANDOMISE, 0x00);
      delay(DALI_TWO_PACKET_DELAY);
      delay(100);
      while(ShortAddr < 64)
      {
        long SearchAddr = 0xFFFFFF;
        bool Response = 0;
        long LowLimit = 0;
        long HighLimit = 0x1000000;
        Response = SearchAndCompare(SearchAddr);
        delay(DALI_TWO_PACKET_DELAY);
        if(Response)
        {
          digitalWrite(LED_PIN, LOW);
          Serial.println("Device detected, address searching...");
          if(!SearchAndCompare(SearchAddr - 1))
          {
            delay(DALI_TWO_PACKET_DELAY);
            SearchAndCompare(SearchAddr);
            delay(DALI_TWO_PACKET_DELAY);
            DaliTransmitCMD(PRG_SHORT_ADDR, ((ShortAddr << 1) | 1));
            delay(3*DALI_TWO_PACKET_DELAY);
            DaliTransmitCMD(WITHDRAW, 0x00);
            Serial.print("24-bit address found: 0x");
            Serial.println(SearchAddr, HEX);
            Serial.print("Assigning short address ");
            Serial.println(ShortAddr);
            break;
          }
        }
        else
        {
          Serial.println("No devices detected");
          break;
        }
    
        while(1)
        {
          SearchAddr = (long)((LowLimit + HighLimit) / 2);
    
          Response = SearchAndCompare(SearchAddr);
          delay(DALI_TWO_PACKET_DELAY);
    
          if (Response)
          {
            digitalWrite(LED_PIN, LOW);
    
            if ((SearchAddr == 0) || (!SearchAndCompare(SearchAddr - 1)))
              break;
            
            HighLimit = SearchAddr;
          }
          else
            LowLimit = SearchAddr;
        }
    
        delay(DALI_TWO_PACKET_DELAY);
        SearchAndCompare(SearchAddr);
        delay(DALI_TWO_PACKET_DELAY);
        DaliTransmitCMD(PRG_SHORT_ADDR, ((ShortAddr << 1) | 1));
        delay(5*DALI_TWO_PACKET_DELAY);
        DaliTransmitCMD(WITHDRAW, 0x00);
        delay(DALI_TWO_PACKET_DELAY);
        
        Serial.print("24-bit address found: 0x");
        Serial.println(SearchAddr, HEX);
        Serial.print("Assigning short address ");
        Serial.println(ShortAddr);
    
        ShortAddr++;
    
       // break; //только для одного модуля
      }
    
      delay(DALI_TWO_PACKET_DELAY);
      DaliTransmitCMD(TERMINATE, 0x00);
      delay(DALI_TWO_PACKET_DELAY);
      Serial.println("Init complete");
    }
    //-------------------------------------------------
    bool SearchAndCompare(long SearchAddr)
    {
      bool Response = 0;
      
      uint8_t HighByte = SearchAddr >> 16;
      uint8_t MiddleByte = SearchAddr >> 8;
      uint8_t LowByte = SearchAddr;
    
      for(uint8_t i = 0; i < 3; i++)
      {
        DaliTransmitCMD(SEARCHADDRH, HighByte);
        delay(DALI_TWO_PACKET_DELAY);
        DaliTransmitCMD(SEARCHADDRM, MiddleByte);
        delay(DALI_TWO_PACKET_DELAY);
        DaliTransmitCMD(SEARCHADDRL, LowByte);
        delay(DALI_TWO_PACKET_DELAY);
      }
      DaliTransmitCMD(COMPARE, 0x00);
      delayMicroseconds(7 * DALI_HALF_BIT_TIME);
      
      for(uint8_t i = 0; i < DALI_RESPONSE_DELAY_COUNT; i++)
      {
        if (analogRead(DALI_RX_PIN) < DALI_ANALOG_LEVEL)
        {
          Response = 1;
          digitalWrite(LED_PIN, HIGH);
          break;
        }
        
        delayMicroseconds(DALI_HALF_BIT_TIME);
      }
    
      return Response;
    }
    //-------------------------------------------------
    void DaliTransmitCMD(uint8_t Part1, uint8_t Part2)
    {
      uint8_t DALI_CMD[] = { Part1, Part2 };
      
      //Старт бит
      digitalWrite(DALI_TX_PIN, LOW);
      delayMicroseconds(DALI_HALF_BIT_TIME);
      digitalWrite(DALI_TX_PIN, HIGH);
      delayMicroseconds(DALI_HALF_BIT_TIME);
      //команда
      for (uint8_t CmdPart = 0; CmdPart < 2; CmdPart++)
      {
        for(int i = 7; i >= 0; i--)
        {
          bool BitToSend = false;
    
          if ((DALI_CMD[CmdPart] >> i) & 1)
            BitToSend = true;
          
          if (BitToSend)
            digitalWrite(DALI_TX_PIN, LOW);
          else
            digitalWrite(DALI_TX_PIN, HIGH);
    
          delayMicroseconds(DALI_HALF_BIT_TIME);
    
          if (BitToSend)
            digitalWrite(DALI_TX_PIN, HIGH);
          else
            digitalWrite(DALI_TX_PIN, LOW);
    
          delayMicroseconds(DALI_HALF_BIT_TIME);
        }
      }
    
      digitalWrite(DALI_TX_PIN, HIGH);
    }
    
    

    Или скачать

    А это тестовый. Управляем двумя модулями подключенными к DALI.
    Посмотреть
    #define DALI_TX_PIN   3
    #define DALI_RX_PIN   A0
    #define BROADCAST_CMD       0b11111111
    #define DOWN                0b00000010
    #define UP                  0b00000001
    #define DALI_CHNL_COUNT     4
    #define LAMP_OFF_VALUE      0
    
    #define DALI_HALF_BIT_TIME      416 //microseconds
    #define DALI_TWO_PACKET_DELAY   10 //miliseconds
    
    //аналоговые входы
    uint8_t AnalogPins[DALI_CHNL_COUNT] = {A1, A2, A3, A4, };
    //кнопки
    uint8_t KeyPins[DALI_CHNL_COUNT] = {4, 5, 6, 7, };
    uint8_t DALIPrevVals[DALI_CHNL_COUNT] = {0, 0, 0, 0};
    uint8_t LampState[DALI_CHNL_COUNT] = {0, 0, 0, 0};
    
    void setup()
    {
      pinMode(DALI_TX_PIN, OUTPUT);
      digitalWrite(DALI_TX_PIN, HIGH);
    
      for(uint8_t i = 0; i < DALI_CHNL_COUNT; i++)
      {
        pinMode(KeyPins[i], INPUT);
        digitalWrite(KeyPins[i], HIGH);
      }
    }
    
    void loop()
    {
      for(uint8_t PWM = 2; PWM < DALI_CHNL_COUNT; PWM++)
      {
        if (LampState[PWM] == 1)
        {
        uint16_t ADCValue = analogRead(AnalogPins[PWM]);
        
        if (ADCValue > 1016)
          ADCValue = 1016;
        ADCValue /= 4;
        
        uint8_t PWMVal = ADCValue;
    
        if (abs(DALIPrevVals[PWM] - PWMVal) >= 1)
        {
          DALIPrevVals[PWM] = PWMVal;
          DaliTransmitCMD(PWM << 1, PWMVal);
          if (LampState[PWM] == 0)
            LampState[PWM] = 1;
    
          delay(DALI_TWO_PACKET_DELAY);
        }
        }
      }
    
      for(uint8_t KEY = 0; KEY < DALI_CHNL_COUNT; KEY++)
      {
        if (digitalRead(KeyPins[KEY]) == LOW)
        {
          delay(70);
    
          if (KEY == 0)
          {
           DaliTransmitCMD(BROADCAST_CMD, UP);
           delay(DALI_TWO_PACKET_DELAY);
           break;
          }
    
          else if (KEY == 1)
          {
           DaliTransmitCMD(BROADCAST_CMD, DOWN);
           delay(DALI_TWO_PACKET_DELAY);
           break;
          }
    
          if (digitalRead(KeyPins[KEY]) == LOW)
          {
            if (LampState[KEY] == 0)
            {
              LampState[KEY] = 1;
              uint16_t ADCValue = analogRead(AnalogPins[KEY]);
    
              if (ADCValue > 1016)
                ADCValue = 1016;
              ADCValue /= 4;
        
              uint8_t PWMVal = ADCValue;
              DaliTransmitCMD(KEY << 1, PWMVal);
            }
    
            else
            {
              LampState[KEY] = 0;
              DaliTransmitCMD(KEY << 1, LAMP_OFF_VALUE);
            }
    
            delay(DALI_TWO_PACKET_DELAY);
          }
    
          delay(500);
        }
      }
    }
    //-------------------------------------------------
    void DaliTransmitCMD(uint8_t Part1, uint8_t Part2)
    {
      uint8_t DALI_CMD[] = { Part1, Part2 };
      
      //Старт бит
      digitalWrite(DALI_TX_PIN, LOW);
      delayMicroseconds(DALI_HALF_BIT_TIME);
      digitalWrite(DALI_TX_PIN, HIGH);
      delayMicroseconds(DALI_HALF_BIT_TIME);
      //команда
      for (uint8_t CmdPart = 0; CmdPart < 2; CmdPart++)
      {
        for(int i = 7; i >= 0; i--)
        {
          bool BitToSend = false;
    
          if ((DALI_CMD[CmdPart] >> i) & 1)
            BitToSend = true;
          
          if (BitToSend)
            digitalWrite(DALI_TX_PIN, LOW);
          else
            digitalWrite(DALI_TX_PIN, HIGH);
    
          delayMicroseconds(DALI_HALF_BIT_TIME);
    
          if (BitToSend)
            digitalWrite(DALI_TX_PIN, HIGH);
          else
            digitalWrite(DALI_TX_PIN, LOW);
    
          delayMicroseconds(DALI_HALF_BIT_TIME);
        }
      }
    
      digitalWrite(DALI_TX_PIN, HIGH);
    }
    

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

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

    В ролике отчет о проведённой работе.

    Тестировали модули DAP-04 и LCM-60DA от Mean Well. Но работать будет с любыми другими.

    А это схема обвески Arduino переводящая её в режим мастера DALI и блока питания одновременно.



    Это подключение кнопок для тестового скетча.



    А здесь уже маленькая сеть DALI



    • +11
    • 9,5k
    • 8
    Поделиться публикацией

    Похожие публикации

    Комментарии 8

      +1
      Всё вроде здорово в стандарте, но 1200 бит/с… Пяток-другой RGBW источников с попыткой плавной регулировки поставят всю систему на колени и красивому слайдшоу, а если их всей группой регулировать, то весь смысл поштучной адресации пропадает.
        0
        Тут нужно заметить, что по моему мнению DALI отлично подходит для управления светом в жилых помещениях, а для цветомузыки лучше использовать DMX512.
          0
          для цветомузыки — да. но мы говорим про подсветку. Сейчас освещение одной комнаты запросто может иметь десяток, а то и два источников. ну вот плавна регулировка такой систеы на DALI упрётся в пропускную способность.
            0
            Не упрется. Если память не изменяет, там есть команда установить яркость светильника с определенной скоростью изменения. Не надо все время долбить команды изменения яркости.
        0
        Спасибо большое. Давно ждал.
          0
          У ШИМ шим контроллера случайным образом сгенерировался адрес 0xFFFFFF. Интересно насколько хорошо модули генерируют адреса.
            0
            Делал такой же мастер, как у Тимура Набиева (огромное ему спасибо за сэкономленное мне время). Все заработало. Причем отлично. Единственный косяк у меня был при компиляции. Пришлось два файла в один слить — и все скомпилировалось. Мой коллега, который на ардуине пол собаки съел, сказал что так бывает.
            Делаю так же проект с DALI. Могу предложить схему приемопередатчика для DALI master который хорошо соответствует спецификации стандарта. И к тому же гальваноразвязанный.
              0
              Одному мне кажется странным на картинках -12VA вместе с GND? Вторая опечатка тут же — VA. Ну нельзя же так.

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

              Самое читаемое