Дисплеи, датчики и прочие функциональные узлы, управляемые МК.
Ответить

PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 16:59:57

Пытаюсь заставить работать этот датчик и пик вместе, попутно выводя результат на ЖКИ. Нашел неплохое объяснение работы и подключение к МК (https://electrosome.com/hc-sr04-ultrasonic-sensor-pic/). Но вот работать всё это не хочет, пишет "вне радиуса". Хоть в протеусе, хоть в железе. Но всё остальное работает нормально, датчик работает, экран работает, импульсы на датчик приходят и он выдаёт вполне вменяемый результат, смотрел осциллографом. Начал копаться в программе, полностью откомментировал, но вот где ошибка так и не нашёл.

Думал что постоянно в прерывании кручусь, поскольку нет записи или чтения из PORTB, как сказано в даташите, чтобы исключить несоответствие и сбросить флаг прерываний, но нет, раз импульсы от МК есть и текст выводится, значит дело в таймере или расчёте переменной.

Проверил таймер, настраивается с предделителем 1:2, отсчёт начинает только в прерывании. Формула расчёта, вроде бы, верная.

Спойлер
Код:
// подключение ЖКИ модуля
sbit LCD_RS at RD2_bit;
sbit LCD_EN at RD3_bit;
sbit LCD_D7 at RC7_bit;
sbit LCD_D6 at RD4_bit;
sbit LCD_D5 at RD5_bit;
sbit LCD_D4 at RD6_bit;

sbit LCD_RS_Direction at TRISD2_bit;
sbit LCD_EN_Direction at TRISD3_bit;
sbit LCD_D7_Direction at TRISC7_bit;
sbit LCD_D6_Direction at TRISD4_bit;
sbit LCD_D5_Direction at TRISD5_bit;
sbit LCD_D4_Direction at TRISD6_bit;
// завершение подключения ЖКИ модуля

// переменные используемые в программе
int a;

// обработка прерываний
void interrupt()
{
  if(INTCON.RBIF == 1)                 // убеждаемся что прерывание от PORTB
  {
    INTCON.RBIE = 0;                   // отключение прерываний
    if(PORTB.F4 == 1)                  // если на выводе RB4 (ECHO) лог.1
      T1CON.F0 = 1;                    // запуск таймера TMR1
    if(PORTB.F4 == 0)                  // если на выводе RB4 (ECHO) лог.0
    {
      T1CON.F0 = 0;                    // остановка таймера TMR1
      a = (TMR1L | (TMR1H<<8))/58.82;  // рассчёт расстояния, перевод в см
    }
  }
  INTCON.RBIF = 0;                     // сброс флага прерывания PORTB
  INTCON.RBIE = 1;                     // разрешение прерываний PORTB
}
// конец обработки прерываний

// основная программа
void main()
{
  char txt[7];
  Lcd_Init();                        // Инициализация ЖКИ
  Lcd_Cmd(_LCD_CLEAR);               // Очистка экрана
  Lcd_Cmd(_LCD_CURSOR_OFF);          // Отключение курсора

  TRISB = 0b00010000;
  INTCON.GIE = 1;                    // Разрешение глобальный прерываний
  INTCON.RBIF = 0;                   // сброс флага прерывания PORTB
  INTCON.RBIE = 1;                   // разрешение прерываний PORTB

  Lcd_Out(1,6,"Digital");            // вывод приветствия
  Lcd_Out(2,4,"level meter");

  Delay_ms(3000);                    // задержка 3 с
  Lcd_Cmd(_LCD_CLEAR);               // очистка экрана

  T1CON = 0x10;                      // настройка таймера TMR1
 
  while(1)
  {
     TMR1H = 0;                      // сброс значения таймера первого регистра
     TMR1L = 0;                      // сброс значения таймера второго регистра

     a = 0;                          // сброс переменной измерения

     PORTB.F0 = 1;                   // установка лог.1 на выводе RB0 (TRIG)
     Delay_us(10);                   // задержка 10 мкс
     PORTB.F0 = 0;                   // установка лог.0 на выводе RB0 (TRIG)
     
     Delay_ms(100);                  // задержка 100 мс

     a = a + 1;                      // постоянная для исключения ошибки
     if(a>=2 && a<=400)              // проверка расстояния
     {
       IntToStr(a,txt);              // перевод значения в строку для ЖКИ
       Ltrim(txt);                   // удаление непечатных симлов перед строкой
       Lcd_Cmd(_LCD_CLEAR);          // очистка экрана
       Lcd_Out(1,1,"Distance = ");   // вывод текста на 1 строке
       Lcd_Out(1,12,txt);            // вывод результата на 2 строке
       Lcd_Out(1,15,"cm");           // вывод ед.измерения на 2 строке
     }
     else
     {
       Lcd_Cmd(_LCD_CLEAR);          // очистка экрана
       Lcd_Out(1,1,"Out of Range");  // вывод текста на 1 строке
     }
     Delay_ms(400);                  // задержка 400 мс

  }
}


Есть какие либо мысли? Потому что совсем не представляю как у автора это заработало.

Re: PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 17:22:39

От чего не использовать модуль CCP в режиме захвата? А дальше интерпретировать значения tmr2.

Re: PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 18:04:36

То есть использовать метод указанный в 3-м примере (http://www.radioradar.net/hand_book/doc ... _shim.html)?

Не одно и тоже ли это? К тому же придется перенастраивать модуль в прерывании на спад импульса. Хотя если заработает, можно и так оставить. Проверю завтра.

Re: PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 18:17:16

Лучше спросить в конференции микрочип, там тёртые парни. Когда делал толщиномер использовал ccp. Измерял длительность импульса.

Re: PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 18:39:11

Ладно, попробую захватом сделать. Да и может кто нибудь еще, идею подкинет.

Re: PIC16F877 и УЗ датчик HC-SR04

Чт июн 06, 2019 18:55:59

У меня студент недаавно делал проект дальномера на этом модуле, правда, на другом МК. Может заинтересует/поможет.

Re: PIC16F877 и УЗ датчик HC-SR04

Пт июн 07, 2019 17:05:16

Ser60, посмотрел основной цикл программы. В целом всё тоже самое, только написано, конечно же, под другой МК.

Попытался понять где собака зарыта и пришёл к такому выводу, что прерывания обрабатываются не дожидаясь окончания ответа дальномера. На осциллограмме снизу это можно понять:
СпойлерИзображение

Желтая линия (канал А), это импульс на начало измерения дальномеру длительностью 10 мкс.
Синяя линия (канал В), это это ответ датчика.
Розовая линия (канал С), это время выполнения основной программы.
Зеленая линия (канал D) это время выполнения прерывания.

Как видно, обработка прерывания составляет 4 мкс (замерил позже) и завершается задолго до окончания ответа дальномера (синяя линия). Что дальше МК делает (розовая линия), целых 511 мс, мне непонятно, поскольку время выполнения основной программы отслеживаю в начале вечного цикла, устанавливая лог. 1 на одном из выводов и сбрасывая в начале обработчика прерываний.
СпойлерИзображение

Но при всём этом таймер запускается, но не останавливается.

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

Re: PIC16F877 и УЗ датчик HC-SR04

Пт июн 07, 2019 18:00:28

В моём примере таймер работает постоянно и сконфигурирован на аппаратное обнуление по нарастающему фронту импульса от датчика. Также он сконфигурирован на захват по падающему уровню этого импульса и генерацию прерывания, пробуждающее МК. Точнее, использованы два 16-битных таймера, включённые последовательно (как 32-битный) для исключения переполнения при измерении периода и обнуление и захват в них производится синхронно на аппаратном уровне. Я не помню, возможно-ли такое в PIC, если нет, то можно считать число переполнений в отдельной переменной.
Посмотрел Пример 3 по ссылке выше и полностью согласен с приведенным в нём алгоритмом. Как там правильно отмечено, вместо остановки таймера следует сконфигурировать его на захват для повышения точности измерений.

Re: PIC16F877 и УЗ датчик HC-SR04

Пт июн 07, 2019 18:41:18

Да, способ хороший. Но как я сегодня не пытался, микроконтроллеру плевать на изменения уровня RB4-RB7, поэтому он ни как не хочет генерировать прерывание по этим выводам. Придётся разбираться дальше, смотреть что не докурил по этому способу и пробовать еще раз.

Ну а что касается первоначального способа. Удалось заставить его работать. Пришлось изменить всего лишь один оператор if на while в обработчике прерываний, поменять местами RB0 и RB4 по подключению датчика и добавить пару строчек в конфигурации выводов. Хотя сомневаюсь что они что-то изменили.
Спойлер
Код:
// подключение ЖКИ модуля
sbit LCD_RS at RD2_bit;
sbit LCD_EN at RD3_bit;
sbit LCD_D7 at RC7_bit;
sbit LCD_D6 at RD4_bit;
sbit LCD_D5 at RD5_bit;
sbit LCD_D4 at RD6_bit;

sbit LCD_RS_Direction at TRISD2_bit;
sbit LCD_EN_Direction at TRISD3_bit;
sbit LCD_D7_Direction at TRISC7_bit;
sbit LCD_D6_Direction at TRISD4_bit;
sbit LCD_D5_Direction at TRISD5_bit;
sbit LCD_D4_Direction at TRISD6_bit;
// завершение подключения ЖКИ модуля

// переменные используемые в программе
int a;

// обработка прерываний
void interrupt()
{
  if(INTCON.INTF == 1)                 // убеждаемся что прерывание от PORTB
  {
    INTCON.INTE = 0;                   // отключение прерываний
    while (PORTB.F0 = 1)                  // если на выводе RB0 (ECHO) лог.1
      T1CON.F0 = 1;                    // запуск таймера TMR1
    if (PORTB.F0 == 0)                  // если на выводе RB0 (ECHO) лог.0
    {
      T1CON.F0 = 0;                    // остановка таймера TMR1
      a = (TMR1L | (TMR1H<<8))/58.82;  // рассчёт расстояния, перевод в см
    }
  }
  INTCON.INTF = 0;                     // сброс флага прерывания PORTB
  INTCON.INTE = 1;                     // разрешение прерываний PORTB
}
// конец обработки прерываний

// основная программа
void main()
{
  char txt[7];
 
  ANSEL = 0;                         // установка портов в качестве цифровых
  ANSELH = 0;                        // входов/выходов
  C1ON_bit = 0;                      // отключение комапараторов
  C2ON_bit = 0;
 
  Lcd_Init();                        // Инициализация ЖКИ
  Lcd_Cmd(_LCD_CLEAR);               // Очистка экрана
  Lcd_Cmd(_LCD_CURSOR_OFF);          // Отключение курсора

  TRISB = 0b11101111;                // настройка PORTB
  PORTB = 0;                       // сброс значений PORTB
  INTCON.INTF = 0;                   // сброс флага внеш. прерывания (RB0)
  INTCON.INTE = 1;                   // разрешение внеш. прерывания (RB0)
  OPTION_REG.INTEDG = 1;             // прерывание по фронту (RB0)
  //INTCON.PEIE = 1;                 // разрешение прерываний от периферии
  INTCON.GIE = 1;                    // разрешение глобальный прерываний
  //INTCON.RBIF = 0;                   // сброс флага прерывания PORTB
  //INTCON.RBIE = 1;                   // разрешение прерываний PORTB

  Lcd_Out(1,6,"Digital");            // вывод приветствия
  Lcd_Out(2,4,"level meter");

  Delay_ms(3000);                    // задержка 3 с
  Lcd_Cmd(_LCD_CLEAR);               // очистка экрана

  T1CON = 0x00;                      // настройка таймера TMR1
 
  while(1)
  {
     TMR1H = 0;                      // сброс значения таймера первого регистра
     TMR1L = 0;                      // сброс значения таймера второго регистра

     a = 0;                          // сброс переменной измерения

     PORTB.F4 = 1;                   // установка лог.1 на выводе RB4 (TRIG)
     Delay_us(10);                   // задержка 10 мкс
     PORTB.F4 = 0;                   // установка лог.0 на выводе RB4 (TRIG)
     
     Delay_ms(100);                  // задержка 100 мс
     
     a = a + 1;                      // постоянная для исключения ошибки
     if(a>=2 && a<=400)              // проверка расстояния
     {
       IntToStr(a,txt);              // перевод значения в строку для ЖКИ
       Ltrim(txt);                   // удаление непечатных симлов перед строкой
       Lcd_Cmd(_LCD_CLEAR);          // очистка экрана
       Lcd_Out(1,1,"Distance = ");   // вывод текста на 1 строке
       Lcd_Out(1,12,txt);            // вывод результата на 2 строке
       Lcd_Out(1,15,"cm");           // вывод ед.измерения на 2 строке
     }
     else
     {
       Lcd_Cmd(_LCD_CLEAR);          // очистка экрана
       Lcd_Out(1,1,"Out of Range");  // вывод текста на 1 строке
     }
     Delay_ms(400);                  // задержка 400 мс

  }
}

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