之前在arduino上的实现的新的步进电机算法,需要移植到32位MKE06K128上。这个任务听上去事实而非,整个Marlin固件还涉及别的部分,比如PID加热控制模块,舵机模块,串口指令读取代码。但是我们cocky的Teamleader很反感移植Marlin。没办法,这一步我的理解,只要能实现plan_buffer_line就算完成任务。进一步简化,只要实现一个轴的plan_buffer_line就算完成任务。


硬件连接部分:


我们cocky的Teamleader是个能干的人,很快画出并焊好了MKE06K128的电路板供我当作开发板使用。Freescale有基于eclipse的专用开发环境Kinetis Design Studio,简称KDS,KDS既集成了MKE的交叉编译器,而且以插件的形式提供了“专家系统”,能够可视化的配置硬件管脚,并且生成示意图方便review(如下图),看上去很酷。


这个专家工具在调试硬件和快速实现阶段也非常方便。我在电路板上接上Jlink,并且连接上A4988和步进电机,在排除掉A4988的损坏之后。编写一个简答的定时器程序,并把周期写成3.2KHz(请参考arduino上的实现中关于3.2K的来历),步进电机就应该以1rad/s的速度转,如果不是,就要用示波器和万用表排除芯片,电路板,电源和Jlink的故障可能。如果是,就为下一步编写固件代码打下了坚实的基础。最终调试好的硬件连接图如下:


使用jlink时注意,

在系统上电的情况下,需要去掉jlink上供电的跳线帽,否则无法进行刷写工作。

即使脉冲宽度很窄,也可以驱动电机,而且窄脉冲的噪音很小。

A4988的EN路上拉到电路5V,需要软件将其拉低,步进电机才能正常驱动,否则驱动电路不使能。


软件实现部分:


首先实现plan_buffer_line()函数,需要编写%Planner和%Stepper两个模块。前者管理队列,后者管理步进电机的中断响应驱动。和Marlin固件的区别在于,这里的%Planner不必计算执行的各个速度节点,而是只需要设置稳定后的速度值,%Stepper会动态的计算出瞬时速度。

队列和队列指针都在%Planner模块中定义为全局变量,由于声明了external 关键字,当其他项目包含planner.h时就会引入该变量,即等效于该全局变量为全项目全局变量。

%Planner.cpp中:

block_tblock_buffer[BLOCK_BUFFER_SIZE];volatileunsignedcharblock_buffer_head;volatileunsignedcharblock_buffer_tail;

另外设置了几个强制内联函数:

FORCE_INLINEblock_t*plan_get_current_block();//读取当前block函数FORCE_INLINEboolblocks_queued(){return(block_buffer_head!=block_buffer_tail);}//队列是否非空

在这里:

#defineFORCE_INLINEattribute((always_inline))inline

告诉编译器,设置为强制内联型;对于此,Cpp的语法解释是:

inline关键字仅仅是建议编译器做内联展开处理,而不是强制。在gcc编译器中,如果编译优化设置为-O0,即使是inline函数也不会被内联展开,除非设置了强制内联(attribute((always_inline)))属性。

关于内联函数,补充一点基础知识:

在内联函数内不允许用循环语句和开关语句。否则会被编译器当作普通函数。

在%stepper.cpp中定义了:

block_t*current_block//当前运动实例

由于没有在stepper.h中定义相应的extern 类型,所以该变量为模块内的private全局变量。

即使是使用32位单片机,也不应该在中断响应函数中进行浮点运算,否则中断频率会被大大拖慢。所以%stepper函数的最终结果为:

voidST_PULSE_TI_OnInterrupt(void){/*Writeyourcodehere...*/#ifndefHARDWARE_DEBUG_MODEif(!current_block){current_block=plan_get_current_block();}#defineDISTANCE_COUNT_RESETcurrent_block->rounds_count_per_mstep-=current_block->one_micro_step_mm#defineDISTANCE_IS_ONESTEPcurrent_block->rounds_count_per_mstep>=current_block->one_micro_step_mm#defineDRIVE_PULSEE0_STE_SetVal();\E0_STE_ClrVal()/*==========generateapulsewhenastepaccumulated===========*/if(DISTANCE_IS_ONESTEP){DRIVE_PULSE;DISTANCE_COUNT_RESET;}/*updatethenewspeedand...*/if(current_block){if(current_block->rounds_behind+current_block->rounds_ahead<current_block->rounds){//accumulatetheroundsandthemicrostep_count_for_roundscurrent_block->rounds_behind+=current_block->instance_rate;current_block->rounds_count_per_mstep+=current_block->instance_rate;//whenspeedclimbingupcaseif(current_block->instance_rate<current_block->nominal_rate){current_block->instance_rate+=current_block->acceleration;}//whenhold thenominal_ratecaseelse{//makesuretherateremainsnominal_ratecurrent_block->instance_rate=current_block->nominal_rate;}//updatetheroundsleftforallthethreecase.current_block->rounds_ahead=current_block->instance_rate/2/current_block->acceleration*current_block->instance_rate;}//whenspeedslippingdowncaseelseif(current_block->instance_rate>current_block->exit_rate){current_block->rounds_behind+=current_block->instance_rate;current_block->rounds_count_per_mstep+=current_block->instance_rate;current_block->instance_rate-=current_block->acceleration;}//attheendoftheblockelseif(current_block->instance_rate<=current_block->exit_rate){current_block->instance_rate=0;current_block->nominal_rate=0;current_block->rounds_ahead=0;current_block->acceleration=0;current_block->rounds=0;current_block->rounds_count_per_mstep=0;//ST_PULSE_TI_Disable();//current_blockshouldupdatecurrent_block=NULL;}}#endif}

浮点数主要来自两方面,一是每个中断响应函数中的单位时间delta t;另一个是转每秒这个单位中,转往往是小数。为了消灭浮点数,我们对单位进行了转换:

voidplan_buffer_line(constfloate,floatfeed_rate){//voidplan_buffer_line(constfloatx,constfloaty,constfloatz,constfloate,floatfeed_rate,constuint8_textruder){//eisunitofround//feedrateisunitofroundpersecond//pushablockintopipelineblock_t*block=&block_buffer[block_buffer_head];//updatetheinivalueoftheblockblock->nominal_rate=(unsignedint)(feed_rate*ST_PULSE_FREQ);block->rounds=(unsignedint)(e-position[E_AXIS])*FLOAT_FACTOR;block->entry_rate=0;block->exit_rate=0;block->acceleration=(unsignedint)(DEFAULT_ACCELERATION);block->direction_bits=e>position[E_AXIS]?1:0;block->one_micro_step_mm=(unsignedint)FLOAT_FACTOR/MICROSTEP/RESOLUTION;ST_PULSE_TI_Enable();//EnabletheInterruptionforsteppercontrolandplannerS//Movebufferheadblock_buffer_head=next_block_index(block_buffer_head);//block_buffer_head=block_buffer_head+1;//Updatepositionposition[E_AXIS]=e;}

其中,在configuration.h中定义了转换宏:

#defineST_PULSE_FREQ10000#defineFLOAT_FACTOR(ST_PULSE_FREQ*ST_PULSE_FREQ)

由此可见,为了在计时器中断响应函数中不出现浮点数,必须要给位移,速度和加速度乘以一个因子。而为了防止程序中出现变量超出整型大小而溢出,计时器频率不能太高。具体的范围,又受限于打印机的位移范围(往往是0~200mm)。这种算法,数据结构和处理器浮点运算性能局限性的共同作用,产生了最终的代码,非常的经典。

正是由于Team leader质疑修改步进电机算法,我才能发现这么多隐藏在理所当然中的深刻限制和工程智慧,感谢他的偏执。同时我更加深刻理解了Marlin固件中步进电机算法的合理和经典。

(完)