Назначить задержки на 1 мс или 2 мс в C? - PullRequest
3 голосов
/ 01 ноября 2010

Я использую код для настройки простого робота.Я использую WinAVR , и используемый там код похож на C, но без библиотек stdio.h и тому подобного, поэтому код для простых вещей следует вводить вручную (например, преобразование десятичных чисел в шестнадцатеричные числа).это многошаговая процедура, включающая ASCII манипулирование символами).

Примером используемого кода является (просто чтобы показать вам, о чем я говорю :))

.
.
.
    DDRA = 0x00;
    A = adc(0); // Right-hand sensor
    u = A>>4;
    l = A&0x0F;
    TransmitByte(h[u]);
    TransmitByte(h[l]);
    TransmitByte(' ');
.
.
.

В некоторых случаях я должен использовать WinAVR и не могу использовать внешние библиотеки (например, stdio.h).В любом случае, я хочу подать сигнал с длительностью импульса 1 мс или 2 мс через серводвигатель.Я знаю, какой порт установить и тому подобное;все, что мне нужно сделать, это применить задержку, чтобы сохранить этот порт установленным перед его очисткой.

Теперь я знаю, как установить задержки, мы должны создать пустые циклы, такие как:

int value= **??**
for(i = 0; i<value; i++)
    ;

Какое значение я должен ввести в "значение" для цикла 1 мс?

Ответы [ 7 ]

4 голосов
/ 01 ноября 2010

Скорее всего, вам придется рассчитать разумное значение, затем посмотреть на генерируемый сигнал (например, с помощью осциллографа) и корректировать свое значение, пока не достигнете нужного временного диапазона. Учитывая, что у вас, очевидно, есть запас в 2: 1, вы могли бы ударить его достаточно близко с первого раза, но я бы не стал на этом много.

Для первого приближения сгенерируйте пустой цикл, сосчитайте циклы команд для одного цикла и умножьте их на время для одного такта. Это должно дать, по крайней мере, разумное приближение времени, затрачиваемое на одно выполнение цикла, поэтому разделение необходимого вам времени на это должно привести вас к исходной точке для правильного количества итераций.

Edit: Я также должен отметить, что (по крайней мере, большинство) AVR имеют встроенные таймеры, так что вы можете использовать их вместо этого. Это может 1) позволить вам выполнять другую обработку и / или 2) снизить энергопотребление на время.

Если вы используете петли задержки, вы можете использовать AVR-libc утилиты петли задержки для обработки деталей.

3 голосов
/ 01 ноября 2010

Если моя программа достаточно проста, нет необходимости в явном программировании таймера, но она должна быть переносимой.Один из моих вариантов для определенной задержки: AVR Libc delay function:

#include <delay.h>
_delay_ms (2) // Sleeps 2 ms
1 голос
/ 01 ноября 2010

У вас почти наверняка должно быть настроено прерывание для выполнения кода с предсказуемым интервалом. Если вы посмотрите на примеры программ, поставляемых с вашим процессором, вы, вероятно, найдете пример такого.

Как правило, для хранения таймера будет использоваться слово / длинное слово памяти, которое будет увеличиваться при каждом прерывании. Если ваше прерывание по таймеру выполняется 10000 раз в секунду и увеличивает «interrupt_counter» на единицу каждый раз, процедура «подождите 1 мс» может выглядеть следующим образом:

extern volatile unsigned long interrupt_counter;

unsigned long temp_value = interrupt_counter;

do {} while(10 > (interrupt_counter - temp_value));
/* Would reverse operands above and use less-than if this weren't HTML. */

Обратите внимание, что, как написано, код будет ждать от 900 мкс до 1000 мкс. Если вы измените сравнение на большее или равное, оно будет ждать между 1000 и 1100. Если нужно что-то сделать пять раз с интервалами в 1 мс, ожидая какое-то произвольное время до 1 мс в первый раз, можно записать код как:

extern volatile unsigned long interrupt_counter;
unsigned long temp_value = interrupt_counter;
for (int i=0; 5>i; i++)
{
    do {} while(!((temp_value - interrupt_counter) & 0x80000000)); /* Wait for underflow */
    temp_value += 10;
    do_action_thing();
}

Это должно запускать do_something() с точными интервалами, даже если для их завершения требуется несколько сотен микросекунд. Если они иногда занимают более 1 мс, система попытается запустить каждый из них в «правильное» время (поэтому, если один вызов занимает 1,3 мс, а следующий завершается мгновенно, следующий произойдет через 700 мкс).

1 голос
/ 01 ноября 2010

Это действительно изящный маленький задатель, который я иногда использую.Это для AVR.

************************Header File***********************************

// Scheduler data structure for storing task data
typedef struct
{
   // Pointer to task
   void (* pTask)(void);
   // Initial delay in ticks
   unsigned int Delay;
   // Periodic interval in ticks
   unsigned int Period;
   // Runme flag (indicating when the task is due to run)
   unsigned char RunMe;
} sTask;

// Function prototypes
//-------------------------------------------------------------------

void SCH_Init_T1(void);
void SCH_Start(void);
// Core scheduler functions
void SCH_Dispatch_Tasks(void);
unsigned char SCH_Add_Task(void (*)(void), const unsigned int, const unsigned int);
unsigned char SCH_Delete_Task(const unsigned char);

// Maximum number of tasks
// MUST BE ADJUSTED FOR EACH NEW PROJECT
#define SCH_MAX_TASKS (1)

************************Header File***********************************

************************C File***********************************

#include "SCH_AVR.h"
#include <avr/io.h>
#include <avr/interrupt.h>


// The array of tasks
sTask SCH_tasks_G[SCH_MAX_TASKS];


/*------------------------------------------------------------------*-

  SCH_Dispatch_Tasks()

  This is the 'dispatcher' function.  When a task (function)
  is due to run, SCH_Dispatch_Tasks() will run it.
  This function must be called (repeatedly) from the main loop.

-*------------------------------------------------------------------*/

void SCH_Dispatch_Tasks(void)
{
   unsigned char Index;

   // Dispatches (runs) the next task (if one is ready)
   for(Index = 0; Index < SCH_MAX_TASKS; Index++)
   {
      if((SCH_tasks_G[Index].RunMe > 0) && (SCH_tasks_G[Index].pTask != 0))
      {
         (*SCH_tasks_G[Index].pTask)();  // Run the task
         SCH_tasks_G[Index].RunMe -= 1;   // Reset / reduce RunMe flag

         // Periodic tasks will automatically run again
         // - if this is a 'one shot' task, remove it from the array
         if(SCH_tasks_G[Index].Period == 0)
         {
            SCH_Delete_Task(Index);
         }
      }
   }
}

/*------------------------------------------------------------------*-

  SCH_Add_Task()

  Causes a task (function) to be executed at regular intervals 
  or after a user-defined delay

  pFunction - The name of the function which is to be scheduled.
              NOTE: All scheduled functions must be 'void, void' -
              that is, they must take no parameters, and have 
              a void return type. 

  DELAY     - The interval (TICKS) before the task is first executed

  PERIOD    - If 'PERIOD' is 0, the function is only called once,
              at the time determined by 'DELAY'.  If PERIOD is non-zero,
              then the function is called repeatedly at an interval
              determined by the value of PERIOD (see below for examples
              which should help clarify this).


  RETURN VALUE:  

  Returns the position in the task array at which the task has been 
  added.  If the return value is SCH_MAX_TASKS then the task could 
  not be added to the array (there was insufficient space).  If the
  return value is < SCH_MAX_TASKS, then the task was added 
  successfully.  

  Note: this return value may be required, if a task is
  to be subsequently deleted - see SCH_Delete_Task().

  EXAMPLES:

  Task_ID = SCH_Add_Task(Do_X,1000,0);
  Causes the function Do_X() to be executed once after 1000 sch ticks.            

  Task_ID = SCH_Add_Task(Do_X,0,1000);
  Causes the function Do_X() to be executed regularly, every 1000 sch ticks.            

  Task_ID = SCH_Add_Task(Do_X,300,1000);
  Causes the function Do_X() to be executed regularly, every 1000 ticks.
  Task will be first executed at T = 300 ticks, then 1300, 2300, etc.            

-*------------------------------------------------------------------*/

unsigned char SCH_Add_Task(void (*pFunction)(), const unsigned int DELAY, const unsigned int PERIOD)
{
   unsigned char Index = 0;

   // First find a gap in the array (if there is one)
   while((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
   {
      Index++;
   }

   // Have we reached the end of the list?   
   if(Index == SCH_MAX_TASKS)
   {
      // Task list is full, return an error code
      return SCH_MAX_TASKS;  
   }

   // If we're here, there is a space in the task array
   SCH_tasks_G[Index].pTask = pFunction;
   SCH_tasks_G[Index].Delay =DELAY;
   SCH_tasks_G[Index].Period = PERIOD;
   SCH_tasks_G[Index].RunMe = 0;

   // return position of task (to allow later deletion)
   return Index;
}

/*------------------------------------------------------------------*-

  SCH_Delete_Task()

  Removes a task from the scheduler.  Note that this does
  *not* delete the associated function from memory: 
  it simply means that it is no longer called by the scheduler. 

  TASK_INDEX - The task index.  Provided by SCH_Add_Task(). 

  RETURN VALUE:  RETURN_ERROR or RETURN_NORMAL

-*------------------------------------------------------------------*/

unsigned char SCH_Delete_Task(const unsigned char TASK_INDEX)
{
   // Return_code can be used for error reporting, NOT USED HERE THOUGH!
   unsigned char Return_code = 0;

   SCH_tasks_G[TASK_INDEX].pTask = 0;
   SCH_tasks_G[TASK_INDEX].Delay = 0;
   SCH_tasks_G[TASK_INDEX].Period = 0;
   SCH_tasks_G[TASK_INDEX].RunMe = 0;

   return Return_code;
}

/*------------------------------------------------------------------*-

  SCH_Init_T1()

  Scheduler initialisation function.  Prepares scheduler
  data structures and sets up timer interrupts at required rate.
  You must call this function before using the scheduler.  

-*------------------------------------------------------------------*/

void SCH_Init_T1(void)
{
   unsigned char i;

   for(i = 0; i < SCH_MAX_TASKS; i++)
   {
      SCH_Delete_Task(i);
   }

   // Set up Timer 1
   // Values for 1ms and 10ms ticks are provided for various crystals

   OCR1A = 15000;   // 10ms tick, Crystal 12 MHz
   //OCR1A = 20000;   // 10ms tick, Crystal 16 MHz
   //OCR1A = 12500;   // 10ms tick, Crystal 10 MHz
   //OCR1A = 10000;   // 10ms tick, Crystal 8  MHz

   //OCR1A = 2000;    // 1ms tick, Crystal 16 MHz
   //OCR1A = 1500;    // 1ms tick, Crystal 12 MHz
   //OCR1A = 1250;    // 1ms tick, Crystal 10 MHz
   //OCR1A = 1000;    // 1ms tick, Crystal 8  MHz

   TCCR1B = (1 << CS11) | (1 << WGM12);  // Timer clock = system clock/8
   TIMSK |= 1 << OCIE1A;   //Timer 1 Output Compare A Match Interrupt Enable
}

/*------------------------------------------------------------------*-

  SCH_Start()

  Starts the scheduler, by enabling interrupts.

  NOTE: Usually called after all regular tasks are added,
  to keep the tasks synchronised.

  NOTE: ONLY THE SCHEDULER INTERRUPT SHOULD BE ENABLED!!! 

-*------------------------------------------------------------------*/

void SCH_Start(void)
{
      sei();
}

/*------------------------------------------------------------------*-

  SCH_Update

  This is the scheduler ISR.  It is called at a rate 
  determined by the timer settings in SCH_Init_T1().

-*------------------------------------------------------------------*/

ISR(TIMER1_COMPA_vect)
{
   unsigned char Index;
   for(Index = 0; Index < SCH_MAX_TASKS; Index++)
   {
      // Check if there is a task at this location
      if(SCH_tasks_G[Index].pTask)
      {
         if(SCH_tasks_G[Index].Delay == 0)
         {
            // The task is due to run, Inc. the 'RunMe' flag
            SCH_tasks_G[Index].RunMe += 1;

            if(SCH_tasks_G[Index].Period)
            {
               // Schedule periodic tasks to run again
               SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Period;
               SCH_tasks_G[Index].Delay -= 1;
            }
         }
         else
         {
            // Not yet ready to run: just decrement the delay
            SCH_tasks_G[Index].Delay -= 1;
         }
      }
   }
}

// ------------------------------------------------------------------


************************C File***********************************
1 голос
/ 01 ноября 2010

Если вам нужно более точное значение времени, вы должны использовать процедуру обработки прерывания на основе внутреннего таймера. Помните, что цикл For - это инструкция по блокировке, поэтому во время итерации остальная часть вашей программы блокируется. Вы могли бы установить ISR на основе таймера с глобальной переменной, которая подсчитывает 1 при каждом запуске ISR. Затем вы можете использовать эту переменную в операторе if, чтобы установить время ширины. Также это ядро, вероятно, поддерживает ШИМ для использования с сервоприводами типа RC. Так что это может быть лучший маршрут.

1 голос
/ 01 ноября 2010

Большинство чипов ATmega AVR, которые обычно используются для создания простых роботов, имеют функцию, известную как широтно-импульсная модуляция (ШИМ), которая может использоваться для управления сервоприводами. Этот пост в блоге может послужить кратким введением в управление сервоприводами с использованием ШИМ.Если вы посмотрите на библиотеку сервоуправления платформы Arduino *1005*, вы обнаружите, что она также использует ШИМ.

Это может быть лучшим выбором, чем полагаться на постоянное выполнение цикла, так как изменения флагов оптимизации компилятора и тактовой частоты чипа потенциально могут нарушить такую ​​простую функцию задержки.

1 голос
/ 01 ноября 2010

Это пойдет на настоящего робота?Все, что у вас есть, - это процессор, никаких других интегральных микросхем, которые могут измерять время?

Если оба ответа «да», хорошо ... если вы знаете точные сроки выполнения операций, вы можете использоватьцикл для создания точных задержек.Выведите свой код в код сборки и посмотрите точную последовательность используемых инструкций.Затем проверьте руководство процессора, оно будет иметь эту информацию.

...