Программирование AVR: работа с USB Проблема совместного использования кода 1-Wire и V-USB Thu, October 19 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Проблема совместного использования кода 1-Wire и V-USB Печать
Добавил(а) microsin   

К сожалению, простое использование кода чтения шины 1-Wire (например, термодатчиков DS1820 или DS18B20) вместе с библиотекой V-USB приводит к ошибкам. Причина в том, что шина 1-Wire требует формирования точных и коротких интервалов времени. Но к сожалению, прерывания и сигналов USB и код обработки протокола USB не дают возможности точно выдерживать такие интервалы. Вот пример низкоуровневых подпрограмм, работающих с шиной 1-Wire:

//=============================================================================
// Функции для работы с 1-Wire
// Тексты независящих от платформы функций (поиск устройств - OWFirst, OWNext и
// OWSearch) взяты из документа Dallas Semiconductor: 
// "Application Note 187: 1-Wire Search Algorithm"
// http://www.maxim-ic.com/appnotes.cfm/appnote_number/187
//=============================================================================
// Отправка одного бита данных по шине 1-Wire.
//-----------------------------------------------------------------------------
void OWWriteBit(u8 bit_value)
{
#ifdef ONE_WIRE_HAS_PRIORITY
   cli();
#endif
   if(bit_value == 0)
   {
      DQDDR |= _BV(DQ);
      _delay_us(60);
      DQDDR &= ~_BV(DQ);
      _delay_us(10);
   }
   else
   {
      DQDDR |= _BV(DQ);
      _delay_us(3);
      DQDDR &= ~_BV(DQ);
      _delay_us(64);
   }
#ifdef ONE_WIRE_HAS_PRIORITY
   sei();
#endif
}
 
//-----------------------------------------------------------------------------
// Отправка 8 бит данных по шине 1-Wire.
//-----------------------------------------------------------------------------
void OWWriteByte(u8 byte_value)
{
   u8 i;
 
   for(i = 0; i < 8; i++) 
   {
      OWWriteBit(byte_value & 0x01);
      byte_value >>= 1;
   }
}
 
//-----------------------------------------------------------------------------
// Чтение одного бита данных с шины 1-Wire.
// Вернет 1, если бит читается как 1, иначе вернет 0.
//-----------------------------------------------------------------------------
u8 OWReadBit(void)
{
   u8 ret;
 
#ifdef ONE_WIRE_HAS_PRIORITY
   cli();
#endif
   DQDDR |= _BV(DQ);
   _delay_us(6);
  DQDDR &= ~_BV(DQ);
   _delay_us(9);
   ret = (DQPIN & _BV(DQ));
   _delay_us(55);
#ifdef ONE_WIRE_HAS_PRIORITY
   sei();
#endif
   return ret >> DQ; // Значение бита - в младшем разряде
}
 
//-----------------------------------------------------------------------------
// Чтение байта с шины 1-Wire.
//-----------------------------------------------------------------------------
u8 OWReadByte(void)
{
   u8 i, mask = 0x01, ret = 0x00;
 
   for(i = 0; i < 8; i++) 
   {
      if(OWReadBit())
         ret |= mask;
      mask << = 1;
   }
   return ret;
}
 
u8 OWReset (void)
{
   u8 ret;
#ifdef ONE_WIRE_HAS_PRIORITY
   cli();
#endif
   DQDDR |= (1 << DQ);
   _delay_us(480);
   DQDDR &= ~(1 << DQ);
   _delay_us(70);
   ret = (DQPIN & (1 << DQ));
   _delay_us(410);
#ifdef ONE_WIRE_HAS_PRIORITY
   sei();
#endif
   return (0 == ret);
}

Здесь для формирования задержек протокола 1Wire используются функции _delay_us. Очевидно, что для точных задержек требуется, чтобы во время формирования задержки использование процессора было монопольным, т. е. нужно запретить прерывания. Это как раз и делает макрос ONE_WIRE_HAS_PRIORITY - если его определить, то в приоритете будет читаться шина 1-Wire, а если нет, то приоритет будет отдан прерываниям библиотеки V-USB. Однако проблему это никак не решает - либо будет надежно читаться шина 1-Wire, либо будет надежно работать код обслуживания USB. 

Если дать приоритет шине 1-Wire (определить макрос ONE_WIRE_HAS_PRIORITY), то обычно работа USB становится непредсказуемой, зависимой от частоты опроса 1-Wire и момента вызова опроса. Устройство USB отваливается, перестает передавать данные и т. п. Если дать приоритет USB, то тогда начинает ненадежно работать шина 1-Wire. Например, при чтении термодатчика DS18B20 некоторые чтения будут давать совершенно неправильный результат - вместо 28 градусов Цельсия может быть прочитано 48, или даже 6144 градуса, и т. п.

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

Проверка на предельные значения еще проще. По паспорту датчик DS18B20 может работать в диапазоне температур -55 .. +125 градуса по Цельсию. Следовательно, нужно просто отбрасывать результаты чтения термодатчика, которые не попадают в этот диапазон.

Пример кода, который выполняет такую фильтрацию измерений, приведен ниже. Он может работать, когда приоритет коду V-USB (т. е. прерывания во время формирования задержек разрешены), и рассчитан на то, что датчик DS18B20 опрашивается по шине 1-Wire с интервалом 1 раз в секунду.

//Объединение, сделанное для удобного преобразования
// байтового представления температуры в число со знаком.
typedef union 
{
   u16 u;
   s16 s;
}convT;
 
//Функция принимает на входе прочитанное значение температуры
// (в формате с фиксированной точкой b000000000000000.0).
//Вернет 0, если чтение температуры было ошибочно (не совпадает
// с предыдущими тремя значениями, накопленными в массиве etalons),
// иначе вернет 1.
static bool isReadingTemperatureOK (u16 temperature)
{
   #define DEEP_CHECK 3
 
   convT cv;
   static u16 etalons[DEEP_CHECK];
   u8 idx;
   bool alleq;
 
   //Проверка 1, на диапазон:
   cv.u = temperature >> 1;
   if ((cv.s <= -55) || (cv.s >= 125))
      return false;
 
   //Проверка 2, на "одинаковость":
   alleq = true;
   for (idx=0; idx < DEEP_CHECK; idx++)
   {
      //Проверка на одинаковость:
      if (cv.u != etalons[idx])
         alleq = false;
      if(idx < (DEEP_CHECK-1))
      {
         //Сдвиг стека "одинаковостей" влево.
         etalons[idx] = etalons[idx+1];
      }
      else
      {
         //Сохранение последнего результата.
         etalons[idx] = cv.u;
      }
   }
   return alleq;
}

У Сергея Кухтецкого [1] каким-то чудом получилось читать термодатчик DS1820 без дополнительной обработки - наверное потому, что момент обращения по шине 1-Wire совпадал по времени с подачей запроса по шине USB. Однако не факт, что код, который нормально работает на 16 МГц, будет также работать на других частотах, допустимых для библиотеки V-USB.

Примечание: капитан Очевидность конечно заметит, что все проблемы будут решены, если совместно с протоколом 1-Wire для работы с USB использовать микроконтроллеры AVR USB (т. е. с аппаратной поддержкой USB). Ну что же, он прав на 100%.

[Ссылки]

1. AVR-USB-MEGA16: измеряем и контролируем температуру.

 

Добавить комментарий


Защитный код
Обновить

Top of Page