ESP8266 + Dali = ошибка Exception 9: LoadStoreAlignmentCause

vlad-323
Offline
Зарегистрирован: 19.04.2021

Доброго времени суток. Хочу сделать "мост" между wifi и Dali. За основу взял код: https://habr.com/ru/post/321888/ Для теста все прошил и залил в Arduino nano и ожидаемо все хорошо, все работает. Для ESP же есть проблема - не могу получить статус светильника, падает в ошибку Exception 9: LoadStoreAlignmentCause: Load or store to an unaligned address, ругается на строчку 472 в файле Dali.cpp: timeArray[k] = timeArray[k — 1]. Что я пытался сделать — с помощью sizeof выяснил, сколько занимают типы данных в atmega328 и в esp8266. Ну и в итоге поменял все int на int16_t и с другими типами также. Но это не помогло. Прикрепляю исходники, может кто подскажет.

Dali_esp.ino

#include "Dali.h"


const int DALI_TX = 4;
const int DALI_RX_A = A0;


void setup()
{
  Serial.begin(115200);
  dali.setupTransmit(DALI_TX);
  dali.setupAnalogReceive(DALI_RX_A);
  dali.busTest();
  dali.msgMode = true;
}


void loop()
{
  const int delaytime = 500;
  String comMsg;

  // Read command from port
  delay(delaytime);

  while (Serial.available())
  {
    comMsg = comMsg + (char)(Serial.read());
  };

  if (comMsg == "level")
  {
    dali.transmit(0b00000001, QUERY_ACTUAL_LEVEL);
    Serial.println("Lamp 1 level:");
    Serial.println(dali.receive());
    delay(10);

    dali.transmit(0b00000011, QUERY_ACTUAL_LEVEL);
    Serial.println("Lamp 2 level:");
    Serial.println(dali.receive());
    delay(10);

    dali.transmit(0b00000101, QUERY_ACTUAL_LEVEL);
    Serial.println("Lamp 3 level:");
    Serial.println(dali.receive());
    delay(10);
  };

};

Dali.h

#ifndef dali_h
#define dali_h

//timer scaling factors for different transmission speeds
#define MAN_300 0
#define MAN_600 1
#define MAN_1200 2
#define MAN_2400 3
#define MAN_4800 4
#define MAN_9600 5
#define MAN_19200 6
#define MAN_38400 7

/*
  Timer 2 in the ATMega328 and Timer 1 in a ATtiny85 is used to find the time between
  each transition coming from the demodulation circuit.
  Their setup is for sampling the input in regular intervals.
  For practical reasons we use power of 2 timer prescaller for sampling,
  for best timing we use pulse lenght as integer multiple of sampling speed.
  We chose to sample every 8 ticks, and pulse lenght of 48 ticks
  thats 6 samples per pulse, lower sampling rate (3) will not work well for
  innacurate clocks (like internal oscilator) higher sampling rate (12) will
  cause too much overhead and will not work at higher transmission speeds.
  This gives us 16000000Hz/48/256 = 1302 pulses per second (so it's not really 1200)
  At different transmission speeds or on different microcontroller frequencies, clock prescaller is adjusted
  to be compatible with those values. We allow about 50% clock speed difference both ways
  allowing us to transmit even with up to 100% in clock speed difference
*/

// DALI coomands
#define BROADCAST_DP        0b11111110
#define BROADCAST_C         0b11111111
#define ON_DP               0b11111110
#define OFF_DP              0b00000000
#define ON_C                0b00000101
#define OFF_C               0b00000000
#define QUERY_STATUS        0b10010000
#define QUERY_ACTUAL_LEVEL  0b10100000
#define RESET               0b00100000

//setup timing for transmitter
#define HALF_BIT_INTERVAL 1666

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#include <pins_arduino.h>
#endif

class Dali
{
  public:
    Dali(); //the constructor
    void setTxPin(uint8_t pin); //set the arduino digital pin for transmit.
    void setRxAnalogPin(uint8_t pin); //set the arduino digital pin for receive.
    void workAround1MhzTinyCore(uint8_t a = 1); //apply workaround for defect in tiny Core library for 1Mhz
    void setupTransmit(uint8_t pin); //set up transmission
    void setupAnalogReceive(uint8_t pin);
    void transmit(uint8_t cmd1, uint8_t cmd2); //transmit 16 bits of data
    void scanShortAdd(); //scan for short address
    void busTest(); // bus test
    void initialisation(); //initialization of new luminaries
    bool cmdCheck(String & input, int16_t & cmd1, int16_t & cmd2);
    uint8_t receive(); //get response

    int16_t minResponseLevel();
    int16_t maxResponseLevel();

    uint8_t speedFactor;
    uint16_t delay1;
    uint16_t delay2;
    uint16_t period;
    String errorMsg; //error message of last operation
    bool msgMode; //0 - get only response from dali bus to COM; 1 - response with text (comments)
    bool getResponse;
    uint8_t RxAnalogPin;

    long daliTimeout = 20000; //us, DALI response timeout
    int16_t analogLevel = 870; //analog border level (less - "0"; more - "1")




  private:

    void sendByte(uint8_t b); //transmit 8 bits of data
    void sendBit(int16_t b); //transmit 1 bit of data
    void sendZero(void); //transmit "0"
    void sendOne(void); //transmit "1"
    void splitAdd(long input, uint8_t &highbyte, uint8_t &middlebyte, uint8_t &lowbyte); //split random address

    int16_t readBinaryString(char *s);
    
    uint8_t TxPin;

    uint8_t applyWorkAround1Mhz;
    uint8_t rxAnalogPin = 0;

};//end of class Dali

// Cant really do this as a real C++ class, since we need to have
// an ISR
extern "C"
{


}

extern Dali dali;

#endif

Dali.cpp

#include "Dali.h"

Dali::Dali() //constructor
{
  applyWorkAround1Mhz = 0;
}


void Dali::setTxPin(uint8_t pin)
{
  TxPin = pin; // user sets the digital pin as output
  pinMode(TxPin, OUTPUT);
  digitalWrite(TxPin, HIGH);
}

void Dali::setRxAnalogPin(uint8_t pin)
{
  RxAnalogPin = pin; // user sets the digital pin as output
}

void Dali::workAround1MhzTinyCore(uint8_t a)
{
  applyWorkAround1Mhz = a;
}

void Dali::setupAnalogReceive(uint8_t pin)
{
  setRxAnalogPin(pin); // user sets the analog pin as input
}


void Dali::setupTransmit(uint8_t pin)
{
  setTxPin(pin);
  speedFactor = 2;
  //we don't use exact calculation of passed time spent outside of transmitter
  //because of high ovehead associated with it, instead we use this
  //emprirically determined values to compensate for the time loss

#if F_CPU == 1000000UL
  uint16_t compensationFactor = 88; //must be divisible by 8 for workaround
#elif F_CPU == 8000000UL
  uint16_t compensationFactor = 12;
#else //16000000Mhz
  uint16_t compensationFactor = 4;
#endif

#if (F_CPU == 80000000UL) || (F_CPU == 160000000)   // ESP8266 80MHz or 160 MHz
  delay1 = delay2 = (HALF_BIT_INTERVAL >> speedFactor) - 2;
#else
  delay1 = (HALF_BIT_INTERVAL >> speedFactor) - compensationFactor;
  delay2 = (HALF_BIT_INTERVAL >> speedFactor) - 2;
  period = delay1 + delay2;

#if F_CPU == 1000000UL
  delay2 -= 22; //22+2 = 24 is divisible by 8
  if (applyWorkAround1Mhz) { //definition of micro delay is broken for 1MHz speed in tiny cores as of now (May 2013)
    //this is a workaround that will allow us to transmit on 1Mhz
    //divide the wait time by 8
    delay1 >>= 3;
    delay2 >>= 3;
  }
#endif
#endif
}


void Dali::transmit(uint8_t cmd1, uint8_t cmd2) // transmit commands to DALI bus (address byte, command byte)
{
  sendBit(1);
  sendByte(cmd1);
  sendByte(cmd2);
  digitalWrite(TxPin, HIGH);
}


void Dali::sendByte(uint8_t b)
{
  for (int16_t i = 7; i >= 0; i--)
  {
    sendBit((b >> i) & 1);
  }
}


void Dali::sendBit(int16_t b)
{
  if (b) {
    sendOne();
  }
  else {
    sendZero();
  }
}


void Dali::sendZero(void)
{
  digitalWrite(TxPin, HIGH);
  delayMicroseconds(delay2);
  digitalWrite(TxPin, LOW);
  delayMicroseconds(delay1);
}


void Dali::sendOne(void)
{
  digitalWrite(TxPin, LOW);
  delayMicroseconds(delay2);
  digitalWrite(TxPin, HIGH);
  delayMicroseconds(delay1);
}


void Dali::busTest() //DALI bus test
{
  int16_t maxLevel;
  int16_t minLevel;

  //Luminaries must turn on and turn off. If not, check connection.
  delay(100);
  dali.transmit(BROADCAST_C, OFF_C); //Broadcast ON
  delay(500);
  dali.transmit(BROADCAST_C, ON_C); //Broadcast OFF
  delay(100);
  //while (!Serial); // wait for serial port to connect. Needed for native USB port only

  //Receive response from luminaries: max and min level
  dali.transmit(BROADCAST_C, QUERY_STATUS);
  maxLevel = dali.maxResponseLevel();

  dali.transmit(BROADCAST_C, QUERY_STATUS);
  minLevel = dali.minResponseLevel();


  dali.analogLevel = (int16_t)(maxLevel + minLevel) / 2;
}


void Dali::splitAdd(long input, uint8_t &highbyte, uint8_t &middlebyte, uint8_t &lowbyte)
{
  highbyte = input >> 16;
  middlebyte = input >> 8;
  lowbyte = input;
}


// define min response level
int16_t Dali::minResponseLevel()
{

  const uint8_t dalistep = 40; //us
  uint16_t rxmin = 1024;
  uint16_t dalidata;
  long idalistep;



  for (idalistep = 0; idalistep < dali.daliTimeout; idalistep = idalistep + dalistep) {
    dalidata = analogRead(RxAnalogPin);
    if (dalidata < rxmin) {
      rxmin = dalidata;
    };
    delayMicroseconds(dalistep);
  }
  return rxmin;
}

// define max response level
int16_t Dali::maxResponseLevel()
{

  const uint8_t dalistep = 40; //us
  uint16_t rxmax = 0;
  uint16_t dalidata;
  long idalistep;


  for (idalistep = 0; idalistep < dali.daliTimeout; idalistep = idalistep + dalistep) {
    dalidata = analogRead(dali.RxAnalogPin);
    if (dalidata > rxmax) {
      rxmax = dalidata;
    };
    delayMicroseconds(dalistep);
  }
  return rxmax;
}


//scan for individual short address
void Dali::scanShortAdd()
{

  const int16_t delayTime = 10;
  const uint8_t start_ind_adress = 0;
  const uint8_t finish_ind_adress = 127;
  uint8_t add_byte;
  uint8_t device_short_add;
  uint8_t response;

  dali.transmit(BROADCAST_C, OFF_C); // Broadcast Off
  delay(delayTime);

  if (dali.msgMode) {
    Serial.println("Short addresses:");
  }

  for (device_short_add = start_ind_adress; device_short_add <= 63; device_short_add++) {

    add_byte = 1 + (device_short_add << 1); // convert short address to address byte


    dali.transmit(add_byte, 0xA1);

    response = dali.receive();

    if (dali.getResponse) {

      dali.transmit(add_byte, ON_C); // switch on
      delay(1000);
      dali.transmit(add_byte, OFF_C); // switch off
      delay(1000);

    }
    else {
      response = 0;
    }



    if (dali.msgMode) {
      Serial.print("BIN: ");
      Serial.print(device_short_add, BIN);
      Serial.print(" ");
      Serial.print("DEC: ");
      Serial.print(device_short_add, DEC);
      Serial.print(" ");
      Serial.print("HEX: ");
      Serial.print(device_short_add, HEX);
      Serial.print(" ");
      if (dali.getResponse) {
        Serial.print("Get response");
      }
      else {
        Serial.print("No response");
      }
      Serial.println();
    }
    else {
      if (dali.getResponse) {
        Serial.println(255, BIN);
      }
      else {
        Serial.println(0, BIN);
      }

    }

  }

  dali.transmit(BROADCAST_C, ON_C); // Broadcast On
  Serial.println();
  delay(delayTime);

}


int16_t Dali::readBinaryString(char *s)
{
  int16_t result = 0;
  while (*s) {
    result <<= 1;
    if (*s++ == '1') result |= 1;
  }
  return result;
}


bool Dali::cmdCheck(String & input, int16_t & cmd1, int16_t & cmd2)
{
  bool test = true;

  input.replace(" ", "");   // Delete spaces

  if (input.length() != 16) {
    test = false; //check if command contain 16bit
  }
  else {
    for (int16_t i = 0; i <= input.length() - 1; i++) {
      if ((int16_t)input.charAt(i) == 49 or (int16_t)input.charAt(i) == 48) {}
      else {
        test = false;
      };
    };
  };

  if (test) {
  char * S1 = new char[input.substring(0, 8).length() + 1];
  strcpy(S1,input.substring(0, 8).c_str());
  cmd1 = readBinaryString(S1);    
  char * S2 = new char[input.substring(8, 16).length() + 1];
  strcpy(S2,input.substring(0, 8).c_str());
  cmd2 = readBinaryString(S2);
  }

  return test;
}

void Dali::initialisation() {

  const int16_t delaytime = 10; //ms

  long low_longadd = 0x000000;
  long high_longadd = 0xFFFFFF;
  long longadd = (long)(low_longadd + high_longadd) / 2;
  uint8_t highbyte;
  uint8_t middlebyte;
  uint8_t lowbyte;
  uint8_t short_add = 0;
  uint8_t cmd2;

  delay(delaytime);
  dali.transmit(BROADCAST_C, RESET);
  delay(delaytime);
  dali.transmit(BROADCAST_C, RESET);
  delay(delaytime);
  dali.transmit(BROADCAST_C, OFF_C);
  delay(delaytime);
  dali.transmit(0b10100101, 0b00000000); //initialise
  delay(delaytime);
  dali.transmit(0b10100101, 0b00000000); //initialise
  delay(delaytime);
  dali.transmit(0b10100111, 0b00000000); //randomise
  delay(delaytime);
  dali.transmit(0b10100111, 0b00000000); //randomise

  if (dali.msgMode) {
    Serial.println("Searching fo long addresses:");
  }

  while (longadd <= 0xFFFFFF - 2 and short_add <= 64) {
    while ((high_longadd - low_longadd) > 1) {

      dali.splitAdd(longadd, highbyte, middlebyte, lowbyte); //divide 24bit adress into three 8bit adresses
      delay(delaytime);
      dali.transmit(0b10110001, highbyte); //search HB
      delay(delaytime);
      dali.transmit(0b10110011, middlebyte); //search MB
      delay(delaytime);
      dali.transmit(0b10110101, lowbyte); //search LB
      delay(delaytime);
      dali.transmit(0b10101001, 0b00000000); //compare

      if (minResponseLevel() > dali.analogLevel)
      {
        low_longadd = longadd;
      }
      else
      {
        high_longadd = longadd;
      }

      longadd = (low_longadd + high_longadd) / 2; //center

      if (dali.msgMode) {
        Serial.print("BIN: ");
        Serial.print(longadd + 1, BIN);
        Serial.print(" ");
        Serial.print("DEC: ");
        Serial.print(longadd + 1, DEC);
        Serial.print(" ");
        Serial.print("HEX: ");
        Serial.print(longadd + 1, HEX);
        Serial.println();
      }
      else {
        Serial.println(longadd + 1);
      }
    } // second while


    if (high_longadd != 0xFFFFFF)
    {
      splitAdd(longadd + 1, highbyte, middlebyte, lowbyte);
      dali.transmit(0b10110001, highbyte); //search HB
      delay(delaytime);
      dali.transmit(0b10110011, middlebyte); //search MB
      delay(delaytime);
      dali.transmit(0b10110101, lowbyte); //search LB
      delay(delaytime);
      dali.transmit(0b10110111, 1 + (short_add << 1)); //program short adress
      delay(delaytime);
      dali.transmit(0b10101011, 0b00000000); //withdraw
      delay(delaytime);
      dali.transmit(1 + (short_add << 1), ON_C);
      delay(1000);
      dali.transmit(1 + (short_add << 1), OFF_C);
      delay(delaytime);
      short_add++;

      if (dali.msgMode) {
        Serial.println("Assigning a short address");
      }

      high_longadd = 0xFFFFFF;
      longadd = (low_longadd + high_longadd) / 2;

    }
    else {
      if (dali.msgMode) {
        Serial.println("End");
      }
    }
  } // first while


  dali.transmit(0b10100001, 0b00000000);  //terminate
  dali.transmit(BROADCAST_C, ON_C);  //broadcast on
}


uint8_t Dali::receive() {

  uint32_t startFuncTime = 0;
  bool previousLogicLevel = 1;
  bool currentLogicLevel = 1;
  uint16_t arrLength = 20;
  int16_t __attribute__((aligned(2))) timeArray[arrLength];
  int16_t i = 0;
  int16_t k = 0;
  bool __attribute__((aligned(1))) logicLevelArray[arrLength];
  //Serial.println("timeArray:");
  //Serial.println(sizeof(timeArray[arrLength]));
  int16_t response = 0;

  dali.getResponse = false;
  startFuncTime = micros();

  // add check for micros overlap here!!!

  while (micros() - startFuncTime < dali.daliTimeout and i < arrLength)
  {
    // geting response
    if (analogRead(dali.RxAnalogPin) > dali.analogLevel) {
      currentLogicLevel = 1;
    }
    else {
      currentLogicLevel = 0;
    }

    if (previousLogicLevel != currentLogicLevel) {
      timeArray[i] = micros() - startFuncTime;
      logicLevelArray[i] = currentLogicLevel;
      previousLogicLevel = currentLogicLevel;
      dali.getResponse = true;
      i++;

    }
  }



  arrLength = i;

  //decoding to manchester
  for (i = 0; i < arrLength - 1; i++) 
  {
    if ((timeArray[i + 1] - timeArray[i]) > 0.75 * dali.period) 
    {
      for (k = arrLength; k > i; k--) 
      {
        timeArray[k] = timeArray[k - 1];
        logicLevelArray[k] = logicLevelArray[k - 1];
        //Serial.println(k);
      }
      arrLength++;
      timeArray[i + 1] = (timeArray[i] + timeArray[i + 2]) / 2;
      logicLevelArray[i + 1] = logicLevelArray[i];
    }
  }

  



  k = 8;

  for (i = 1; i < arrLength; i++) {
    if (logicLevelArray[i] == 1) {
      if ((int16_t)round((timeArray[i] - timeArray[0]) / (0.5 * dali.period)) & 1) {
        response = response + (1 << k);
      }
      k--;
    }
  }


  //remove start bit
  response = (uint8_t)response;

  return response;

} 



Dali dali;

 

Logik
Offline
Зарегистрирован: 05.08.2014

см. стр.428 и 431. И все что с теми переменными связано. Для начала воще убири __attribute__((aligned(...,  пусть само с своими проблемами разбирается )))

vlad-323
Offline
Зарегистрирован: 19.04.2021

Спасибо за совет, но к сожалению то, что я убрал __attribute... ничего не дало. Это я экспериментировал, у автора исходников этого не было. Зато, если в 428 строке жестко указать размер массива [20], а не [arrLength] то тогда ошибка не появляется, но функция всегда возвращает значение 255.

Logik
Offline
Зарегистрирован: 05.08.2014

Обратил внимание на странность. Эта самая arrLength- размер массивов. И ограничивает перебор по ним, например  for (i = 1; i < arrLength; i++) {... в стр.488 . Но ранее есть стр.476  arrLength++;. Это очень странно выглядит. И похоже приводит к выходу за пределы массива. Да и в стр.470 for (k = arrLength;.... сразу индекс за пределами массива.  Это вероятно и вызывает ту самую проблему из стартового сообщения. Сдается мне что Dali::receive()- одна большая лажа... На атмеге эти ошибки не проявлялись, т.к. она вообще не следит за нарушением границ.

vlad-323
Offline
Зарегистрирован: 19.04.2021

Уважаемый Logik если заинтересованы, то я готов оплатить вам работу за переписывание функции. Прошу написать мне на почту vlad-27@i.ua

Logik
Offline
Зарегистрирован: 05.08.2014

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

GarryC
Offline
Зарегистрирован: 08.08.2016

В строках с 441 по 459 цикл крутится, пока не истечет время, либо накопится доступное количество (20) сообщений.
После этого в строке 463 число сообщений ставится на 1 больше, чем реально накоплено. Исправьте на arrLength = i-1;
и посмотрите, что получится.

 

vlad-323
Offline
Зарегистрирован: 19.04.2021

Спасибо за совет, но к сожалению не помогло. Все равно та же ошибка.

asv
Offline
Зарегистрирован: 26.04.2021

На esp32 код из статьи нормально отрабатывает

b707
Offline
Зарегистрирован: 26.05.2017

asv пишет:

На esp32 код из статьи нормально отрабатывает

может у автора статьи была другая библиотека?

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

asv
Offline
Зарегистрирован: 26.04.2021

Там есть форк который запускали на 

WeMOs D1 mini Pin Function - ESP-8266 Pin

https://github.com/b0b7h3d0g/DALI

Какая физическая схема подключения DALI, в ней других мастер-контроллеров  нет?