DIY运动控制器——移植grbl(软件架构、脉冲产生)
移植grbl到stm32
读书的时候受导师和专业的影响吧,对机床挺感兴趣的。今天开始,就聊一下国外的开源数控项目grbl。
早在16年的时候就有听说过gbl,无奈当时自己嵌入式水平太差,没能玩得转,后来就不了了之了。2021年,自己重新阅读了一番grbl源码,进行了仔细研究,也作了些笔记。我打算在博客上把自己的内容重新整理一下,分享给大家。说的不对地方,大家请指正。我建了一个qq群(966403026),有兴趣的话可以进群讨论。
我研究的grbl版本为0.8和1.1f,两个版本的架构并没有发生变化,只是新版本支持了jog运动、探针等新功能。推荐大家从0.8版本开始看,有了一个大致框架后,再看1.1版本的。因为新版本的grbl功能更多了,变量也更多了,会产生一些混淆,读起来麻烦。
我打算基于0.8版本开始讲解,因为这对介绍一个CNC系统完全够了。我去年写了3篇博客,介绍了grbl的速度前瞻和圆弧插补,是基于1.1版本讲解的:
grbl源码解析——圆弧插补
grbl源码解析——速度前瞻(1)
grbl源码解析——速度前瞻(2)
自己也把grbl0.8版本移植到了stm32f407上,我设置了免积分下载,有兴趣的话可以下载来看:
grbl0.8_stm32
软件架构
Bresenham算法
grbl的基本功能包括G代码解析、直线圆弧插补、速度前瞻、加减速算法、脉冲产生算法、串口通讯、通讯协议解析等组成。阅读源码的时候,大多数代码都很容易的,让我耗时最多的其实是脉冲产生算法和速度前瞻部分。我读书时接触的直线圆弧插补算法为时间分割算法,算出每个插补周期(假设为1ms)需要移动的位移量就行,只需要一个周期为1ms的定时器就行(需要fpga发送脉冲、或者走ethercat)。然而grbl却用了两个定时器来产生脉冲信号,这就把我整蒙了。也许是思维惯性的原因吧,grbl的脉冲产生原理我琢磨了好久才想明白了。
grbl的脉冲产生代码位于stepper.c,其运用了Bresenham算法。Bresenham算法是计算机图形学的基础内容,其实我阅读了计算机图形学中的相关内容,才把grbl的脉冲产生原理搞懂的。
大家可以百度或者B站搜“Bresenham。
// Stepper state variable. Contains running data and trapezoid variables.
typedef struct {
// Used by the bresenham line algorithm
int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y,
counter_z;
uint32_t event_count;
uint32_t step_events_completed; // The number of step events left in current motion
// Used by the trapezoid generator
uint32_t cycles_per_step_event; // The number of machine cycles between each step event
uint32_t trapezoid_tick_cycle_counter; // The cycles since last trapezoid_tick. Used to generate ticks at a steady
// pace without allocating a separate timer
uint32_t trapezoid_adjusted_rate; // The current rate of step_events according to the trapezoid generator
uint32_t min_safe_rate; // Minimum safe rate for full deceleration rate reduction step. Otherwise halves step_rate.
} stepper_t;
stepper_t结构体里就是用于记录脉冲数据和t型加减速变量的。
其中与Bresenham相关的变量为counter_x、counter_y、counter_z、event_count、step_events_completed。
// This struct is used when buffering the setup for each linear movement "nominal" values are as specified in
// the source g-code and may never actually be reached if acceleration management is active.
typedef struct {
// Fields used by the bresenham algorithm for tracing the line
uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
uint32_t steps_x, steps_y, steps_z; // Step count along each axis
int32_t step_event_count; // The number of step events required to complete this block
// Fields used by the motion planner to manage acceleration
float nominal_speed; // The nominal speed for this block in mm/min
float entry_speed; // Entry speed at previous-current block junction in mm/min
float max_entry_speed; // Maximum allowable junction entry speed in mm/min
float millimeters; // The total travel of this block in mm
uint8_t recalculate_flag; // Planner flag to recalculate trapezoids on entry junction
uint8_t nominal_length_flag; // Planner flag for nominal speed always reached
// Settings for the trapezoid generator
uint32_t initial_rate; // The step rate at start of block
uint32_t final_rate; // The step rate at end of block
int32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t accelerate_until; // The index of the step event on which to stop acceleration
uint32_t decelerate_after; // The index of the step event on which to start decelerating
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
} block_t;
该结构体是速度规划缓存,其中与Bresenham相关的变量为steps_x, steps_y, steps_z,step_event_count和direction_bits。
Bresenham算法如下
// 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();
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 = 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
}
}
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<<X_STEP_BIT);
st.counter_x -= st.event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
}
st.counter_y += current_block->steps_y;
if (st.counter_y > 0) {
out_bits |= (1<<Y_STEP_BIT);
st.counter_y -= st.event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; }
}
st.counter_z += current_block->steps_z;
if (st.counter_z > 0) {
out_bits |= (1<<Z_STEP_BIT);
st.counter_z -= st.event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; }
}
st.step_events_completed++; // Iterate step events
// While in block steps, check for de/ac-celeration events and execute them accordingly.
if (st.step_events_completed < current_block->step_event_count) {
……
}
假设x、y、z方向的位移量为(4,5,6),其存储于steps_x, steps_y, steps_z
。我们可以不用考虑正负值,因为步进电机由方向引脚和脉冲引脚两个输入端构成,位移量的正负由方向引脚的高低电平来表示,因此在grbl的Bresenham算法中,只考虑正值就行了。
首先是变量的初始化
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;
即(counter_x ,counter_y ,counter_z)=(-3,-3,-3)
current_block->step_event_count = max(4,5,6)=6;其位于plan_buffer_line函数中
void plan_buffer_line(float x, float y, float z, float feed_rate, uint8_t invert_feed_rate)
{
// Prepare to set up new block
block_t *block = &block_buffer[block_buffer_head];
// Calculate target position in absolute steps
int32_t target[3];
target[X_AXIS] = lround(x*settings.steps_per_mm[X_AXIS]);
target[Y_AXIS] = lround(y*settings.steps_per_mm[Y_AXIS]);
target[Z_AXIS] = lround(z*settings.steps_per_mm[Z_AXIS]);
// Compute direction bits for this block
block->direction_bits = 0;
if (target[X_AXIS] < pl.position[X_AXIS]) { block->direction_bits |= (1<<X_DIRECTION_BIT); }
if (target[Y_AXIS] < pl.position[Y_AXIS]) { block->direction_bits |= (1<<Y_DIRECTION_BIT); }
if (target[Z_AXIS] < pl.position[Z_AXIS]) { block->direction_bits |= (1<<Z_DIRECTION_BIT); }
// Number of steps for each axis
block->steps_x = labs(target[X_AXIS]-pl.position[X_AXIS]);
block->steps_y = labs(target[Y_AXIS]-pl.position[Y_AXIS]);
block->steps_z = labs(target[Z_AXIS]-pl.position[Z_AXIS]);
block->step_event_count = max(block->steps_x, max(block->steps_y, block->steps_z));
// Bail if this is a zero-length block
if (block->step_event_count == 0) { return; };
接着依据下列代码开始循环运算,计算脉冲
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<<X_STEP_BIT);
st.counter_x -= st.event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
}
st.counter_y += current_block->steps_y;
if (st.counter_y > 0) {
out_bits |= (1<<Y_STEP_BIT);
st.counter_y -= st.event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; }
}
st.counter_z += current_block->steps_z;
if (st.counter_z > 0) {
out_bits |= (1<<Z_STEP_BIT);
st.counter_z -= st.event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; }
}
st.step_events_completed++; // Iterate step events
其计算流程如下
当step_events_completed值与step_event_count 相等时,则需要对缓存块重新初始化
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// runtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize()
{
if (current_block != NULL) {
// Replan buffer from the feed hold stop location.
plan_cycle_reinitialize(current_block->step_event_count - st.step_events_completed);
// Update initial rate and timers after feed hold.
st.trapezoid_adjusted_rate = 0; // Resumes from rest
set_step_events_per_minute(st.trapezoid_adjusted_rate);
st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Start halfway for midpoint rule.
st.step_events_completed = 0;
sys.state = STATE_QUEUED;
} else {
sys.state = STATE_IDLE;
}
}
脉冲产生
脉冲产生一共需要用到两个定时器,一个控制运算周期(TIm3)、一个控制脉宽(TIm4)。我自己写了一个demo(基于stm32)用于测试Bresenham算法。
定时器初始化代码如下
void TIM3_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 2000-1;
TIM_TimeBaseStructure.TIM_Prescaler = 8400-1; // 10kHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 清除定时器更新中断标志位
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
// 开启定时器更新中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM3, DISABLE);
}
void TIM4_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 10000-1;
TIM_TimeBaseStructure.TIM_Prescaler = 840-1; // 1000kHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 清除定时器更新中断标志位
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
// 开启定时器更新中断
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM4, DISABLE);
}
Bresenham算法移植到stm32
// Step and direction port invert masks.
static uint8_t step_port_invert_mask = ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT));
static uint8_t dir_port_invert_mask = ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT));
struct Line {
uint32_t steps_x, steps_y, steps_z;
int32_t maximum_steps;
uint8_t direction_bits;
uint32_t rate;
};
struct Line line_temp;
struct Line *current_line = 0;
volatile int32_t counter_x, counter_y, counter_z; // counter variables for the bresenham line tracer
uint32_t iterations; // The number of iterations left to complete the current_line
uint8_t out_bits = 0; // The next stepping-bits to be output
uint8_t axis0 = 0;
uint8_t axis1 = 0;
uint8_t axis2 = 0;
void st_buffer_line(int32_t steps_x, int32_t steps_y, int32_t steps_z, uint32_t microseconds)
{
struct Line *line = &line_temp;
line->steps_x = labs(steps_x);
line->steps_y = labs(steps_y);
line->steps_z = labs(steps_z);
line->maximum_steps = max(line->steps_x, max(line->steps_y, line->steps_z));
// Bail if this is a zero-length line
if (line->maximum_steps == 0) { return; };
line->rate = microseconds/line->maximum_steps;
uint8_t direction_bits = 0;
if (steps_x < 0) { direction_bits |= (1<<X_DIRECTION_BIT); }
if (steps_y < 0) { direction_bits |= (1<<Y_DIRECTION_BIT); }
if (steps_z < 0) { direction_bits |= (1<<Z_DIRECTION_BIT); }
line->direction_bits = direction_bits;
current_line = &line_temp;
counter_x = -(current_line->maximum_steps >> 1);
counter_y = counter_x;
counter_z = counter_x;
iterations = current_line->maximum_steps;
TIM_Cmd(TIM3, ENABLE);
}
void Stepper_MainISR(void)
{
if(out_bits & (1<<X_STEP_BIT))
{
if(step_port_invert_mask & (1<<X_STEP_BIT))
{
GPIO_SetBits(GPIO_STEP_X_PORT, GPIO_STEP_X_PIN);
axis0 = 1;
}
else
{
GPIO_ResetBits(GPIO_STEP_X_PORT, GPIO_STEP_X_PIN);
axis0 = 0;
}
}
if(out_bits & (1<<Y_STEP_BIT))
{
if(step_port_invert_mask & (1<<Y_STEP_BIT))
{
GPIO_SetBits(GPIO_STEP_Y_PORT, GPIO_STEP_Y_PIN);
axis1 = 1;
}
else
{
GPIO_ResetBits(GPIO_STEP_Y_PORT, GPIO_STEP_Y_PIN);
axis1 = 0;
}
}
if(out_bits & (1<<Z_STEP_BIT))
{
if(step_port_invert_mask & (1<<Z_STEP_BIT))
{
GPIO_SetBits(GPIO_STEP_Z_PORT, GPIO_STEP_Z_PIN);
axis2 = 1;
}
else
{
GPIO_ResetBits(GPIO_STEP_Z_PORT, GPIO_STEP_Z_PIN);
axis2 = 0;
}
}
TIM_Cmd(TIM4, ENABLE);
//Bresenham算法
if (current_line != 0)
{
out_bits = current_line->direction_bits;
counter_x += current_line->steps_x;
if (counter_x > 0) {
out_bits |= (1<<X_STEP_BIT);
counter_x -= current_line->maximum_steps;
}
counter_y += current_line->steps_y;
if (counter_y > 0) {
out_bits |= (1<<Y_STEP_BIT);
counter_y -= current_line->maximum_steps;
}
counter_z += current_line->steps_z;
if (counter_z > 0) {
out_bits |= (1<<Z_STEP_BIT);
counter_z -= current_line->maximum_steps;
}
// If current line is finished, reset pointer
iterations -= 1;
if (iterations <= 0) {
current_line = 0;
}
}
else
{
out_bits = 0;
TIM_Cmd(TIM3, DISABLE);
}
//out_bits ^= 0;
}
void Stepper_PortResetISR(void)
{
// Reset stepping pins (leave the direction pins)
if(step_port_invert_mask & (1<<X_STEP_BIT))
{
GPIO_ResetBits(GPIO_STEP_X_PORT, GPIO_STEP_X_PIN);
axis0 = 0;
}
else
{
GPIO_SetBits(GPIO_STEP_X_PORT, GPIO_STEP_X_PIN);
axis0 = 1;
}
if(step_port_invert_mask & (1<<Y_STEP_BIT))
{
GPIO_ResetBits(GPIO_STEP_Y_PORT, GPIO_STEP_Y_PIN);
axis1 = 0;
}
else
{
GPIO_SetBits(GPIO_STEP_Y_PORT, GPIO_STEP_Y_PIN);
axis1 = 1;
}
if(step_port_invert_mask & (1<<Z_STEP_BIT))
{
GPIO_ResetBits(GPIO_STEP_Z_PORT, GPIO_STEP_Z_PIN);
axis2 = 0;
}
else
{
GPIO_SetBits(GPIO_STEP_Z_PORT, GPIO_STEP_Z_PIN);
axis2 = 1;
}
TIM_Cmd(TIM4, DISABLE);
}
中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
Stepper_MainISR();
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
Stepper_PortResetISR();
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
demo的主函数
int main(void)
{
//初始化延时函数
delay_init(168);
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/* LED 端口初始化 */
LED_GPIO_Config();
/*初始化按键*/
Key_GPIO_Config();
//Configure step and direction interface pins
GPIO_InitGPIO(GPIO_STEPPER);
TIM3_Init();
TIM4_Init();
TIM_Cmd(TIM3, ENABLE);
TIM_Cmd(TIM4, ENABLE);
/* 轮询按键状态,若按键按下则反转LED */
while(1)
{
st_buffer_line(1,2,3,0);
delay_ms(2000);
st_buffer_line(3,3,3,0);
delay_ms(2000);
st_buffer_line(2,4,6,0);
delay_ms(2000);
}
}
实验效果
tim3的中断为200ms触发一次(即pwm频率最高为5hz),tim4的中断为100ms触发一次(即pwm高电平持续100ms)。因此tim3决定pwm的频率。
pwm频率
上文提到,tim3决定了pwm的频率。TIM3->ARR = arr和TIM3->PSC=psc决定了tim3的频率。频率=84m/(arr*psc)。
注:arr为16位寄存器、psc为16位寄存器
st_buffer_line(1,2,10,500000);
line->rate = microseconds/line->maximum_steps;
config_step_timer(current_line->rate);
st_buffer_line(1,2,10,500000)表示发送10个脉冲,需要500000us。即发送x个脉冲,需要y us。可求出pwm频率=xm/y hz。
由频率=xm/y hz 和 频率=84m/(arrpsc)。可得出arrpsc = 84*y/x = 84 * line->rate 。
因此,改写config_step_timer()函数。
// 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
//cycles 的单位是 cpu ticks per step即每个脉冲占用多少个cpu周期
//这个函数可以实现的最高pwm频率为1000khz(理论值),即TIM3->PSC=84-1、TIM3->ARR=1-1
//假设步进电机最高转速为3r/s、32细分。即pwm频率=3*32*200=19200hz。即每个脉冲为52.1us。16细分为104.2us。
//cycles的单位是microseconds/step
static uint32_t config_step_timer(uint32_t cycles)
{
uint16_t ceiling;
uint16_t prescaler;
uint32_t actual_cycles;
if (cycles <= 0xffffL) { //65536
ceiling = cycles;
prescaler = 1; // prescaler: 不分频
actual_cycles = ceiling;
} else if (cycles <= 0x7ffffL) { //65536*8
ceiling = cycles >> 3;
prescaler = 8; // prescaler: 8分频
actual_cycles = ceiling * 8L;
} else if (cycles <= 0x3fffffL) { //65536*64
ceiling = cycles >> 6;
prescaler = 64; // prescaler: 64分频
actual_cycles = ceiling * 64L;
} else if (cycles <= 0xffffffL) { //65536*256
ceiling = (cycles >> 8);
prescaler = 256; // prescaler: 256分频
actual_cycles = ceiling * 256L;
} else if (cycles <= 0x3ffffffL) { //65536*1024
ceiling = (cycles >> 10);
prescaler = 1024; // prescaler: 1024分频
actual_cycles = ceiling * 1024L;
} else {
// Okay, that was slower than we actually go. Just set the slowest speed
//超出1024的分频范围的话就用最长的定时,即最慢的速度,由于psc寄存器是16位的,65536/84=780.2。因此顶多780*84分频。
ceiling = 0xffff;
prescaler = 1024;
actual_cycles = 0xffff * 1024;
}
// Set prescaler,avr的定时器为16mhz,stm32的tim3为84mhz
//由公式可得arr*psc = 84*y/x = 84 * line->rate 。 将84赋值给TIM3->PSC、将rate(即cycles)赋值给TIM3->ARR
TIM3->PSC = prescaler * 84 - 1;
TIM3->ARR = ceiling-1; //计数器自动重装值
//设定pwm的占空比为周期的一半,当然也可以设定为1个固定值,比如10us。
TIM4->PSC = TIM3->PSC;
TIM4->ARR = (ceiling>>1) - 1;
return(actual_cycles);
}
void st_buffer_line(int32_t steps_x, int32_t steps_y, int32_t steps_z, uint32_t microseconds)
{
struct Line *line = &line_temp;
line->steps_x = labs(steps_x);
line->steps_y = labs(steps_y);
line->steps_z = labs(steps_z);
line->maximum_steps = max(line->steps_x, max(line->steps_y, line->steps_z));
// Bail if this is a zero-length line
if (line->maximum_steps == 0) { return; };
line->rate = microseconds/line->maximum_steps;
uint8_t direction_bits = 0;
if (steps_x < 0) { direction_bits |= (1<<X_DIRECTION_BIT); }
if (steps_y < 0) { direction_bits |= (1<<Y_DIRECTION_BIT); }
if (steps_z < 0) { direction_bits |= (1<<Z_DIRECTION_BIT); }
line->direction_bits = direction_bits;
current_line = &line_temp;
config_step_timer(current_line->rate);
counter_x = -(current_line->maximum_steps >> 1);
counter_y = counter_x;
counter_z = counter_x;
iterations = current_line->maximum_steps;
TIM_Cmd(TIM3, ENABLE);
}
实验结果
st_buffer_line(1,2,10,500000);//最高频率为50ms,即20hz
delay_ms(1000);
st_buffer_line(1,5,10,500);//最高频率为50us,即20khz
delay_ms(1000);
st_buffer_line(5,6,7,7000000);//最高频率为1000000us,即1hz
delay_ms(10000);
实验结果可知,满足要求。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)