Всем привет. Нашему отделу была поставлена задача провести презентацию цифрового интерфейса 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



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


  1. MEG123
    03.02.2018 02:44

    Всё вроде здорово в стандарте, но 1200 бит/с… Пяток-другой RGBW источников с попыткой плавной регулировки поставят всю систему на колени и красивому слайдшоу, а если их всей группой регулировать, то весь смысл поштучной адресации пропадает.


    1. pesp
      05.02.2018 14:09

      Тут нужно заметить, что по моему мнению DALI отлично подходит для управления светом в жилых помещениях, а для цветомузыки лучше использовать DMX512.


      1. MEG123
        05.02.2018 14:17

        для цветомузыки — да. но мы говорим про подсветку. Сейчас освещение одной комнаты запросто может иметь десяток, а то и два источников. ну вот плавна регулировка такой систеы на DALI упрётся в пропускную способность.


        1. pesp
          05.02.2018 15:54

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


  1. RealZoN
    03.02.2018 20:10

    Спасибо большое. Давно ждал.


  1. kolodkin
    05.02.2018 05:51

    У ШИМ шим контроллера случайным образом сгенерировался адрес 0xFFFFFF. Интересно насколько хорошо модули генерируют адреса.


  1. pesp
    05.02.2018 10:52

    Делал такой же мастер, как у Тимура Набиева (огромное ему спасибо за сэкономленное мне время). Все заработало. Причем отлично. Единственный косяк у меня был при компиляции. Пришлось два файла в один слить — и все скомпилировалось. Мой коллега, который на ардуине пол собаки съел, сказал что так бывает.
    Делаю так же проект с DALI. Могу предложить схему приемопередатчика для DALI master который хорошо соответствует спецификации стандарта. И к тому же гальваноразвязанный.


  1. rexen
    05.02.2018 14:43

    Одному мне кажется странным на картинках -12VA вместе с GND? Вторая опечатка тут же — VA. Ну нельзя же так.