完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
我原来在16年初的时候,用光驱的步进电机DIY了1个简易的雕刻机,有人提到用雕刻机做PCB板,但光驱步进电机功率太小,所以重新设计了这个雕刻机。
主要分成2部分,一部分为控制器,一部分为主机。 控制器:主芯片:STM32F103ZET6;显示 TFT 3.2寸带触摸,FSMC控制;NRF24L01通讯;SD卡 主机:控制采用STM32F103C8T6最小系统版;NRF24L01通讯;37步进电机*3,A4988步进电机控制器*3,XD211槽型光电模块*2 X轴,Y轴限位用,755高速电机,JTO0.3-4钻夹头;直径8mm,行程1mm丝杆;滑块,不锈钢外壳。 不锈钢外壳我先通过三维SolidWorks把外形设计出来后用1.5mm不锈钢激光切割折弯而成。 刚开始的时候,只用了1个单片机直接控制,但发现安装和操作不方便,所以使用了2个单片机,一个C8T6直接控制雕刻机,一个ZET6带TFT触摸屏进行控制和设置。2个单片机之间用NRF24L01进行通信,减少相互间联线。 这些是雕刻机的照片,我用双色板来模拟的PCB板,雕刻机主机下面的主板就是用这个程序雕刻出来的。 现在这个程序我USB和串口控制还没有加入。现在是SD卡文件控制。 这个是雕刻机的程序: 我使用了简易的OS操作系统,是在凤舞天的MSOS修改过来的,共支持8个任务的切换。 控制器ZET6主要有4个任务 1. NRFTask NRF24L01的通讯任务. 2. FileTask SD卡文件的处理 3. ShowTask GUI页面文件的处理(包括菜单和雕刻机设置与控制) 4. GUITask GUI底层操作的处理(隐含在GUI_OS.C文件中) 主机C8T6有3个任务 1. NRFTask NRF24L01的通讯任务. 2. CtrlTask G代码的解析任务 3. MotoTask 步进电机的控制任务。 一、 MSOS的介绍---------------------------------在21楼 二、G代码的解析----------------------------------在22楼 三、NRF的通讯------------------------------------在25楼 |
|
相关推荐
49个回答
|
|
一、MSOS基本介绍:
MSOS的基本配置在OS_conf.h中配置 主要是配置任务数量和堆栈的长度,由于MSOS是简化版的uCOS的简化版,因此不提供任务的删除,所有任务需在开始的时候就创建。 [AppleScript] 纯文本查看 复制代码 /*TaskSum必须要跟实际配套,实际的任务数必须小于TaskSum,否则出现异常 如果TaskSum 大于实际任务数,则大于部分的RAM是浪费的,但程序可以运行 为了简化定义,每个任务的任务栈深度和消息队列深度改为相同的大小 */ #define TaskStackSumIsSame 0 /*是否使用相同数量的堆栈*/ #define TaskSum 5 /*任务总数4个用户任务加1个系统任务,最大为8个,其中用户任务7个,系统任务1个*/ #define QueueStackSum 20 //消息队列深度 #if TaskStackSumIsSame == 0 #define TaskStackSum0 200 //任务栈深度 #define TaskStackSum1 200 //任务栈深度 #define TaskStackSum2 200 //任务栈深度 #define TaskStackSum3 200 //任务栈深度 #define TaskStackSum4 64 //任务栈深度 #define TaskStackSum5 64 //任务栈深度 #define TaskStackSum6 64 //任务栈深度 #define TaskStackSum7 64 //任务栈深度 #endif #if TaskStackSumIsSame == 1 #define TaskStackSum 200 //任务栈深度 #endif 在创建任务前需要创建任务的指针,同时也是任务的优先级。 [AppleScript] 纯文本查看 复制代码 u8 NRFTaskPriority ; /*NRF解析任务*/u8 MotoTaskPriority ; /*MOTO解析任务*/u8 CtrlTaskPriority;/******************************************************************************** 函数名 : main* 描述 : msOS整个程序入口* : InitializeData:初始化存储数据区* 输入参数 : 无* 返回参数 : int*********************************************************************************版本 作者 日期 说明*V0.1 Wangsw 2013/09/03 初始版本*******************************************************************************/int main(void) { NVIC_Config(); InitializeOs(); //初始化OS InitializeApp(); //初始化应用层 NRFTaskPriority = OS.CreateTask(NRFTask); //创建任务,优先级0 NRFTask为NRF通讯任务的主函数 NRFTaskPriority 为NRFTask的优先级同时也是指针 CtrlTaskPriority = OS.CreateTask(CtrlTask); MotoTaskPriority = OS.CreateTask(MotoTask); OS.Start(); //MSOS启动} 任务的优先级是按照创建的顺序来实现的,最早创建的任务优先级最高。系统任务也就是空闲任务在OS.Start()中自动创建。 用户任务必须为死循环,在死循环中必须有OS.PendMessageQueue函数或OS.DelayTimeTick函数把系统的控制权交还给OS来切换任务。 [AppleScript] 纯文本查看 复制代码 /******************************************************************************** 函数名 : PendMessageQueue* 描述 : 等待消息队列,当消息队列为空时,所在任务挂起* 输入参数 : eventPointer 队列事件块指针,timeout 等待时间,1mS为单位* 返回参数 : *********************************************************************************版本 作者 日期 说明*V0.2 ZZ 2013/10/08 修改了不能同时post2个不是最高优先级的BUG*V0.1 Wangsw 2013/09/11 初始版本*******************************************************************************/static void * PendMessageQueue (uint32_t timeout){ void * messagePointer; Queue * queuePointer;// Assert(InterruptNesting == 0); EnterCritical(); queuePointer = OsCurrentTaskPointer->QueuePointer; /* Point at queue control block */ if (queuePointer->Entries > 0) /* See if any messages in the queue */ { messagePointer = PopQueue(queuePointer); ExitCritical(); return (messagePointer); /* Return message received */ } OsCurrentTaskPointer->State |= TaskStatusMessageQueue; /* Task will have to pend for a message to be posted */ OsCurrentTaskPointer->Delay = timeout; /* Load timeout into TCB */ PriorityReadyTable &= ~(1 << OsCurrentPriority); ExitCritical(); Schedule(); /*任务切换节点,如果当前没有message的话就切换任务,当有message的时候就从这里切换回来 */ EnterCritical(); queuePointer = OsCurrentTaskPointer->QueuePointer; messagePointer = PopQueue(queuePointer); if ((messagePointer != OS_null) || (timeout == 0)) /*Wangsw add || (timeout == 0) for direct DataQueue */ { /* Did we get a message? */ OsCurrentTaskPointer->MessagePointer = OS_null; /* Extract message from TCB (Put there by QPost) */ OsCurrentTaskPointer->State = TaskStatusReady; ExitCritical(); return (messagePointer); /* Return message received */ } OsCurrentTaskPointer->State = TaskStatusReady; ExitCritical(); return (OS_null); /* No message received */}/******************************************************************************** 函数名 : DelayTimeTick* 描述 : 任务挂起等待时间,单位为1mS* 输入参数 : timeTick,挂起时间,1mS为单位,比如1000,就是1秒钟* 返回参数 : 无*********************************************************************************版本 作者 日期 说明*V0.1 Wangsw 2013/09/11 初始版本*******************************************************************************/static void DelayTimeTick (uint32_t timeTick){ if (timeTick > 0) { EnterCritical(); PriorityReadyTable &= ~(1 << OsCurrentPriority); OsCurrentTaskPointer->Delay = timeTick; ExitCritical(); Schedule(); }} PendMessageQueue 主要是在运行到这个函数的时候进行消息获取,但如果消息列队中为空的时候进行任务切换。 DelayTimeTick 是当需要延时时切换出任务,当延时时间到后切换回来,时间单位为1ms 如果一个任务是纯粹的死循环,不需要其他任务发送给消息的时候可以不用PendMessageQueue 函数,只用DelayTimeTick 短时延时并交出控制权。 对应于PendMessageQueue 函数PostMessageQueue 为消息发送函数 [AppleScript] 纯文本查看 复制代码 /******************************************************************************** 函数名 : PostMessageQueue* 描述 : 发送一个消息到消息队列中,处于等待的任务会自动运行* 输入参数 : eventPointer 队列事件块指针,messagePointer发送消息指针* 返回参数 : 无*********************************************************************************版本 作者 日期 说明*V0.2 ZZ 2013/10/08 修改了不能同时post2个不是最高优先级的BUG*V0.1 Wangsw 2013/09/11 初始版本*******************************************************************************/static uint8_t PostMessageQueue (uint8_t Priority, void *messagePointer){ Queue * queuePointer; Task *taskPointer; // Assert(TaskCounter >= Priority); EnterCritical(); taskPointer = &TaskBlock[Priority]; queuePointer = taskPointer->QueuePointer; /* Point to queue control block */ if (queuePointer->Entries == 0x00) /* 当前的队列里消息长度为0 */ { PushQueue(queuePointer,messagePointer); //将消息压入队列 taskPointer->Delay = 0; taskPointer->MessagePointer = messagePointer; taskPointer->State &= ~TaskStatusMessageQueue; if (taskPointer->State == TaskStatusReady) //如果是ready状态 { PriorityReadyTable |= 1 << taskPointer->Priority; ExitCritical(); Schedule(); /* Find highest priority task ready to run */ return (OS_true); } PushQueue(queuePointer,messagePointer); ExitCritical(); return (OS_true); } if (queuePointer->Entries >= queuePointer->Size) { /* Make sure queue is not full */ ExitCritical(); return (OS_false); } PushQueue(queuePointer,messagePointer); ExitCritical(); return (OS_true);} PostMessageQueue 为发送一个消息到指定任务的消息列队,消息发送完成后如果接收消息的任务优先级高于本身的优先级的话,MSOS为自动切换任务到接收消息的任务中去。 消息为32位的4字节的数据,可以是指针,函数或者你自己定义的消息,具体区分靠最高为字节来定义数据类型,具体类型在OS_CONF.H中定义(0xF0-0XFF,0X20和0X08),或者在APP中定义,但不能是0X20和0X08。 正常的一个任务的主函数如下: 通过选择获取的message 的内容来知道获取的是什么类型的数据,并进行处理。 [AppleScript] 纯文本查看 复制代码 void MotoTask(void){ u32 message; InitPlanQueue(); while(1) { message = (u32)OS.PendMessageQueue(5000); switch(GetMessageType(message)) { case UseMessage_PlanRun: //Plan运行消息 PlanRun(); break; case FuntionMessageType: //函数指针消息,直接以0x20或0x08来判断 Function(message); break; default: break; } }} MSOS提供了2种软定时器,一种由MSOS自动分配定时器的编号,时间间隔为1ms,定时器在计时过程中无法取消;另一种为指定定时器编号,定时时间为0.1ms,指定定时器编号的可以在计时过程中取消定时。 [AppleScript] 纯文本查看 复制代码 /******************************************************************************** 函数名 : Start* 描述 : 软件定时器* 输入参数 : handleMode: 两种处理方式,一种直接在节拍中断中处理,适合费用低的,* 另一种在消息中处理,适合处理费用高的。* : delay:延时节拍数,以系统节拍为单位* : registerFunction: 注册回调执行函数,延时超时后,执行此函数。* data:消息值,MessageTimer类型为32bit地址,其他类型下都是24bit数据* 返回参数 : uint8_t类型,返回ID号,从0开始,若失败则返回invalid(-1)*******************************************************************************/static uint8_t Start(TimerhandleModeEnum handleMode, uint32_t delay, OS_func registerFunction){ uint8_t i; EnterCritical(); for(i = 0; i < Timer1msSum; i++) { if(!OS_GetBit(State_1ms, i)) { TimerBlock_1ms.Delay = delay;/*延时时间*/ TimerBlock_1ms.RegisterFunction = registerFunction;/*回调函数*/ if(handleMode) { OS_SetBit(Mode_1ms, i); TimerBlock_1ms.Priority = OsCurrentPriority; } else { OS_ResetBit(Mode_1ms, i); } OS_SetBit(State_1ms, i); ExitCritical(); return(i); } } ExitCritical(); return(OS_invalid);}/******************************************************************************** 函数名 : Start* 描述 : 软件定时器* 输入参数 : handleMode: 两种处理方式,一种直接在节拍中断中处理,适合费用低的,* 另一种在消息中处理,适合处理费用高的。* : delay:延时节拍数,以系统节拍为单位* : registerFunction: 注册回调执行函数,延时超时后,执行此函数。* data:消息值,MessageTimer类型为32bit地址,其他类型下都是24bit数据* 返回参数 : uint8_t类型,返回ID号,从0开始,若失败则返回invalid(-1)*******************************************************************************/void StartAt(uint8_t id, uint32_t delay, OS_func registerFunction){ EnterCritical(); TimerBlock_100us[id].Delay = delay;/*延时时间*/ TimerBlock_100us[id].RegisterFunction = registerFunction;/*回调函数*/ TimerBlock_100us[id].Priority = OsCurrentPriority; OS_SetBit(State_100us, id); ExitCritical();}/******************************************************************************** 函数名 : Stop* 描述 : 停止某一路的软件定时器* 输入参数 : id为0、1、2...* 返回参数 : 无*********************************************************************************版本 作者 日期 说明*V0.1 Wangsw 2013/09/10 初始版本*******************************************************************************/static void StopAt(uint8_t id) {// Assert(id < Timer1msSum); EnterCritical(); OS_ResetBit(State_100us, id); ExitCritical();} 同时MSOS提供了8个互斥量,编号为0-7,在使用的时候需要自己指定使用那个互斥量编号,由于我使用到的互斥量只是在GUI的TFT读写函数中用到,所以没有做自动分配 [AppleScript] 纯文本查看 复制代码 /******************************************************************************** 函数名 : OSMutexLock* 描述 : 互斥量闭锁* 输入参数 : OSMutexNum:互斥量编号 * 返回参数 : 0:成功;非0:不成功*********************************************************************************版本 作者 日期 说明*V2.0 ZZ 2014/10/24 *******************************************************************************/uint8_t OSMutexLock (uint8_t OSMutexNum){ //不得在中断中调用该函数 // Assert(OSMutexNum <8); if(OSMutexNum > 7) return 1; //mutex编号大于7的话出错 0-7 对应8个通道 EnterCritical(); //关闭中断 if (!OS_IsUseMutex(OSMutexNum)) //互斥标志为0,信号量可用 { OsCurrentTaskPointer->State |=(0x100< |
|
|
|
二、 G_Code代码解析:
1. G_Code文件的获取 我现在的G_Code是存储在SD卡上的,在GUI的FileList控件中通过触摸屏选择需要文件的路径,在FATFS中打开文件 [AppleScript] 纯文本查看 复制代码 /********************************************函数:FileLoad功能:文件读取参数:message:需要读取的文件信息返回:无********************************************/void FileLoad(u32 message){u8* Path;GUI_FileListStruct *FL_T;u8 chgchar[2]={0X5C,0X00};//转义符 等效"" GCodeStruct GCode_One; FL_T = (GUI_FileListStruct *)((message&0xffffff)|RAM_ADDRESS); //获取文件名信息//需增加文件类型判断Path = mymalloc(sizeof(FL_T->path)+sizeof(FL_T->fname)); strcpy((char*)Path,(const char*)FL_T->path); //拷贝path到pname里面strcat((char*)Path,(const char*)chgchar); //添加转义符strcat((char*)Path,(const char*)FL_T->fname); //添加新增的名字f_res = f_open(filescr, (const TCHAR*)Path, FA_OPEN_DEFAULT); //打开文件 myfree(Path); //释放内存 if(f_res == FR_OK) //读取文件正常{DK.File_B_P->Open =1; //文件打开标志DK.File_B_P->NeedRead = 1; //文件需要继续读取DK.File_B_P->NotEnd = 1; //设置文件没有结束标志FileRead = 0; //FileBuffNum = 0; Page_DK_File();ReadFile(); //读取数据到缓存中DK.File_B_P->ReadStart = 1; //文件开始读取标志 while(DK.File_B_P->NotEnd){if(ReadOneLine()==0){TransformGCode(OneLine,&GCode_One); ShowRun(&GCode_One,Green); } } f_res = f_lseek(filescr, 0); FileRead = 0; FileBuffNum = 0; DK.File_B_P->Open =1; //文件打开标志DK.File_B_P->NeedRead = 1; //需要读取文件DK.File_B_P->NotEnd = 1; ReadFile(); //读取数据到缓存中DK.File_B_P->ReadStart = 1; //文件开始读取标志 DK.Ctrl = CTRL_PAUSE; //停止OS.Timer.Start(TimerMessageHandle,10,DK_File_Start);}else{DK.Ctrl = CTRL_STOP; //停止 } } 在FileLoad中先进行一遍G代码的解析(TransformGCode(OneLine,&GCode_One);)和在TFT显示绿色的刀路(ShowRun(&GCode_One,Green); ),在开始雕刻前方便知道选择的CNC文件是否正确。 然后跳转到DK_File_Start中等待开始雕刻的命令。 [AppleScript] 纯文本查看 复制代码 /********************************************函数:DK_File_Start功能:雕刻机脱机SD卡文件开始雕刻参数:无返回:无********************************************/void DK_File_Start(void){// NRF_GCode_Struct *NRF_GCode_P;DK.N = 0;while(DK.File_B_P->NotEnd){if(DK.Ctrl == CTRL_STOP)return; //停止if(DK.Alarm) return; //有故障退出自动if(NRF.Send == 0) //等待上次发送完成{ if(DK.NRF.queue < 5) //从机的队列小于5个{if(!ReadOneLine()) //正常读到数据{if(!TransformGCode(OneLine,&G_Code)) //数据解析正常{ShowRun(&G_Code,Red); //红色显示倒路Send_GCode(&G_Code);} } else{//sd卡数据读取错误}}else{Send_DK_Action(NRF_CX_STATE); //查询DK状态;}}else{OS.DelayTimeTick(5); }}} 当触摸屏开始按钮按下后,开始正式的G代码解析和雕刻。 2. G_Code的解析 由于我自己的雕刻机最大的雕刻尺寸为15mm*15mm,将坐标位置放大100倍对能够满足雕刻机0.01mm的精度,所以没有使用浮点运算,直接通过对浮动数放大100倍来用整型运算,小于0.01的坐标直接放弃,这样所有运算为整型计算,提高运算速度。直线插补和圆弧插补使用逐点插补运算,没有使用复杂的浮点运算全部为整型计算。 坐标位置X 1.01 Y 2.02 对应的值为整型 X 101 Y 202 。 这样对移植可能有点问题,但对于我自己的机器精度完全足够 这个是G_Code的结构体,我只取我需要使用的坐标代码。 [AppleScript] 纯文本查看 复制代码 typedef struct {u8 Action; //功能码u16 ValueWords; //位标志,对应X,Y等有数据坐标的对应位置1s32 X;s32 Y;s32 I;s32 J;s32 Z; u32 N; }GCodeStruct; TransformGCode是将line中的一条ASC码存储的G代码转换成自己需要的控制代码。 a) 流程是先判断是否为注释,如果是注释则直接返回。 b) 对控制码G和M全部遍历一遍,应为在G代码中会出现如G00 G90 G94 G71 G40 G54 G80这样的代码,对于设置的代码则直接处理掉。 c) 对坐标代码遍历一遍,并对数据的对应标志位置1(SetBit(Queue->ValueWords,word_bit); ),因为在简化的G代码控制中有时候坐标是缺省的,如G01 X62.65 Y57.71;G01 X59.44第二个G代码中Y坐标就省略了。这样在插补的时候知道那个坐标需要使用原来的而不是0. 当G代码转换完成后且没有出错就开始通过NRF发送到主机进行运行。(函数:Send_GCode(&G_Code);) [AppleScript] 纯文本查看 复制代码 u8 TransformGCode(u8 *line,GCodeStruct * Queue)//parser_state_t *G_Code){u8 char_counter = 1;u8 letter;s32 value_f;s32 value_i;u8 word_bit = 0; //跟踪变量的位值 DK.GC_Status = 0;mymemset(Queue,0,sizeof(GCodeStruct)); if (line[1] == ';'|| line[1] == '('|| line[1] == '%')return GCSTATUS_OK; // commentsDK.N++;// Pass 1: Commandswhile (next_statement(&letter, &value_f,&value_i, line, &char_counter)){switch (letter){case 'N':break;case 'G':SetBit(Queue->ValueWords,WORD_GM);switch (value_i){case 0: Queue->Action = GC_SEEK_G0; break; //快速移动case 1: Queue->Action = GC_LINEAR_G1; break;//直线移动case 2: Queue->Action = GC_CW_ARC; break;case 3: Queue->Action = GC_CCW_ARC; break;case 4: Queue->Action = GC_DWELL_G4; break; //暂停case 20: DK.State_B_P->Inches_Mode = true; break;case 21: DK.State_B_P->Inches_Mode = false; break;case 28: Queue->Action = GC_GO_HOME_G28; break;//移动到原点 case 90: DK.State_B_P->Absolute_Mode = true; break;case 91: DK.State_B_P->Absolute_Mode = false;break;case 92: Queue->Action = GC_RESET_XYZ_G92; break;//G92:定义当前位置 例如:G92X10允许编程的绝对零点,通过重置当前位置为指定的值。这将设置机器的X坐标为10;若没有指定坐标的G92将重置所有轴为零。case 30: case 64:case 40:case 17: // G17 蔓犷?疣犷麇?镫铖觐耱?X-Ycase 94: // Feedrate per minutecase 98: // Feedrate per minute (group type A)case 97: // Constant spindle speed M T Takes an S address integer, which is interpreted as rev/min (rpm). The default speed mode per system parameter if no mode is programmed. case 49: // Tool length offset compensation cancelcase 80: // Cancel canned cyclebreak;default: FAIL(GCSTATUS_UNSUPPORTED_STATEMENT);}break;case 'M':SetBit(Queue->ValueWords,WORD_GM);switch (value_i){case 112: // Emergency Stop case 0:case 1:case 2:case 30:case 60:Queue->Action = GC_STOP;break;case 3: Queue->Action = GC_M03; break;// case 4: G_Code->spindle_direction = -1; break;case 5: Queue->Action = GC_M05; break;case 23: // Thread gradual pullout ONcase 24: // Thread gradual pullout OFFcase 52: // Unload Last tool from spindlecase 49: // Feedrate override NOT allowedcase 48: // Feedrate override allowedcase 8: // Coolant oncase 9: // Coolant offcase 105: // M105: Get Extruder Temperature Example: M105 Request the temperature of the current extruder and the build base in degrees Celsius. The temperatures are returned to the host computer. For example, the line sent to the host in response to this command looks like case 106: // M106: Fan On Example: M106 S127 Turn on the cooling fan at half speed. Optional parameter 'S' declares the PWM value (0-255) case 107: // Fan Off case 108: // M108: Set Extruder Speed Sets speed of extruder motor. (Deprecated in current firmware, see M113) case 110: // Set Current Line Number case 113: // Set Extruder PWM case 140: // Bed Temperature (Fast) Example: M140 S55 Set the temperature of the build bed to 55oC case 141: //Chamber Temperature (Fast) Example: M141 S30 Set the temperature of the chamber to 30oCcase 142: // Holding Pressure Example: M142 S1 Set the holding pressure of the bed to 1 bar. case 6:return DK.GC_Status;default: FAIL(GCSTATUS_UNSUPPORTED_STATEMENT);}break;}if (DK.GC_Status)return (DK.GC_Status);}if (DK.GC_Status)return(DK.GC_Status);char_counter = 1;while (next_statement(&letter, &value_f, &value_i,line, &char_counter)){double unit_millimeters_value = to_millimeters(value_f);switch (letter){case 'X':word_bit = WORD_X;if (DK.State_B_P->Absolute_Mode)Queue->X = unit_millimeters_value;elseQueue->X += unit_millimeters_value;break; case 'Y':word_bit = WORD_Y;if (DK.State_B_P->Absolute_Mode)Queue->Y = unit_millimeters_value;elseQueue->Y += unit_millimeters_value;break;case 'Z':word_bit = WORD_Z;if (DK.State_B_P->Absolute_Mode)Queue->Z = unit_millimeters_value;elseQueue->Z += unit_millimeters_value;break;case 'I':word_bit = WORD_I; Queue->I = unit_millimeters_value; break; case 'J':word_bit = WORD_J;Queue->J = unit_millimeters_value; break; case 'K':break; case 'E': break;case 'F':break;case 'P':case 'S': break;case 'R':case 'G':case 'N':case 'M':break;default:FAIL(GCSTATUS_UNSUPPORTED_PARAM);}if(word_bit){if (GetBit(Queue->ValueWords,word_bit)) { FAIL(FAIL_WORD_REPEATED); } // [Word repeated]SetBit(Queue->ValueWords,word_bit); }}return(DK.GC_Status);} |
|
|
|
非常好,学习下楼主的步进电机速度处理
|
|
|
|
|
|
三、NRF通讯
我的NRF通讯分主从机结构,控制端为ZET6主机,雕刻机C8T6为从机。主机发送命令后从机响应返回数据。 NRF通讯是由NRFTASK任务来完成的,通讯的命令为Buff中的第一个字来区分这组Buff是什么内容。 [AppleScript] 纯文本查看 复制代码 /*****通讯控制码****/#define End_D 0x5A //结束码#define NRF_CX 0x10 //通讯协议:查询#define NRF_CX_STATE 0x11 //通讯协议:查询#define NRF_CX_SYS 0x12 #define NRF_KZ 0x20 //通讯协议:控制#define NRF_KZ_GCODE 0x21#define NRF_KZ_DW 0x22#define NRF_KZ_RESET 0x23#define NRF_KZ_MAINMOTO_ON 0x24#define NRF_KZ_MAINMOTO_OFF 0x25#define NRF_KZ_MOTO_ON 0x26#define NRF_KZ_MOTO_OFF 0x27#define NRF_KZ_GOTOZERO 0x28#define NRF_SZ 0x30 //通讯协议:设置#define NRF_SZ_SYS 0x30 #define NRF_REPEAT 0x80 //数据重发标志 从机根据控制字来跳转到对应的控制中去。 NRF的通讯过程: 在NRF主机的NRFTASK接收到开始发送命令的时候,进入发送模式。 首先设置NRF的状态是OK的(NRF.Ctrl = NRF_M_OK;),然后进行数据发送,如果发送失败,则加重发标志后再次发送,直到正确发送。 [AppleScript] 纯文本查看 复制代码 void NRF_M_CRTL(u8 *TX_Buf) //M 主站控制{ NRF.Ctrl = NRF_M_OK; //设置NRF的状态为 NRF_M_OK while(NRF.Send) //NRF本次发送的是否完成,NRF.Send = 0 发送已经完成,NRF.Send = 1 发送还在进行中 { if(NRF.Ctrl != NRF_M_OK) //NRF发送发生错误,没有成功发送 {//通讯错误 DK.ErrN ++;//错误计数加1 Tx_Buf[0] |= NRF_REPEAT; //设置重发标志 OS.DelayTimeTick(10); //延时10ms } NRF_M_Send(TX_Buf); //发送数据 }} [AppleScript] 纯文本查看 复制代码 void NRF_M_Send(u8 *TX_Buf){ NRF.Time_Out=0; //清除通讯超时 TX_Mode();//发送模式 OS.DelayTimeTick(1);//等待1ms if(NRF24L01_TxPacket(TX_Buf)==TX_OK) //发送成功 { RX_Mode(); //设置接收模式 while(NRF24L01_RxPacket(RX_BUF)!=0) //等待接收的数据 { OS.DelayTimeTick(2); //延时2ms NRF.Time_Out++; //timeout计数 if(NRF.Time_Out >=NRF_TIME_OUT) //timeout计数值大于设定值,说明从机没有发送数据,本次发送失败 { NRF.Ctrl = NRF_ERR_TIMEOUT; //超时等待次数超过了设置值 NRF.Time_Out++; // return; } } NRFTask_RX(RX_BUF,Tx_Buf); //数据解析处理 } else //发送失败 等待下次重发 { NRF.Ctrl = NRF_ERR_SEND; NRF.ERR_SEND++; } } 当接收到正确的数据后对数据进行解析,根据发送时的控制命令跳转到对应的解析函数中去 [AppleScript] 纯文本查看 复制代码 void NRFTask_RX(u8 *RX_Buf,u8 *TX_Buf) //控制代码选择{ switch (RX_Buf[0]) { case NRF_CX_STATE: case NRF_KZ_GCODE: case NRF_KZ_DW: case NRF_KZ_RESET: case NRF_KZ_MAINMOTO_ON: case NRF_KZ_MOTO_ON: case NRF_KZ_MOTO_OFF: case NRF_KZ_MAINMOTO_OFF: case NRF_KZ_GOTOZERO: Save_CX_Buf(RX_Buf); NRF.Ctrl=NRF_M_OK; NRF.Send = 0; break; case NRF_SZ_SYS: Save_CX_Buf(RX_Buf); NRF.Ctrl=NRF_M_OK; NRF.Send = 0; break; case NRF_CX_SYS: Save_SYS_Buf(RX_Buf); NRF.Ctrl=NRF_M_OK; NRF.Send = 0; break; case 0xFF: NRF.Ctrl = NRF_ERR_S; NRF.ERR_S++; break; default: NRF.Ctrl = NRF_ERR_FC; NRF.ERR_FC++; break; }} 这样NRF主机的一次通讯就完成了。 NRF从机的话我现在没有使用中断进行判断,而是通过每2ms查询一次NRF24L01中是否有数据来判断是否接受到了数据。 当NRF从机接收到数据后需要对接收的数据判断是否为重发的数据,如果是重发的数据并且和上次接收的数据相同,那么需要把本次接收的数据丢弃,并返回雕刻机的运行状态数据。 由于需要判断与上次接收的数据是否相同,应此接收的BUFF采用了双队列, [AppleScript] 纯文本查看 复制代码 typedef struct { u8 Buf[2][32]; u8 New; u8 Old;}BUF_Struct; #define RX_BUF_O Rx_Buf.Buf[Rx_Buf.Old]#define RX_BUF_N Rx_Buf.Buf[Rx_Buf.New]void Switch_Buf(void){ if(Rx_Buf.Old == 0) { Rx_Buf.Old = 1; Rx_Buf.New = 0; } else { Rx_Buf.Old = 0; Rx_Buf.New = 1; }} 通过下标的转换来切换当前使用那组BUFF |
|
|
|
厉害了我的哥
|
|
|
|
楼主,能否共享原理图供大家学习学习?
|
|
|
|
厉害了牛哇
|
|
|
|
|
|
|
|
原子哥你好,我在做一个写字机械手,功能类似于雕刻机,是根据你的源码改的,名字叫GRBL,基于stm32F4,是给数控机床用的。我用上位机GRBL controller与下位机通信时,打开端口会出现 No data from COM port after connect. Expecting Grbl version string,无法连接端口, 请问你们当时遇到过吗。
|
|
|
|
膜拜膜拜
|
|
|
|
grbl 与上位机有通讯协议的。开始的时候单片机需要发送GRBL的版本号给上位机建立通讯。其他的协议具体的我没有去深入分析。
|
|
|
|
我使用了串口,上位机是返回了版本号的,我搜索了,有人说这是maga2560连接时会出现,stm32的grbl是从2560移植过来的,应该是一样的什么原因,但是grbl controller的作者说这是软件bug,版本更新后也没解决,我也不懂了。
|
|
|
|
支持支持,动手能力很强
|
|
|
|
膜拜大神,问下大神,为什么我精英版下载了大神的程序,2.8的屏白屏无显示。
|
|
|
|
膜拜大神Ing。。。。。。
|
|
|
|
膜拜大神
|
|
|
|
厉害厉害
|
|
|
|
发烧友的开发板不支持
|
|
|
|
惊呆了,学习学习
|
|
|
|
只有小组成员才能发言,加入小组>>
如何使用STM32+nrf24l01架构把有线USB设备无线化?
2491 浏览 7 评论
请问能利用51单片机和nRF24L01模块实现实时语音无线传输吗?
2281 浏览 5 评论
3075 浏览 3 评论
2737 浏览 8 评论
为什么ucosii上移植lwip后系统进入了HardFault_Handler?
2699 浏览 4 评论
请教各位大咖:有没有接收频率32M左右的芯片推荐的?先感谢啦!
514浏览 1评论
772浏览 0评论
866浏览 0评论
548浏览 0评论
357浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-10-11 21:17 , Processed in 1.235228 second(s), Total 86, Slave 78 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号