Простой робот с датчиками

Материал из LicrymWiki

Перейти к: навигация, поиск

Итак, до этого мы собрали простого робота без датчиков, который ездит в случайных направлениях: Простой робот

Позже на портале был разработан простой ИК датчик препятствия:ИК бампер

Теперь было бы хорошо объединить эти две вещи. О том как работает датчик или робот подробно описано в предыдущих статьях, поэтому повторяться не будем.

Изображение:Robot common view.jpg

Изображение:Robot common view2.jpg

[править] Аппаратная часть

Следует отметить что приведенные инструкции скорее всего нельзя повторить дословно, так как маловероятно что попадется такое же шасси, такие же детали, скорее всего будут отличия, например у вашего шасси электромотор будет потреблять меньше и дополнительный мост не потребуется, или вместо фототранзисторов как на схеме будут использованы аналоги. Рассматривайте текст только лишь как указание на общие принципы и идеи, воплощение которых процесс творческий, а результат индивидуальный. Итак, было собранно 6 ИК датчиков на маленьких платах с 4 контактным разъемом на каждой. Для оптической изоляции светодиода от фототранзистора на последние были надеты трубочки из черной изоленты. Датчики закреплены на шасси по 6 направлениям:

Вперед, F (номер 0)
Вперед налево, FL (номер 1)
Вперед направо, FR (номер 2)
Назад налево, BL (номер 3)
Назад, B (номер 4)
Назад направо, BR (номер 5)

В дальнейшем направления будут обозначаться именно так – одной или 2 буквами.

По схеме выводы 1,2,3 (питание, земля, включение светодиодов) соединены вместе. Выводы 4 (выход сигнала) заведены по каналам АЦП. Так как из-за разброса параметров деталей чувствительность датчиков может отличаться, то в программе введен массив ~gPorog, значения которого индивидуально задают порог срабатывания, что позволяет программно выровнять чувствительность. Так как тяговый мотор потребляет до 1,5 А при трогании (а микросхема-драйвер рассчитана всего на 0,6 А), то для него был отдельно сделан Н мост на ~MOSFET транзисторах.

В процессе написания программы обнаружился баг – не работали датчики, как в последствии оказалось – из-за тепловых шумов (см. процедуру опроса в коде). Для его поисков были установлены 6 зеленых светодиодов – как отладочный инструмент. Их установка необязательна.


[править] Программная часть

Программа робота является развитием программы робота Conqerror проекта Железный Феникс (http://www.ironfelix.ru/). В программе 2 таблицы вероятностей, одна рабочая, которая динамически корректируется, исходя из показаний датчиков, а вторая эталонная, ее значения соответствуют распределению вероятностей при отсутствии препятствий. В случае, если обнаруживается препятствие, то программа обнуляет колонку с направлением движения в сторону препятствия, распределяя ее вероятность поровну между разрешенными направлениями. Если препятствие исчезло, то программа восстанавливает запрещенное направление, но не сразу, а на некоторое количество шагов, заданных вначале. Это позволяет избежать дрожания перед препятствием.

Код:

/*****************************************************
This program was produced by the
CodeWizardAVR V1.25.7 beta 5 Professional
Automatic Program Generator
© Copyright 1998-2007 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Project : 
Version : 
Date    : 02.03.2008 - 14.04.2008
Author  : Spiritus Sancti                            
Company : licrym.org                            
Comments: CC BY-NC-SA 


Chip type           : ATmega8
Program type        : Application
Clock frequency     : 16,000000 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 256
*****************************************************/

#include <mega8.h>
#include <stdlib.h>
#include <delay.h> 

#define ADC_VREF_TYPE 0x40   

#define gTimeOfMove 976       //время элементарного хода в мсек (1 сек)  
#define StepsOfRecover 4      //количество шагов, за которые восстанавливается направление 




unsigned int gtime; 
bit ADC_complete;                     
unsigned char sensor_state, sensor_state_last;     /*переменная состояния датчиков,
 соответствующий бит отвечает за соответствующий датчик*/
unsigned int gADC_result;
unsigned char now, h;
//расположение датчиков
//
//      1   0   2
//        \ | /
//         [ ]
//        / | \
//       3  4  5

const unsigned char gporog[6]={30, 30, 30, 30, 30, 30};       /*массив индивидуальных порогов 
срабатывания сенсоров*/
unsigned char d[7][7]={  /*таблица вероятностей переходов, 
строки - текущее движение, столбцы - следующее.*/
//          STO    F   FR    FL    B   BR   BL
/*STO*/     {10,  15,  15,   15,  15,  15,  15},
/*F  */     {17,  23,  25,   25,   0,   0,   0},
/*FR */     {17,  23,  25,   25,   0,   0,   0},
/*FL */     {17,  33,  25,   25,   0,   0,   0},
/*B  */     {25,   0,   0,    0,  25,  25,  25},
/*BR */     {25,   0,   0,    0,  25,  25,  25},
/*BL */     {25,   0,   0,    0,  25,  25,  25}
};   

const unsigned char etalon[7][7]={  /*таблица вероятностей переходов, 
образец из которого восстанавливать будем*/
//          STO    F   FR    FL    B   BR   BL
/*STO*/     {10,  15,  15,   15,  15,  15,  15},
/*F  */     {17,  23,  25,   25,   0,   0,   0},
/*FR */     {17,  23,  25,   25,   0,   0,   0},
/*FL */     {17,  33,  25,   25,   0,   0,   0},
/*B  */     {25,   0,   0,    0,  25,  25,  25},
/*BR */     {25,   0,   0,    0,  25,  25,  25},
/*BL */     {25,   0,   0,    0,  25,  25,  25}
};   



interrupt [TIM1_COMPA] void timer1_compa_isr(void)  //прерывание по таймеру раз в 1/976 сек
{
  if (gtime!=0) --gtime;   
}  
     
void delay (unsigned int time)                      //на вход задержка милисекундах, макс 65535
{   
  gtime=time;
  while(gtime!=0){};
}     



// ADC interrupt service routine
interrupt [ADC_INT] void adc_isr(void)              //прерывание по завершению АЦП
{ 
  unsigned int adc_data;
  // Read the AD conversion result
  adc_data=ADCW;
  gADC_result=adc_data;
  ADC_complete=1;
}
                                           
int poll_sensor(unsigned char SensorNumber)         //процедура опроса сенсора
{  
  int delta, result1, result2; 
  ADMUX&=0b11110000;                                //сбрасываем биты микшера перед выбором входа
  switch(SensorNumber)                              //включаем соответствующий вход
  { 
     case 0:
        ADMUX|=0b00000000;
        break;
     case 1:
        ADMUX|=0b00000001;
        break;
     case 2:
        ADMUX|=0b00000010;
        break;
     case 3:
        ADMUX|=0b00000011;
        break;
     case 4:
        ADMUX|=0b00000100;
        break;            
     case 5:
        ADMUX|=0b00000101;
        break;
     default:
        ADMUX|=0b00000000;
        break;
  };
  PORTD|=1<<5;                                      //Светодиод на 5 ноге порта D
  delay_us(50);                                     //ждем 50 мкс пока светодиод разгорится
  ADCSRA|=0x40;                                     //записываем бит запускающий процесс измерения
  while(ADC_complete!=1);                           //ждем пока не закончится измерение, проверяя флаг
  result1=gADC_result;
  ADC_complete=0;                                   //сбрасываем флаг
 
  PORTD&=~(1<<5);                                   //выключаем светодиод и снова измеряем
  delay_us(10);
  ADCSRA|=0x40;
  while(ADC_complete!=1);
  result2=gADC_result;
  delta=result2-result1;                            //Если вдруг из-за шумов у нас result2<result1 то 
  if (delta < 0) delta=0;                           /*получается отрицательное значение и сбой.
 Это грабля.*/
  ADC_complete=0;
  return delta;                                     //возвращаем значение разности 
} 

void show_sensors (void)                            /*вывести значения сенсоров на отладочные
 светодиоды */
{
  if(sensor_state & 0b00000001) PORTD|=1<<6; else PORTD&=~(1<<6);
  if(sensor_state & 0b00000010) PORTD|=1<<7; else PORTD&=~(1<<7);
  if(sensor_state & 0b00000100) PORTB|=1<<0; else PORTB&=~(1<<0);
  if(sensor_state & 0b00001000) PORTB|=1<<1; else PORTB&=~(1<<1);
  if(sensor_state & 0b00010000) PORTB|=1<<2; else PORTB&=~(1<<2);
  if(sensor_state & 0b00100000) PORTB|=1<<3; else PORTB&=~(1<<3);
}

void check_env(void)                                //процедура проверки окружающего пространства 
{   
  unsigned char i, a, b, c;

  for(i=0; i<6; i++)                                //в цикле опросим все сенсоры
  {      
    a=poll_sensor(i);                               //опрос сенсора 3 раза для исключения шума
    b=poll_sensor(i);                               
    c=poll_sensor(i);
    if(c>=gporog[i]) c=1; else c=0;
    if(b>=gporog[i]) b=1; else b=0;
    if(a>=gporog[i]) a=1; else a=0;
    c=c+b+a;
    if(c>=2)sensor_state|=1<<i; 
     else sensor_state&=~(1<<i);                    /*если сенсор сработал то 1 в соотв бит
 переменной состояний, если нет - то сбраcываем соотв бит*/
  }; 
  show_sensors();
}
  
void delay_walk (unsigned int time)                 //на вход задержка милисекундах, макс 65535, 
{   /*схлапывающаяся задержка, обнуляется как только обнаруживается изменение показаний сенсоров*/
  gtime=time;
  while(gtime!=0)
  {
      check_env();
      if (sensor_state != sensor_state_last)        /*Если текущее состояние сенсоров необработано
 - рвем задержку*/
      {                                             //время проверки окружения 50 мкс (0,5мс)
        break;
      };                              
  };                                   
} 

//далее функции управления двигателями
void f(int t)                                       // Вперед
{   
 PORTD&=~(1<<1); //PORTD.1=0;
 PORTD|=1<<2;    //PORTD.2=1;
 PORTD&=~(1<<3); //PORTD.3=0;
 PORTD&=~(1<<4); //PORTD.4=0;
 if(t!=0) delay_walk(t);
}

void b(int t)                                       // назад
{  
 PORTD|=1<<1;    //PORTD.1=1;
 PORTD&=~(1<<2); //PORTD.2=0;
 PORTD&=~(1<<3); //PORTD.3=0;
 PORTD&=~(1<<4); //PORTD.4=0;
 if(t!=0) delay_walk(t);
}

void fl(int t)                                      // вперед налево
{   
 PORTD&=~(1<<1); //PORTD.1=0;
 PORTD|=1<<2;    //PORTD.2=1;
 PORTD&=~(1<<3); //PORTD.3=0;
 PORTD|=1<<4;    //PORTD.4=1;
 if(t!=0) delay_walk(t);
} 

void fr(int t)                                      // вперед направо
{  
 PORTD&=~(1<<1); //PORTD.1=0;
 PORTD|=1<<2;    //PORTD.2=1;
 PORTD|=1<<3;    //PORTD.3=1;
 PORTD&=~(1<<4); //PORTD.4=0;
 if(t!=0) delay_walk(t);
}  

void bl(int t)                                      // назад налево
{ 
 PORTD|=1<<1;    //PORTD.1=1;
 PORTD&=~(1<<2); //PORTD.2=0;
 PORTD&=~(1<<3); //PORTD.3=0;
 PORTD|=1<<4;    //PORTD.4=1;
 if(t!=0) delay_walk(t);
} 

void br(int t)                                      // назад направо
{ 
 PORTD|=1<<1;    //PORTD.1=1;
 PORTD&=~(1<<2); //PORTD.2=0;
 PORTD|=1<<3;    //PORTD.3=1;
 PORTD&=~(1<<4); //PORTD.4=0;
 if(t!=0) delay_walk(t);
}
 
void stop(int t)                                    //стоп
{ 
 PORTD&=~(1<<1); //PORTD.1=0;
 PORTD&=~(1<<2); //PORTD.2=0;
 PORTD&=~(1<<3); //PORTD.3=0;
 PORTD&=~(1<<4); //PORTD.4=0;
 if(t!=0) delay_walk(t);
} 

void led_on(void)                                   //Включить светодиод            
{ 
 PORTD|=1<<0;    //PORTD.0=1;
} 

void led_off(void)                                  //Выключить светодиод
{ 
PORTD&=~(1<<0); //PORTD.0=0;
}    

void go(unsigned char napr)                         /*Выборка направления и отдача команды 
двигателям  */
{     
  switch(napr)
  {
     case 0:
        stop(gTimeOfMove);
        break; 
     case 1:
        f(gTimeOfMove);
        break;
     case 2:
        fr(gTimeOfMove);
        break;
     case 3:
        fl(gTimeOfMove);
        break;
     case 4:
        b(gTimeOfMove);
        break;
     case 5:
        br(gTimeOfMove);
        break;
     case 6:
        bl(gTimeOfMove);
        break;
  };
}   
                                                     
void disable_way(unsigned char col)                     /*функция, запрещающая полученую колонку
 таблицы*/
{
unsigned char zeroes, notZeroes, i, s, part;
   for (s = 0; s <= 6; s++)                             //для каждой строки таблицы вероятностей
   { 
     for (i=0; i<=6; i++)                               /*для текущей строки найдем кол-во нулей,
 за исключением текущей ячейки заданной колонки*/
     {                                                  /*нуль - уже запрещенное направление,
 мы должны найти количество пока еще разрешенных*/
       if (d[s][i]==0 && i!=col) zeroes++;              /*и найдя их количество раскидываем 
вероятность текущей ячейки меж ними поровну*/
     };
     notZeroes = 6 - zeroes;                            
     part = d[s][col] / notZeroes;                      
     
     for (i=0; i<=6; i++)
     {                                             
       if (d[s][i]!=0 && i!=col) d[s][col]+=part;       
     };                                             
     
     d[s][col]=0;                                       //собственно само обнуление текущей ячеки
   
   };
}   

void enable_way (unsigned char col)                     /*функция восстановления значений таблицы 
вероятностей*/
{unsigned char s;
   for (s=0; s<=6; s++)                                 //для каждой строки таблицы вероятностей
   {                                                    /*восстанавливаем табл. на (текущ - 
знач.эталон)/(кол-во ходов для восстановления)*/
        if (d[s][col] != etalon[s][col])                //за один раз
        { 
            if (d[s][col] > etalon[s][col])
            {
            d[s][col]-= etalon[s][col] / StepsOfRecover;
            };
            
            
            if (d[s][col] < etalon[s][col]) 
            {
            d[s][col]+= etalon[s][col] / StepsOfRecover;
            };
                      
        };
               
   };
  
}




unsigned char next_move(void)                           //выясняем следующий ход     
{   
  int a, b, c;
  
  do
  {
        a = rand()/327; 		                //0..99 число 
        c=0;                                                
        for (b=0; b<7; b++)                             //поик по таблице
        {      	  
            if(a > c && a < (d[now][b]+c) ) break;
            c=c+d[now][b];
        };
  }
  while(b==7);                                          /*Если сумма вероятностей в строке не 100,
 то возможно пролететь */
                                                        /*всю таблицу без выбора и тогда b==7. 
Если такое случается, то снова ищем но с новым случайным числом*/
  now = b;
  return b;
};    

 
void decision(void)                                     /*функция, которая будет принимать решения
 на базе показаний датчиков*/
{ 
  /* //Кусок кода, для распознавания препятствий по 6 направлениям
  if(sensor_state & 0b00000010) disable_way(3); else enable_way(3);   //FL sensor
  if(sensor_state & 0b00000100) disable_way(2); else enable_way(2);   //FR sensor
  if(sensor_state & 0b00001000) disable_way(6); else enable_way(6);   //BL Sensor
  if(sensor_state & 0b00100000) disable_way(5); else enable_way(5);   //BR Sensor
  if(sensor_state & 0b00010000) 
  {
    disable_way(4);
    disable_way(5);
    disable_way(6);
     
  } else enable_way(4);   //B sensor
  if(sensor_state & 0b00000001)
  {
    disable_way(1);
    disable_way(2);
    disable_way(3);
     
  } else enable_way(1);   //F sensor
  */
  
  //Кусок кода для распознавания препятствий по 2 направлениям
  if (sensor_state & 0b00000001 || sensor_state & 0b00000010 || sensor_state & 0b00000100)    //front
  {
    disable_way(1);
    disable_way(2);
    disable_way(3);
  } else 
  {
    enable_way(1);
    enable_way(2);
    enable_way(3);
  };
  
  if (sensor_state & 0b00010000 || sensor_state & 0b00001000 || sensor_state & 0b00100000) //back
  { 
    disable_way(4);
    disable_way(5);
    disable_way(6);
  } else
  {
    enable_way(4);
    enable_way(5);
    enable_way(6);
  };
  
  sensor_state_last = sensor_state;
  
} 

 
void start(void)                                        //начинаем движение
{   
  while(1)
  {                                                     /*проверяем окружение, 
обрабатываем показания датчиков, получаем случайное направление и идем по нему*/
     check_env();
     decision();
     h=next_move();
     go(h);
     led_on();
     delay(100);
     led_off();
     
  };
}  


void main(void)
{  
  int i;
 // unsigned int delta;
  // инициализация портов
  PORTB=0x00;
  DDRB=0xFF;
  PORTC=0x00;
  DDRC=0x00;
  PORTD=0x00;
  DDRD=0xFF;
  TCCR0=0x00;
  TCNT0=0x00;

  // Timer/Counter 1 initialization
  // Clock source: System Clock
  // Clock value: 15,625 kHz
  // Mode: Normal top=FFFFh
  // OC1A output: Discon. 
  // OC1B output: Discon.
  // Noise Canceler: Off
  // Input Capture on Falling Edge
  // Timer 1 Overflow Interrupt: Off
  // Input Capture Interrupt: Off
  // Compare A Match Interrupt: On
  // Compare B Match Interrupt: Off
 
  TCCR1A=0x00; 
  TCCR1B=0x0D;
  TCNT1H=0x00;
  TCNT1L=0x00;
  ICR1H=0x00;
  ICR1L=0x00;
  OCR1AH=0x00;
  OCR1AL=0x0F;
  OCR1BH=0x00;
  OCR1BL=0x00;
 
  // Timer/Counter 2 initialization
  // Clock source: System Clock
  // Clock value: Timer 2 Stopped
  // Mode: Normal top=FFh
  // OC2 output: Disconnected
  ASSR=0x00;
  TCCR2=0x00;
  TCNT2=0x00;
  OCR2=0x00;

  // External Interrupt(s) initialization
  // INT0: Off
  // INT1: Off
  MCUCR=0x00;

  // Timer(s)/Counter(s) Interrupt(s) initialization
  TIMSK=0x10;

  // Analog Comparator initialization
  // Analog Comparator: Off
  // Analog Comparator Input Capture by Timer/Counter 1: Off
  ACSR=0x80;
  SFIOR=0x00;

  // ADC initialization
  // ADC Clock frequency: 1000,000 kHz
  // ADC Voltage Reference: AVCC pin
  ADMUX=ADC_VREF_TYPE & 0xff;
  ADCSRA=0x8C;

  // Global enable interrupts
  #asm("sei")
 

  while (1)
  {
      now=1;
      for (i=0;i<=5;i++)                /*Помигаем светодиодом после включения, 
заодно задержка перед троганием что бы успеть отбежать*/
      { 
        led_on();
        delay(500);
        led_off();
        delay(500);
       }; 
        
      start();   
  };
}