Top.Mail.Ru

Часть 20. Не ModBUS, кое-что попроще (продолжение).

Продолжение предыдущей статьи.

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

Вариант первый.

Я не знаю, кому как, но мне понравилось передавать данные в структурах. Когда и ведущий и ведомый знают, как данные структурированы, нет необходимости городить парсер данных. Рассмотрим код ведущего:

struct s_Data                                                                   // Структура для значений с датчиков
{
  float   HDC_Temp;                                                             // Считанное значение температуры
  float   HDC_Hum;                                                              // Считанное значение влажности
  uint8_t Address_DS[8];                                                        // Адрес микросхемы DS18B20
  float   DS_Temp;                                                              // Считанное значение температуры
} __attribute ((__packed__));

Также инициализируем класс RS485 - RS485 myRS485(USART3, false, GPIOB, GPIO_PIN_14) - и в main() - myRS485.init(9600) с нужной нам скоростью порта. Запросы инициализируются каждые 5 секунд. Код запроса такой:

myRS485.Send(BuffTX, 1, 0x21);                                            // Отсылаем запрос
Temp = myRS485.Receive((uint8_t*) &Data);                                 // Читаем данные, которые нам возвращаются
if((Temp != RS485_Empty) && (Temp > 0))                                   // Если они не битые, производим вывод на экран
{
  myTFT.print("Температура HDC     : "); myTFT.println(Data.HDC_Temp, 4);
  myTFT.print("Влажность   HDC     : "); myTFT.println(Data.HDC_Hum, 4);
  myTFT.print("Температура DS18B20 : "); myTFT.println(Data.DS_Temp, 4);
  myTFT.print("Адрес       DS18B20 : ");
  for(uint8_t I=0; I<8; I++)
  {
    myTFT.print(Data.Address_DS[I], HEX);
    myTFT.print(' ');
  }
  myTFT.println(' ');
}

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

Теперь код ведомого. В нём также объявлена точно такая же структура, которая после измерения заполняется данными и передаётся. Также производится инициализация всего, что понадобится. А понадобится нам I2C для датчика HDC1080, который умеет измерять температуру и влажность, сама библиотека датчика и библиотека Dallas которая требует библиотеку OneWire. Обе библиотеки стандартные, используемые всеми Ардуинщиками. Только библиотека OneWire основана на "ногодрыге", на UART и DMA находится в процессе написания.

if(myTimer.isReady())                                                       // Если таймер отщёлкал, запускаем измерение с датчиков
{
  Data.DS_Temp = myDS.getTemp(Address_DS01);                                // Чтение датчика с определённым адресом
  Data.HDC_Temp = myHDC.readTemperature();                                  // Читаем температуру
  Data.HDC_Hum = myHDC.readHumidity();                                      // и влажность с датчика HDC1080
}

CountRX = myRS.Receive((uint8_t*) &Data);                                   // Проверяем, не стучатся ли
if(CountRX > 0)                                                             // Постучались
{
  myRS.Send((uint8_t*) &Data, sizeof(Data));                                // Отдаём данные с датчиков
  myDS.requestTemp();                                                       // Запускаем измерение DS18B20
}

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

Я поставил замер температуры в это место из-за того, что он производится 750 миллисекунд. Если мы сделаем это перед таймером, в это время он может сработать, и мы всё равно не получим, что хотим. А так без особой разницы, где ставить. Поэтому первые один-два запроса от ведущего не будут корректно удовлетворены, данные будут неверны.

Вариант второй.

Отличается от первого только тем, что ведущий передаёт байт не от балды, а номер датчика с которого хочет получить значение. В этом случае на каждый тип датчика создаётся своя структура. В данном примере один датчик HDC1080 и два датчика DS18B20. Нам, соответственно, нужно две структуры: одна для датчика HDC1080 и одна на оба датчика DS18B20. Так как датчики однотипны, а замер и передача производится в разное время, достаточно одной структуры на оба датчика. Необходимо только перед установкой датчиков считать их адреса и занести в переменную. Сделать это можно кодом из первого примера:

struct s_Data_DS                                                                // Структура для значений DS18B20
{
  uint8_t Command;                                                              // Команда, здесь не используется
  uint8_t Address_DS[8];                                                        // Адрес микросхемы
  float   DS_Temp;                                                              // Считанное значение температуры
} __attribute ((__packed__));

struct s_Data_HDC                                                               // Структура для значений HDC1080
{
  uint8_t Command;                                                              // Команда, здесь не используется
  float   HDC_Temp;                                                             // Считанное значение температуры
  float   HDC_Hum;                                                              // Считанное значение влажности
} __attribute ((__packed__));

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

/* Делаем запрос на измерение датчика HDC */
BuffTX[0] = SENSOR_HDC;                                                   // В первый байт буфера запихиваем команду чтения датчика HDC1080
myRS485.Send(BuffTX, 1, 0x21);                                            // Отсылаем запрос
delay(1000);                                                              // Задержка для окончания обмена
Temp = myRS485.Receive((uint8_t*) &Data_HDC);                             // Читаем данные, которые нам возвращаются

if((Temp != RS485_Empty) && (Temp > 0))                                   // Если они не битые, производим вывод на экран
{
  myTFT.print("Температура HDC     : "); myTFT.println(Data_HDC.HDC_Temp, 4);
  myTFT.print("Влажность   HDC     : "); myTFT.println(Data_HDC.HDC_Hum, 4);
  myTFT.println(' ');
}

В первый байт буфера для передачи записываем код нужного нам датчика и отсылаем буфер ведомому. Ждём одну секунду. В этой секунде небольшая загвоздка: функция передачи неблокирующая, мы посылаем пакет и идём дальше, а программа крутится быстрее, чем происходит передача, обработка данных ведомым и приём пакета от него. Таким образом, мы послали пакет, идём дальше, а так как пакет ещё не передан и не принят ответ, проверка данных говорит, что их нет, и мы идём посылать запрос для другого датчика. От его имени опять посылается пакет, он ставится в очередь, а первый пакет уже передался и RS485 переключается на приём. При этом передача продолжается, но уже в никуда, а принятый пакет теперь не соответствует запросу и ничего не работает, на линии мешанина. Поэтому здесь между передачей и приёмом стоит задержка. За это время пакет успевает уйти и приняться ответ, нужно в будущем продумать этот момент и как-то поправить библиотеку. Как только задержка отработала, у нас в буфере лежат данные, если не произошло сбоя при сеансе связи. Нам остаётся только вывести данные на экран.

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

if(myTimer.isReady())                                                       // Если таймер сработал, запускаем измерение с датчиков
{
  Data_DS01.DS_Temp = myDS.getTemp(Address_DS1);                            // Чтение первого датчика DS18B20
  for(uint8_t I=0; I<8; I++) Data_DS01.Address_DS[I] = Address_DS1[I];      // Набиваем номер датчика в массив
  Data_DS02.DS_Temp = myDS.getTemp(Address_DS2);                            // Чтение второго датчика DS18B20
  for(uint8_t I=0; I<8; I++) Data_DS02.Address_DS[I] = Address_DS2[I];      // Набиваем номер датчика в массив
  Data_HDC.HDC_Temp = myHDC.readTemperature();                              // Читаем температуру
  Data_HDC.HDC_Hum  = myHDC.readHumidity();                                 // и влажность с датчика HDC1080
}

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

CountRX = myRS.Receive(BufferRX);                                           // Проверяем не стучаться ли
if(CountRX > 0)                                                             // Постучались
{
  switch (BufferRX[0])                                                      // Проверяем, кто именно пришёл
  {
    case SENSOR_HDC:
      myRS.Send((uint8_t*) &Data_HDC, sizeof(Data_HDC));                    // и отдаём данные с датчика HDC1080
      break;
    case SENSOR_DS1:
      myRS.Send((uint8_t*) &Data_DS01, sizeof(Data_DS01));                  // с первого датчика DS18B20
      break;
    case SENSOR_DS2:
      myRS.Send((uint8_t*) &Data_DS02, sizeof(Data_DS02));                  // со второго датчика DS18B20
      break;
    default:
      break;
  }
  myDS.requestTemp();                                                       // Запускаем измерение DS18B20
}

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

Все материалы и библиотеки находятся на яндекс диске, дополнительная информация на форуме, примеры в каталоге Example. В этот раз примеры не привязаны ни к какому МК, они будут работать на любом, где написаны драйверы I2C и UART. Если убрать работу с датчиком HDC1080, тогда код работает вообще на любом МК, я практически везде уже дописал библиотеку UART.

Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x