在 C 语言中为 1 毫秒或 2 毫秒指定延迟?

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并且不能使用外部库(例如)。不管怎样,我想通过舵机应用脉宽为1毫秒或2毫秒的信号。我知道要设置哪个端口等等;我所需要做的就是应用延迟以保持该端口设置,然后再将其清除。

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

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

对于1毫秒的循环,我应该在"value"中放入什么值?


你可以尝试在这里询问:http://electronics.stackexchange.com/ - Peter G.
7个回答

4
你可能需要计算一个合理的值,然后查看生成的信号(例如,使用示波器)并调整你的值,直到你达到正确的时间范围。考虑到你显然有2:1的余量,第一次你可能会相当接近,但我不会抱太大希望。
对于你的第一个近似值,生成一个空循环并计算一个循环的指令周期数,并将其乘以一个时钟周期的时间。这应该至少给出单次执行循环所需的时间的合理近似值,因此将你需要的时间除以它,就可以得到正确迭代次数的大致值。
编辑:我也应该注意到,至少大多数AVR都有板载定时器,因此你可能能够使用它们。这可以让你进行其他处理和/或在持续时间内降低功耗。
如果你确实使用延迟循环,你可能想使用AVR-libc的延迟循环工具来处理细节。

谢谢您的回复,我认为这是我最好的解决方案。因为我不能使用外部库,而且我的代码应该是直截了当的,并且必须使用循环,所以这可能是一个好的方法。再次感谢! - NLed

3

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

#include <delay.h>
_delay_ms (2) // Sleeps 2 ms

谢谢回答,但是这里不能使用除特定库以外的其他内容。 - NLed
我不明白你所说的“除特定库之外”的意思。AVR Libc是WinAVR的一部分。 - Peter G.

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***********************************

哇,非常感谢!我真的很想将这个作为我的解决方案发布,但是我不能使用额外的库,因为我的代码不能包含它们! - NLed

1

你应该几乎肯定配置了一个中断,以在可预测的时间间隔内运行代码。如果你查看CPU附带的示例程序,你可能会找到这样的示例。

通常,人们会使用一个字/长字来存储计时器,每次中断都会将其递增。如果你的计时器中断每秒运行10,000次,并且每次递增“interrupt_counter”的值,则“等待1ms”例程可能如下所示:

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微秒后发生)。


非常感谢您的回复和解释这段代码,如果我的其他方法失败了,我可能会尝试这个。 - NLed

1

这个程序是要用在真正的机器人上吗?你只有一个CPU,没有其他集成电路可以提供时间测量吗?

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


1

大多数用于制作简单机器人的ATmega AVR芯片都具有脉宽调制(PWM)功能,可以用于控制伺服电机。pulse-width modulation(PWM)可能是使用PWM控制伺服电机的快速介绍。如果您查看Arduino平台的伺服控制库,您会发现它也使用PWM。

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


谢谢您的回复,我会查看那些链接。 - NLed
博客文章的链接似乎已经损坏了。 - Peter Mortensen

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

谢谢,如果我不能让我的其他代码工作,我希望能尝试这个。 - NLed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接