3

我正在使用代码来配置一个简单的机器人。我正在使用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 ms 或 2 ms 的信号。我知道要设置什么端口等等;我需要做的就是在清除它之前应用延迟以保持该端口设置。

现在我知道如何设置延迟,我们应该创建空的 for 循环,例如:

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

对于 1 ms 循环,我应该为“值”输入什么值?

4

7 回答 7

4

您可能必须计算一个合理的值,然后查看生成的信号(例如,使用示波器)并调整您的值,直到达到正确的时间范围。鉴于您显然有 2:1 的保证金,您可能会在第一次合理地接近它,但我不会太多。

对于您的第一个近似值,生成一个空循环并计算一个循环的指令周期,然后将其乘以一个时钟周期的时间。这应该至少给出单次执行循环所花费的时间的合理近似值,因此将您需要的时间除以该时间应该可以让您进入正确数量的迭代范围。

编辑:但是,我还应该注意,(至少大多数)AVR 具有板载计时器,因此您可以改用它们。这可以 1)让您进行其他处理和/或 2)降低持续时间的功耗。

如果您确实使用延迟循环,您可能需要使用AVR-libc的延迟循环实用程序来处理细节。

于 2010-10-31T22:27:53.933 回答
3

如果我的程序足够简单,则不需要显式的计时器编程,但它应该是可移植的。我对定义延迟的选择之一是AVR Libc延迟函数:

#include <delay.h>
_delay_ms (2) // Sleeps 2 ms
于 2010-10-31T22:37:27.087 回答
1

这会去一个真正的机器人吗?你只有一个CPU,没有其他可以测量时间的集成电路吗?

如果两个答案都是“是”,那么...如果您知道操作的确切时间,您可以使用循环来创建精确的延迟。将您的代码输出为汇编代码,并查看使用的指令的确切顺序。然后,检查处理器的手册,它会有这些信息。

于 2010-10-31T22:27:55.980 回答
1

大多数通常用于制造简单机器人的 ATmega AVR 芯片都具有称为脉冲宽度调制(PWM) 的功能,可用于控制伺服系统。这篇博文可以作为使用 PWM 控制伺服系统的快速介绍。如果你看一下Arduino 平台的伺服控制库,你会发现它也使用了 PWM。

这可能是比依赖于以恒定次数运行循环更好的选择,因为对编译器优化标志和芯片时钟速度的更改可能会破坏这种简单的延迟功能。

于 2010-10-31T22:45:28.177 回答
1

如果您需要更精确的时间值,您应该使用基于内部定时器的中断服务程序。请记住 For 循环是一条阻塞指令,因此在它迭代时,程序的其余部分会被阻塞。您可以使用一个全局变量设置一个基于计时器的 ISR,每次 ISR 运行时该变量都加 1。然后,您可以在“if 语句”中使用该变量来设置宽度时间。此外,该内核可能支持 PWM 与 RC 型伺服系统一起使用。所以这可能是一条更好的路线。

于 2010-10-31T22:50:02.233 回答
1

这是我有时使用的一个非常整洁的小任务器。这是一个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***********************************
于 2010-10-31T23:08:48.363 回答
1

您几乎可以肯定将中断配置为以可预测的时间间隔运行代码。如果您查看随 CPU 提供的示例程序,您可能会找到这样的示例。

通常,人们将使用内存的一个字/长字来保存一个计时器,该计时器将在每次中断时递增。如果您的定时器中断每秒运行 10,000 次,并且每次将“interrupt_counter”递增 1,则“等待 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 µs 到 1000 µs。如果将比较更改为大于或等于,它将在 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()以精确的间隔运行 's,即使它们需要数百微秒才能完成。如果它们有时超过 1 毫秒,系统将尝试在“适当的”时间运行每个调用(因此,如果一个调用需要 1.3 毫秒,而下一个调用立即完成,则下一个调用将在 700 微秒后发生)。

于 2010-10-31T23:19:16.163 回答