GRBL五:定时器控制策略解析本文讲述的是根据grbl原始程序两个定时器控制脉冲输出的方式,由于在stm32上有PWM输出功能,所以可以用一个定时器的方式输出PWM来控制脉冲(因为pwm的占空比不影响步进速度,只需要固定占空就好,值修改脉冲周期就OK了),可以节约一个定时器,关于这种设计方式我只是有这个想法,本文未作实践。
GRBL的脉冲输出靠两个定时器协同控制输出的
具体控制策略:第一个定时器控制脉冲周期(因为步进电机脉冲周期决定速度)
第二个定时器控制一个周期中低电平的时间(脉冲宽度不重要,只要CPU能检测的到就好),相当于延时
流程如下图所示
所有代码都在stepper中定义
第一步:初始化引脚和定时器
在初始化定时器中没有给两个定时器设置分频和初值,也没有打开定时器开关
用的STM32通用16位定时器3和4
[cpp] view plain copy
- // 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<<STEPPERS_DISABLE_BIT;
- //ZJK ADD FOR STEP_IO INIT
- GPIO_InitTypeDef GPIO_InitStructure;
- //X_STEP:PA8
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- X_STEP_PORT = (X_STEP_PORT & ~X_STEP_MASK) | (settings.invert_mask&X_STEP_MASK);
- //Y_STEP:PD15
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOD, &GPIO_InitStructure);
- Y_STEP_PORT = (Y_STEP_PORT & ~Y_STEP_MASK) | (settings.invert_mask&Y_STEP_MASK);
- //X_DIR :PA6
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- X_DIRECTION_PORT = (X_DIRECTION_PORT & ~X_DIRECTION_MASK) | (settings.invert_mask&X_DIRECTION_MASK);
- //Y_DIR :PA2
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- Y_DIRECTION_PORT = (Y_DIRECTION_PORT & ~Y_DIRECTION_MASK) | (settings.invert_mask&Y_DIRECTION_MASK);
- //X_RES :PA3
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //Y_RES :PA4
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //END ZJK STEPP_IO INIT
- // waveform generation = 0100 = CTC
- // TCCR1B &= ~(1<<WGM13);
- // TCCR1B |= (1<<WGM12);
- // TCCR1A &= ~(1<<WGM11);
- // TCCR1A &= ~(1<<WGM10);
- //
- // // output mode = 00 (disconnected)
- // TCCR1A &= ~(3<<COM1A0);
- // TCCR1A &= ~(3<<COM1B0);
- //timer3 init 通用定时器
- RCC->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<<TOIE2); // Enable Timer2 Overflow interrupt
- // #ifdef STEP_PULSE_DELAY
- // TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
- // #endif
- //timer4 init 通用定时器
- RCC->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()主要是关定时器
[cpp] view plain copy
- // 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<<STEPPERS_DISABLE_BIT);
- STEPPERS_DISABLE_EN;
- } else {
- //STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
- STEPPERS_DISABLE_DIS;
- }
- if (sys.state == STATE_CYCLE) {
- // Initialize stepper output bits
- out_bits = (0) ^ (settings.invert_mask);
- // Initialize step pulse timing from settings. Here to ensure updating after re-writing.
- #ifdef STEP_PULSE_DELAY
- // Set total step pulse time after direction pin set. Ad hoc computation from oscilloscope.
- step_pulse_time = -(((settings.pulse_microseconds+STEP_PULSE_DELAY-2)*TICKS_PER_MICROSECOND) >> 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<<OCIE1A);
- //bit0=1开启定时器,bit6,5=00边沿,bit4=0向上计数 1向下,bit9,8=00无分频 01=2* 10=4*
- TIM3->CR1|=0X01;
- }
- }
[cpp] view plain copy
- // Stepper shutdown
- void st_go_idle()
- {
- // Disable stepper driver interrupt
- //TIMSK1 &= ~(1<<OCIE1A);
- //bit0=1关闭定时器
- TIM3->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<<STEPPERS_DISABLE_BIT);
- STEPPERS_DISABLE_DIS;
- } else {
- //STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
- STEPPERS_DISABLE_EN;
- }
- }
- }
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的赋值
因为定时器三设置的是周期时间,就是电机的速度控制,由于有加减速,这个时间肯定是不定的,通过计算随时改变的,那就是一个函数中通过参数设置,执行一个周期后计算该参数赋给寄存器
[cpp] view plain copy
- //设置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);
- }
[cpp] view plain copy
- // 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<<CS10)) | (prescaler<<CS10);
- TIM3->PSC=prescaler; //设置分频
- // Set ceiling
- TIM3->ARR=ceiling; //设置计数值
- return(actual_cycles);
- }
|
共1人点赞
|