GRBL五:STM32代码移植——定时器控制策略解析

                          GRBL五:定时器控制策略解析

本文讲述的是根据grbl原始程序两个定时器控制脉冲输出的方式,由于在stm32上有PWM输出功能,所以可以用一个定时器的方式输出PWM来控制脉冲(因为pwm的占空比不影响步进速度,只需要固定占空就好,值修改脉冲周期就OK了),可以节约一个定时器,关于这种设计方式我只是有这个想法,本文未作实践。
GRBL的脉冲输出靠两个定时器协同控制输出的
具体控制策略:第一个定时器控制脉冲周期(因为步进电机脉冲周期决定速度)
                         第二个定时器控制一个周期中低电平的时间(脉冲宽度不重要,只要CPU能检测的到就好),相当于延时
流程如下图所示
GRBL五:STM32代码移植——定时器控制策略解析_第1张图片
所有代码都在stepper中定义
第一步:初始化引脚和定时器
在初始化定时器中没有给两个定时器设置分频和初值,也没有打开定时器开关
用的STM32通用16位定时器3和4
// Initialize and start the stepper motor subsystem
void st_init()
{
  // Configure directions of interface pins
//  STEPPING_DDR |= STEPPING_MASK;
//  STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK) | settings.invert_mask;
//  STEPPERS_DISABLE_DDR |= 1<APB1ENR|=1<<1;//使能TIMER3时钟
	//设置允许更新中断,必须同时设置了才能更新中断
	TIM3->DIER|=1<<0;//允许中断更新
	TIM3->DIER|=1<<6;//触发中断使能
	//设置NVIC 
    //NVIC_PreemptionPriority:抢占优先级
	//NVIC_SubPriority       :响应优先级
	//NVIC_Channel           :中断编号
	//NVIC_Group             :中断分组 0~4
	//注意优先级不能超过设定的组的范围!否则会有意想不到的错误
	//组划分:
	//组0:0位抢占优先级,4位响应优先级
	//组1:1位抢占优先级,3位响应优先级
	//组2:2位抢占优先级,2位响应优先级
	//组3:3位抢占优先级,1位响应优先级
	//组4:4位抢占优先级,0位响应优先级
	//抢占:低优先级的中断执行的同时高优先级可以将其抢回,高结束后再执行低
	//响应:同时中断时谁先响应(高优先级,数小的)
	//NVIC_SubPriority和NVIC_PreemptionPriority的原则是,数值越小,越优先
    //中断分组设置,设置NVIC相关寄存器,使能中断
	//sys.c中,直接调用
	MY_NVIC_Init(1,2,TIM3_IRQChannel,2);    //抢占1,响应2,组2


  // Configure Timer 2
//  TCCR2A = 0; // Normal operation
//  TCCR2B = 0; // Disable timer until needed.
//  TIMSK2 |= (1<APB1ENR|=1<<2;//使能TIMER4时钟
	//设置允许更新中断,必须同时设置了才能更新中断
	TIM4->DIER|=1<<0;//允许中断更新
	TIM4->DIER|=1<<6;//触发中断使能
	//设置NVIC 
    //NVIC_PreemptionPriority:抢占优先级
	//NVIC_SubPriority       :响应优先级
	//NVIC_Channel           :中断编号
	//NVIC_Group             :中断分组 0~4
	//注意优先级不能超过设定的组的范围!否则会有意想不到的错误
	//组划分:
	//组0:0位抢占优先级,4位响应优先级
	//组1:1位抢占优先级,3位响应优先级
	//组2:2位抢占优先级,2位响应优先级
	//组3:3位抢占优先级,1位响应优先级
	//组4:4位抢占优先级,0位响应优先级
	//抢占:低优先级的中断执行的同时高优先级可以将其抢回,高结束后再执行低
	//响应:同时中断时谁先响应(高优先级,数小的)
	//NVIC_SubPriority和NVIC_PreemptionPriority的原则是,数值越小,越优先
    //中断分组设置,设置NVIC相关寄存器,使能中断
	//sys.c中,直接调用
	MY_NVIC_Init(0,1,TIM4_IRQChannel,2);    //抢占0,响应1,组2

  // Start in the idle state, but first wake up to check for keep steppers enabled option.
  st_wake_up();
  st_go_idle();
}
第二步:设置定时器4的初值在st_wake_up中计算的,在TIM3的中断服务函数中赋值的
               st_wake_up()主要是开定时器3,计算定时器4的初值
               st_go_idle()主要是关定时器
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle.
void st_wake_up() 
{
  // Enable steppers by resetting the stepper disable port
  if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { 
    //STEPPERS_DISABLE_PORT |= (1<> 3);
      // Set delay between direction pin write and step command.
      OCR2A = -(((settings.pulse_microseconds)*TICKS_PER_MICROSECOND) >> 3);
    #else // Normal operation
      // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement.
	  //设置一个周期中低电平时间,也就是TIM4的计时时间,因为TIM4中拉高的输出脚,所以它计时的时间其实为拉低时候的延时时间
	  //>>3是因为定时器八分频
	  //取反因为AVR用的普通定时器模式,计数器从填入的值开始计时直到溢出产生溢出中断,所以填入的值=256-实际的定时值
      //step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
	  //张纪宽修改bug,因为STM32的定时器模式跟AVR不同,STM32是从0开始计数,计数到设定值后进入中断,所以不需要取反
	   step_pulse_time = ((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3;
    #endif
    // Enable stepper driver interrupt
    //TIMSK1 |= (1<CR1|=0X01;
  }
}
// Stepper shutdown
void st_go_idle() 
{
  // Disable stepper driver interrupt
  //TIMSK1 &= ~(1<CR1&=~0X01;
  // Disable steppers only upon system alarm activated or by user setting to not be kept enabled.
  if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) {
    // Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
    // stop and not drift from residual inertial forces at the end of the last movement.
    delay_ms(settings.stepper_idle_lock_time);
    if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { 
      //STEPPERS_DISABLE_PORT &= ~(1<
TIM4定时时间计算方法:TIM4控制一个周期中低电平时间的,相当于就是延时时间,因为在TIM4中断函数里拉高了
step引脚,进入此中断服务函数证明定时结束,定时结束将引脚拉高,作用就是延时低电平时间
step_pulse_time = ((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3;
settings.pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS;//setting default中赋值的
  #define DEFAULT_STEP_PULSE_MICROSECONDS 10  //default.h中   这个值代表延时的时间,单位us
因为步进电机靠频率大小控制速度,所以脉宽没要求,只要cpu能检测出来就好了
左移三位代表TIM4设置的8分频

第三步:设置定时器3的赋值
因为定时器三设置的是周期时间,就是电机的速度控制,由于有加减速,这个时间肯定是不定的,通过计算随时改变的,那就是一个函数中通过参数设置,执行一个周期后计算该参数赋给寄存器
//设置TIM3定时时间,也就是一个周期的时间
//参数steps_per_minute其实为频率,次数/min   f=次数/60  每秒钟多少下
//所以其他都是定制,主要就是steps_per_minute的值决定了周期,这里的参数其实就是速度,steps/min,因为脉冲周期最终决定的就是运动速度,在这里把两者联系了起来
static void set_step_events_per_minute(uint32_t steps_per_minute) 
{
  if (steps_per_minute < MINIMUM_STEPS_PER_MINUTE) { steps_per_minute = MINIMUM_STEPS_PER_MINUTE; }
  //st.cycles_per_step_event = config_step_timer((TICKS_PER_MICROSECOND*1000000*60)/steps_per_minute); //(TICKS_PER_MICROSECOND*1000000*60)超出32位,所以改变了下顺序
  //计算定时器填入的值,不是定时时间
  //填入的值=系统每秒钟跳动的次数(次数/s) 除以 电机运动速度(steps/s)
  //注意steps_per_minute/60为steps/s,正好与分子上72MHZ的单位:跳动次数/s相对应,注意这里跳动次数与steps的区别,跳动次数指的是单片机定时计数器每秒钟的跳动次数,steps指的是每秒钟电机需要几个脉冲,相除之后的值就代表每个脉冲单片机跳动次数,正好就是需要填入的值,哇塞太巧妙了
  st.cycles_per_step_event = config_step_timer(TICKS_PER_MICROSECOND*1000000/steps_per_minute*60);
}
// Configures the prescaler and ceiling of timer 1 to produce the given rate as accurately as possible.
// Returns the actual number of cycles per interrupt
//设置周期时间	TIM3的定时时间就是周期时间
static uint32_t config_step_timer(uint32_t cycles)
{
  uint16_t ceiling;
  uint16_t prescaler;
  uint32_t actual_cycles;
  if (cycles <= 0xffffL) {
    ceiling = cycles;
    prescaler = 72-1; // prescaler: 0
    actual_cycles = ceiling;
  } else if (cycles <= 0x7ffffL) {
    ceiling = cycles >> 3;
    prescaler = 72*8-1; // prescaler: 8
    actual_cycles = ceiling * 8L;
  } else if (cycles <= 0x3fffffL) {
    ceiling =  cycles >> 6;
    prescaler = 72*64-1; // prescaler: 64
    actual_cycles = ceiling * 64L;
  } else if (cycles <= 0xffffffL) {
    ceiling =  (cycles >> 8);
    prescaler = 72*256-1; // prescaler: 256
    actual_cycles = ceiling * 256L;
  } else if (cycles <= 0x3ffffffL) {
    ceiling = (cycles >> 10);
    //prescaler = 72*1024-1; // prescaler: 1024	这里超了十六位,设置错误
	prescaler = 65535; //不精确了
    actual_cycles = ceiling * 1024L;    
  } else {
    // Okay, that was slower than we actually go. Just set the slowest speed
    ceiling = 0xffff;
    prescaler = 65535;
    actual_cycles = 0xffff * 1024;
  }
  // Set prescaler	设置时钟分频
  //TCCR1B = (TCCR1B & ~(0x07<PSC=prescaler; //设置分频
  // Set ceiling
  TIM3->ARR=ceiling; //设置计数值
  return(actual_cycles);
}
定时器值=TICKS_PER_MICROSECOND*1000000/steps_per_minute*60   
本来是TICKS_PER_MICROSECOND*1000000*60 /steps_per_minute 由于这样会超出32bit范围,所以修改成上面了
TICKS_PER_MICROSECOND*1000000*60代表每分钟系统时钟跳动次数
steps_per_minute代表电机每分钟需要的脉冲个数,也就是速度
填入的值=系统每秒钟跳动的次数(次数/s) 除以 电机运动速度(steps/s)
  //注意steps_per_minute/60为steps/s,正好与分子上72MHZ的单位:跳动次数/s相对应,注意这里跳动次数与steps的区别,跳动次数指的是单片机定时计数器每秒钟的跳动次数,steps指的是每秒钟电机需要几个脉冲,相除之后的值就代表每个脉冲单片机跳动次数,正好就是需要填入的值,哇塞太巧妙了
static uint32_t config_step_timer(uint32_t cycles)这个函数就是具体的填入值方法,分频和余量的方式
因为很有可能设置时间超出了16位定时器的时间,所以对应分频设置可以设置最大的时间

第四步: 两个定时器的中断服务函数,在中断服务函数中进行的参数的赋值,定时器的赋值,定时器的开关等
这是定时器控制最重要的程序
// "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is executed at the rate set with
// config_step_timer. It pops blocks from the block_buffer and executes them by pulsing the stepper pins appropriately. 
// It is supported by The Stepper Port Reset Interrupt which it uses to reset the stepper port after each pulse. 
// The bresenham line tracer algorithm controls all three stepper outputs simultaneously with these two interrupts.
//ISR(TIMER1_COMPA_vect)
//定时器3中断服务函数
/**********中断服务函数完成的工作:
1.TIM3定时时间就是一个周期时间
2.TIM3把信号拉低开始定时接着打开TIM4定时拉低时间,TIM4时间到拉高信号TIM3定时到拉低信号
3.数据操作:计算下一个周期的时间,XYZ的位置,运动的方向,需要的加速度速度等参数
****************************************************************************************/
void TIM3_IRQHandler(void) //中断服务函数名一定正确
{
	if(TIM3->SR&0X0001)//再次判断是否更新事件
	{	        
	  if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
	  /****************************************************************************
		*从这里开始是方向和输出(拉低)控制,开TIM4拉低延时时间定时器,一个周期的开始,TIM3从0开始计数
	  ****************************************************************************/
	  // Set the direction pins a couple of nanoseconds before we step the steppers
	  //STEPPING_PORT = (STEPPING_PORT & ~DIRECTION_MASK) | (out_bits & DIRECTION_MASK);
	  //change by zjk
	   X_DIRECTION_PORT = (X_DIRECTION_PORT & ~X_DIRECTION_MASK) | (out_bits & X_DIRECTION_MASK);
	   Y_DIRECTION_PORT = (Y_DIRECTION_PORT & ~Y_DIRECTION_MASK) | (out_bits & Y_DIRECTION_MASK);
	  //end zjk
	  // Then pulse the stepping pins
	  #ifdef STEP_PULSE_DELAY
	    step_bits = (STEPPING_PORT & ~STEP_MASK) | out_bits; // Store out_bits to prevent overwriting.
	  #else  // Normal operation
	    //STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | out_bits;
		//change by zjk
		X_STEP_PORT = (X_STEP_PORT & ~X_STEP_MASK) | (out_bits& X_STEP_MASK);
		Y_STEP_PORT = (Y_STEP_PORT & ~Y_STEP_MASK) | (out_bits& Y_STEP_MASK);
		//end zjk
	  #endif
	  // Enable step pulse reset timer so that The Stepper Port Reset Interrupt can reset the signal after
	  // exactly settings.pulse_microseconds microseconds, independent of the main Timer1 prescaler.
//	  TCNT2 = step_pulse_time; // Reload timer counter
//	  TCCR2B = (1<PSC=72*8-1; //设置分频 八分频
  	  TIM4->ARR=step_pulse_time; //设置计数值

	  busy = true;
	  // Re-enable interrupts to allow ISR_TIMER2_OVERFLOW to trigger on-time and allow serial communications
	  // regardless of time in this handler. The following code prepares the stepper driver for the next
	  // step interrupt compare and will always finish before returning to the main program.
	  //sei(); //开中断
	  //bit0=1开启定时器,bit6,5=00边沿,bit4=0向上计数 1向下,bit9,8=00无分频 01=2*  10=4*
	  TIM4->CR1|=0X01;
	  /**********************************************************************************
		从这里往下就是数据的操作
	  **********************************************************************************/
	  // If there is no current block, attempt to pop one from the buffer
	  if (current_block == NULL) {
	    // Anything in the buffer? If so, initialize next motion.
		//从主线程中获取信息,在中断服务函数中计算
	    current_block = plan_get_current_block();
		/*********************
		数据操作1:计算下个周期大小
	    *********************/
	    if (current_block != NULL) {
	      if (sys.state == STATE_CYCLE) {
	        // During feed hold, do not update rate and trap counter. Keep decelerating.
			//st.trapezoid_adjusted_rate决定周期大小,(st.trapezoid_adjusted_rate/60)代表速度
	        st.trapezoid_adjusted_rate = current_block->initial_rate;//初始速度,也就是启动时候的速度
	        set_step_events_per_minute(st.trapezoid_adjusted_rate); // Initialize cycles_per_step_event
	        st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Start halfway for midpoint rule.
	      }
	      st.min_safe_rate = current_block->rate_delta + (current_block->rate_delta >> 1); // 1.5 x rate_delta
	      st.counter_x = -(current_block->step_event_count >> 1);
	      st.counter_y = st.counter_x;
	      st.counter_z = st.counter_x;
	      st.event_count = current_block->step_event_count;
	      st.step_events_completed = 0;     
	    } else {
	      st_go_idle();
	      bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
	    }    
	  } 
	  /*********************
		数据操作2:计算位置信息
	  *********************/
	  if (current_block != NULL) {
	    // Execute step displacement profile by bresenham line algorithm
	    out_bits = current_block->direction_bits;
	    st.counter_x += current_block->steps_x;
	    if (st.counter_x > 0) {
	      out_bits |= (1<steps_y;
	    if (st.counter_y > 0) {
	      out_bits |= (1<steps_z;
	//    if (st.counter_z > 0) {
	//      out_bits |= (1<step_event_count) {
	      if (sys.state == STATE_HOLD) {
	        // Check for and execute feed hold by enforcing a steady deceleration from the moment of 
	        // execution. The rate of deceleration is limited by rate_delta and will never decelerate
	        // faster or slower than in normal operation. If the distance required for the feed hold 
	        // deceleration spans more than one block, the initial rate of the following blocks are not
	        // updated and deceleration is continued according to their corresponding rate_delta.
	        // NOTE: The trapezoid tick cycle counter is not updated intentionally. This ensures that 
	        // the deceleration is smooth regardless of where the feed hold is initiated and if the
	        // deceleration distance spans multiple blocks.
	        if ( iterate_trapezoid_cycle_counter() ) {                    
	          // If deceleration complete, set system flags and shutdown steppers.
	          if (st.trapezoid_adjusted_rate <= current_block->rate_delta) {
	            // Just go idle. Do not NULL current block. The bresenham algorithm variables must
	            // remain intact to ensure the stepper path is exactly the same. Feed hold is still
	            // active and is released after the buffer has been reinitialized.
	            st_go_idle();
	            bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program that feed hold is complete.
	          } else {
	            st.trapezoid_adjusted_rate -= current_block->rate_delta;
	            set_step_events_per_minute(st.trapezoid_adjusted_rate);
	          }      
	        }
	        
	      } else {
	        // The trapezoid generator always checks step event location to ensure de/ac-celerations are 
	        // executed and terminated at exactly the right time. This helps prevent over/under-shooting
	        // the target position and speed. 
	        // NOTE: By increasing the ACCELERATION_TICKS_PER_SECOND in config.h, the resolution of the 
	        // discrete velocity changes increase and accuracy can increase as well to a point. Numerical 
	        // round-off errors can effect this, if set too high. This is important to note if a user has 
	        // very high acceleration and/or feedrate requirements for their machine.
	        if (st.step_events_completed < current_block->accelerate_until) {
	          // Iterate cycle counter and check if speeds need to be increased.
	          if ( iterate_trapezoid_cycle_counter() ) {
	            st.trapezoid_adjusted_rate += current_block->rate_delta;
	            if (st.trapezoid_adjusted_rate >= current_block->nominal_rate) {
	              // Reached nominal rate a little early. Cruise at nominal rate until decelerate_after.
	              st.trapezoid_adjusted_rate = current_block->nominal_rate;
	            }
	            set_step_events_per_minute(st.trapezoid_adjusted_rate);
	          }
	        } else if (st.step_events_completed >= current_block->decelerate_after) {
	          // Reset trapezoid tick cycle counter to make sure that the deceleration is performed the
	          // same every time. Reset to CYCLES_PER_ACCELERATION_TICK/2 to follow the midpoint rule for
	          // an accurate approximation of the deceleration curve. For triangle profiles, down count
	          // from current cycle counter to ensure exact deceleration curve.
	          if (st.step_events_completed == current_block-> decelerate_after) {
	            if (st.trapezoid_adjusted_rate == current_block->nominal_rate) {
	              st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Trapezoid profile
	            } else {  
	              st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK-st.trapezoid_tick_cycle_counter; // Triangle profile
	            }
	          } else {
	            // Iterate cycle counter and check if speeds need to be reduced.
	            if ( iterate_trapezoid_cycle_counter() ) {  
	              // NOTE: We will only do a full speed reduction if the result is more than the minimum safe 
	              // rate, initialized in trapezoid reset as 1.5 x rate_delta. Otherwise, reduce the speed by
	              // half increments until finished. The half increments are guaranteed not to exceed the 
	              // CNC acceleration limits, because they will never be greater than rate_delta. This catches
	              // small errors that might leave steps hanging after the last trapezoid tick or a very slow
	              // step rate at the end of a full stop deceleration in certain situations. The half rate 
	              // reductions should only be called once or twice per block and create a nice smooth 
	              // end deceleration.
	              if (st.trapezoid_adjusted_rate > st.min_safe_rate) {
	                st.trapezoid_adjusted_rate -= current_block->rate_delta;
	              } else {
	                st.trapezoid_adjusted_rate >>= 1; // Bit shift divide by 2
	              }
	              if (st.trapezoid_adjusted_rate < current_block->final_rate) {
	                // Reached final rate a little early. Cruise to end of block at final rate.
	                st.trapezoid_adjusted_rate = current_block->final_rate;
	              }
	              set_step_events_per_minute(st.trapezoid_adjusted_rate);
	            }
	          }
	        } else {
	          // No accelerations. Make sure we cruise exactly at the nominal rate.
	          if (st.trapezoid_adjusted_rate != current_block->nominal_rate) {
	            st.trapezoid_adjusted_rate = current_block->nominal_rate;
	            set_step_events_per_minute(st.trapezoid_adjusted_rate);
	          }
	        }
	      }            
	    } else {   
	      // If current block is finished, reset pointer 
	      current_block = NULL;
	      plan_discard_current_block();
	    }
	  }
	  out_bits ^= settings.invert_mask;  // Apply step and direction invert mask    
	  busy = false;
	}
  TIM3->SR&=~(1<<0); //状态寄存器清除
}

// This interrupt is set up by ISR_TIMER1_COMPAREA when it sets the motor port bits. It resets
// the motor port after a short period (settings.pulse_microseconds) completing one step cycle.
// NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by
// a few microseconds, if they execute right before one another. Not a big deal, but can
// cause issues at high step rates if another high frequency asynchronous interrupt is 
// added to Grbl.
//ISR(TIMER2_OVF_vect)
//定时器4中断服务函数
void TIM4_IRQHandler(void) //中断服务函数名一定正确
{
	if(TIM4->SR&0X0001)//再次判断是否更新事件
	{

	  // Reset stepping pins (leave the direction pins)
	  //STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK); 
	  X_STEP_PORT = (X_STEP_PORT & ~X_STEP_MASK) | (settings.invert_mask & X_STEP_MASK);
	  Y_STEP_PORT = (Y_STEP_PORT & ~Y_STEP_MASK) | (settings.invert_mask & Y_STEP_MASK);
	  //TCCR2B = 0; // Disable Timer2 to prevent re-entering this interrupt when it's not needed. 
	  //bit0=1关闭定时器
	  TIM4->CR1&=~0X01;
	}
	TIM4->SR&=~(1<<0); //状态寄存器清除
}
在定时器3的中断服务函数中开的定时器4,定时器4中关的自己,一条G代码执行完关闭的定时器3
由此可见,定时器4是通过抢占的方式中断的,所以要设置定时器4的中断优先级要高于TIM3
定时器3的定时间是从主程序里获得赋值到st.trapezoid_adjusted_rate的,这个值就代表时刻变化着的速度
这里速度是如何实时计算的,有待于好好研究,因为速度是变化的,是梯形状的,是启动停止时候缓慢改变的



















你可能感兴趣的:(GRBL,GRBL,定时器,控制策略,STM32移植,脉冲输出)