Code to drive a CNC machine via a PC LPT port lookalike 25 pin 'D', experiment in 'PC/Mach3' replacement. Designed to compile and run on mbed LPC1768, Freescale KL25Z and Freescale KL46Z. Proved on LPC1768 and KL25Z, problem with serial port on KL46Z. Reads subset of 'G Codes' through usb/serial port and drives 3 stepper/servo drives for X, Y and Z, also similar Step/Dir outputs for spindle motor control. Emulates PC LPT, outputs 'charge pump', proved driving Seig KX3 CNC mill
command_interpreter.cpp
- Committer:
- JonFreeman
- Date:
- 2014-02-20
- Revision:
- 2:b3c668ec43ac
- Parent:
- 1:66ee619f206b
- Child:
- 3:7aaf0072cc22
File content as of revision 2:b3c668ec43ac:
#include "mbed.h" #include "rtos.h" #include "MODSERIAL.h" #include "cnc.h" using namespace std; extern MODSERIAL pc; //extern Serial pc; //extern bool liss_active; extern struct Gparams last_position; extern void move_to_XYZ (struct pirbufgrain & ins) ; //extern struct digital_readouts dro; // //extern void scmd (struct singleGparam * a) ; extern void flags_report_cmd (struct singleGparam * a) ; extern void report_inputs () ; double feed_rate = 1.0, // global scope, mm per minute. DEFAULTS to 1.0mm per min, very slow. spindle_rpm = 0.0; // global scope bool isdigit (int a) { if(a > ('0' - 1) && a < ('9' + 1)) return true; return false; } bool isupper (int a) { if ((a >= 'A') && (a <= 'Z')) return true; return false; } int tolower (int a) { if (isupper(a)) a += 'a' - 'A'; return a; } const int goodcodes[] = {0,'a','b','c','i','j','l','r','x','y','z'}; // possible G Code options const int const_numofcodes = sizeof(goodcodes) / sizeof(int); int find_char_in_goodcodes (int target) // Returns position of char in goodcodes[], 0 if not found. { for (int i = 1; i < const_numofcodes; i++) if (goodcodes[i] == target) return i; return 0; } /* void get_codepositions (struct singleGparam * a, struct Gparams & p) Only call from "void g0g1cmdcore (struct singleGparam * a, double f_rate)" Purpose: G code line may have any number of valid axes or parameters entered in any order or position. This function detects any X,Y,Z,A,I,J,R entries in 'p' if present and copies values into their respective positions within singleGparam 'a', setting the 'changed' flag for each to true if found, false if not found struct Gparams { // Where possibly messy G code line gets ordered and sorted into struct singleGparam x, y, z, i, j, r, a, b, c, d; // After sorting, know where to find any X, Y etc values ! } ; */ void get_codepositions (struct singleGparam * source_array, struct Gparams & dest) { //const int goodcodes[] = {0,'a','b','c','i','j','l','r','x','y','z'}; // possible G Code options //const int const_numofcodes = sizeof(goodcodes) / sizeof(int); // source_array is the array filled by function 'void command_line_interpreter ()'. // It contains any parameters read from the command line : // source_array[i].c may contain 'x' or 'y' etc to tie this entry to one of the 'goodcodes' - or not int codecnt[const_numofcodes +1]; int codepos[const_numofcodes +1]; int j; for (j = 0; j < const_numofcodes; j++) codecnt[j] = codepos[j] = 0; // Zero all results for (int i = 1; i <= source_array[0].i; i++) { // for number of parameters passed to us here for(j = 0; j < const_numofcodes; j++) { // for a, for b, ... for x, then y, then z if (source_array[i].c == goodcodes[j]) { codecnt[j]++; // Count of number of 'a's, 'b's ... 'x's, 'y's, 'z's. All should be 0 or 1 but could be more codepos[j] = i; // Identifies the a[?] containing last incidence of goodcodes[j] } } } dest.x.changed = dest.y.changed = dest.z.changed = dest.a.changed = false; dest.i.changed = dest.j.changed = dest.r.changed = false; dest.x.dbl = last_position.x.dbl; // copy previous coordinates in case not re-specified dest.y.dbl = last_position.y.dbl; dest.z.dbl = last_position.z.dbl; dest.a.dbl = last_position.a.dbl; dest.i.dbl = last_position.i.dbl; dest.j.dbl = last_position.j.dbl; dest.r.dbl = last_position.r.dbl; j = codepos[find_char_in_goodcodes('a')]; if (j) { dest.a.changed = true; dest.a.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('x')]; if (j) { dest.x.changed = true; dest.x.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('y')]; if (j) { dest.y.changed = true; dest.y.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('z')]; if (j) { dest.z.changed = true; dest.z.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('i')]; if (j) { dest.i.changed = true; dest.i.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('j')]; if (j) { dest.j.changed = true; dest.j.dbl = source_array[j].dbl; } j = codepos[find_char_in_goodcodes('r')]; if (j) { dest.r.changed = true; dest.r.dbl = source_array[j].dbl; } } void g2g3cmdcore (struct singleGparam * source_array, int twoorthree) { struct Gparams tmp; struct pirbufgrain start_point, end_point, centre_point, next_point; int state = 0, arc_steps; double rad_start, rad_end, start_angle, end_angle, next_angle, swept_angle, angle_step, arc_len, z_step; if (twoorthree != 2 && twoorthree != 3) { pc.printf("Err got %d when should be 2 or 3", twoorthree); return; } if (twoorthree == 2) pc.printf("g2 Clockwise Arc\r\n"); else pc.printf("g3 CounterClockwise Arc\r\n"); get_codepositions (source_array, tmp); // will overwrite with new where entered pc.printf("X %s\r\n", tmp.x.changed ? "T":"F"); pc.printf("Y %s\r\n", tmp.y.changed ? "T":"F"); pc.printf("Z %s\r\n", tmp.z.changed ? "T":"F"); pc.printf("R %s\r\n", tmp.r.changed ? "T":"F"); pc.printf("I %s\r\n", tmp.i.changed ? "T":"F"); pc.printf("J %s\r\n", tmp.j.changed ? "T":"F"); if (!tmp.x.changed || !tmp.y.changed) state |= 0x10000; // Error, X or Y missing if (tmp.r.changed && !tmp.i.changed && !tmp.j.changed) state |= 1; // Validated R mode got R not I not J if (!tmp.r.changed && tmp.i.changed && tmp.j.changed) state |= 2; // Validated IJ mode not R got I got J start_point.x = last_position.x.dbl; start_point.y = last_position.y.dbl; start_point.z = last_position.z.dbl; end_point.x = tmp.x.dbl; end_point.y = tmp.y.dbl; end_point.z = tmp.z.dbl; switch (state) { case 1: // Radius format arc pc.printf("Valid Radius format arc TO DO - not yet implemeted\r\n"); break; case 2: // Centre format arc ** OFFSETS ARE RELATIVE ** Abs coordinates not catered for pc.printf("Valid Centre format arc\r\n"); centre_point.x = start_point.x + tmp.i.dbl; centre_point.y = start_point.y + tmp.j.dbl; rad_start = hypot(start_point.x - centre_point.x, start_point.y - centre_point.y); rad_end = hypot(end_point.x - centre_point.x, end_point.y - centre_point.y); pc.printf("Start point X %.3f, Y %.3f\r\n", start_point.x, start_point.y); pc.printf("Centre point X %.3f, Y %.3f\r\n", centre_point.x, centre_point.y); pc.printf("End point X %.3f, Y %.3f\r\n", end_point.x, end_point.y); pc.printf("Rad start %.3f, Rad end %.3f\r\n", rad_start, rad_end); if (fabs(rad_start - rad_end) > 0.001) { // if ((rad_start - rad_end) > 0.001 || (rad_start - rad_end) < -0.001) { state |= 0x20000; pc.printf("Radii mismatch error in g2g3\r\n"); } start_angle = atan2(start_point.y - centre_point.y, start_point.x - centre_point.x); end_angle = atan2(end_point.y - centre_point.y, end_point.x - centre_point.x); swept_angle = end_angle - start_angle; //swept_angle = 0.0; //=IF((B$8=2);IF((H$24>-0.0001);(H$24-2*PI());(H$24)) // ;IF((H$24>0.0001);(H$24);(H$24+2*PI()))) if (twoorthree == 2) { if (swept_angle > -epsilon) swept_angle -= TWO_PI; } else { // twoorthree is 3 if (!(swept_angle > epsilon)) swept_angle += TWO_PI; } arc_len = fabs(rad_start * swept_angle); pc.printf("start_angle %.3f, end_angle %.3f, swept_angle %.3f, arc_len %.3f\r\n", start_angle, end_angle, swept_angle, arc_len); arc_steps = (int)(4.0 + fabs(1.7 * rad_end * swept_angle)); // fiddle factors adjusted empirically ! angle_step = swept_angle / arc_steps; next_angle = start_angle; z_step = (end_point.z - start_point.z) / arc_steps; next_point.z = start_point.z; pc.printf("Number of steps = %d, angle_step %.3f\r\n", arc_steps, angle_step); for (int i = 0; i < arc_steps; i++) { // cut 'arc_steps' straight lines next_angle += angle_step; next_point.x = centre_point.x + (rad_start * cos(next_angle)); next_point.y = centre_point.y + (rad_start * sin(next_angle)); next_point.z += z_step; pc.printf("X %.3f, Y %.3f\r\n", next_point.x, next_point.y); Thread::wait(300); next_point.f_rate = feed_rate; move_to_XYZ (next_point); } break; // end of case 2: // Centre format arc ** OFFSETS ARE RELATIVE ** Abs coordinates not catered for default: // Input error detected pc.printf("Input error detected in g2g3, code %x\r\n", state); break; } // end of switch(state) } void g0g1cmdcore (struct singleGparam * source_array, double f_rate) // Updates any / all of x, y, z NCOs { // Only get here when some G0 or G1 input has been read. G0 or G1 determined by f_rate struct pirbufgrain ins;//, outs; struct Gparams tmp; get_codepositions (source_array, tmp); // will overwrite with new where entered if (!tmp.x.changed && !tmp.y.changed && !tmp.z.changed) { pc.printf("No change in X, Y or Z in G0/G1. Ignoring\r\n"); return; } ins.x = tmp.x.dbl; ins.y = tmp.y.dbl; ins.z = tmp.z.dbl; ins.f_rate = f_rate; move_to_XYZ (ins); } void g0cmd (struct singleGparam * a) // Updates any / all of x, y, z NCOs { g0g1cmdcore (a, feed_rate_max); // Defined parameter in code } void g1cmd (struct singleGparam * a) // Updates any / all of x, y, z NCOs { g0g1cmdcore (a, feed_rate); // Settable feed_rate } void g2cmd (struct singleGparam * a) { // Clockwise arc g2g3cmdcore (a, 2); } void g3cmd (struct singleGparam * a) { // Counter clockwise arc g2g3cmdcore (a, 3); } void fcmd (struct singleGparam * a) { // Set Feed Rate command if (a[1].dbl < 0.0) { pc.printf("feed rate %.1f ? Setting to 0\r\n", a[1].dbl); a[1].dbl = 0.0; } if (a[1].dbl > feed_rate_max) { pc.printf ("Error, can't set feed rate to %.1f, max is %.1f, ", a[1].dbl, feed_rate_max); a[1].dbl = feed_rate_max; } pc.printf ("Setting feed_rate to %.1f\r\n", a[1].dbl); feed_rate = a[1].dbl; } extern void spindle_control (double ss) ; extern bool spindle_running () ; void M3cmd (struct singleGparam * a) { spindle_control (spindle_rpm); } void M5cmd (struct singleGparam * a) { spindle_control (0.0); } void scmd (struct singleGparam * a) { if (fabs(a[1].dbl) > spindle_max) { pc.printf ("Errror setting spindle RPM, can't set to %.0f, ignoring request\r\n", a[1].dbl); return; } pc.printf ("Setting spindle RPM to %.0f Can set Pos or Neg for fwd/rev\r\n", a[1].dbl); spindle_rpm = a[1].dbl; if (spindle_running()) spindle_control (spindle_rpm); /* pir_spin = (signed long) (a[1].dbl * spindle_factor); t = ticks; while (t == ticks) {} // wait until just after next interrupt p = mysteppers; if (pir_spin & 0x80000000) p |= SDi; else p &= ~SDi; mysteppers = p; */ } extern void target_cmd (struct singleGparam * a) ; void stopcmd (struct singleGparam * a) {pc.printf("Stop ! er, not working yet\r\n");} void m1cmd (struct singleGparam * a) {pc.printf("m1 Optional Programme Stop\r\n");} void m3cmd (struct singleGparam * a) {pc.printf("m3 Rotate Spindle Clockwise\r\n");} void m4cmd (struct singleGparam * a) {pc.printf("m4 Rotate Spindle Counter Clockwise\r\n");} void m5cmd (struct singleGparam * a) {pc.printf("m5 Stop Spindle\r\n");} /*void m30cmd (struct singleGparam * a) {pc.printf("m30 Programme End and Rewind\r\n");} void m47cmd (struct singleGparam * a) {pc.printf("m47 Repeat Prog from First Line\r\n");} void m48cmd (struct singleGparam * a) {pc.printf("m48 Enable Speed and Feed Override\r\n");} void m49cmd (struct singleGparam * a) {pc.printf("m49 Disable Speed and Feed Override\r\n");} void m98cmd (struct singleGparam * a) {pc.printf("m98 Call Subroutine\r\n");} void m99cmd (struct singleGparam * a) {pc.printf("m99 Return from Subroutine\r\n");} void g10cmd (struct singleGparam * a) {pc.printf("g10 Coord System Origin Set\r\n");} void g17cmd (struct singleGparam * a) {pc.printf("g17 XY Plane Select\r\n");} void g20cmd (struct singleGparam * a) {pc.printf("g20 Inch\r\n");} void g21cmd (struct singleGparam * a) {pc.printf("g21 mm\r\n");} void g40cmd (struct singleGparam * a) {pc.printf("g40 Cutter Compensation Off\r\n");} void g50cmd (struct singleGparam * a) {pc.printf("g50 Reset Scale Factors\r\n");} void g53cmd (struct singleGparam * a) {pc.printf("g53 Move in Absolute Coordinates\r\n");} void g90cmd (struct singleGparam * a) {pc.printf("g90 Absolute Distance Mode\r\n");} */ //void g4cmd (struct singleGparam * a) {pc.printf("g4 Dwell\r\n");} //void g91p1cmd (struct singleGparam * a) {pc.printf("g91.1 \r\n");} //void report_inputs () { void report_ins_cmd (struct singleGparam * a) { report_inputs(); } /*void tasktstone (void const * name) { static int i = 0; pc.printf("Arrived at tasktstone\r\n"); while (true) { pc.printf("%s %d\r\n", name, i++); Thread::wait(9500); osThreadYield(); } }*/ /*void tasktestcmd (struct singleGparam * a) { pc.printf("At tasktestcmd\r\n"); Thread tasktest (tasktstone, (void *) "Bollocks"); pc.printf("Leaving tasktestcmd\r\n"); } */ extern void lissajous (double) ; void lisscmd (struct singleGparam * a) { lissajous (feed_rate); /* if(liss_active) { pc.printf("Can not add Lissajous, already running.\r\n"); } else { pc.printf("Adding Lissajous task\r\n"); liss_active = true; }*/ } /* void drooncmd (struct singleGparam * a) { dro.dro_output = true; // Enable continuous dro display update } void drooffcmd (struct singleGparam * a) { dro.dro_output = false; // Disable continuous dro display update } */ /*void g90p1cmd (struct singleGparam * a) { pc.printf ("Arrived at function fredcmd with %d parameters\r\n", a[0].i); for (int i = 1; i <= a[0].i; i++) { pc.printf ("*%c* ", a[i].c); pc.printf ("%d, ", a[i].i); pc.printf ("%f\r\n", a[i].dbl); } pc.printf (" endof param list\r\n"); } */ void menucmd (struct singleGparam * a); struct kb_command { const char * cmd_word; // points to text e.g. "menu" const char * explan; void (*f)(struct singleGparam *); // points to function } ; struct kb_command const * command_list_ptr = NULL; // Pointer switched between 'input_syntax_check' and 'command_execute' struct kb_command const input_syntax_check [] = { {"menu", "Lists available commands, same as ls", menucmd}, {"ls", "Lists available commands, same as menu", menucmd} } ; struct kb_command const command_execute[] = { {"menu", "Lists available commands, same as ls", menucmd}, {"ls", "Lists available commands, same as menu", menucmd}, {"stop", "To Stop the Machine !", stopcmd}, {"f ", "To set Feed Rate mm/min, e.g. f 25", fcmd}, {"s ", "To set Spindle RPM, e.g. S 1250", scmd}, {"m3", "Start Spindle at last 'S'", M3cmd}, {"m5", "Stop Spindle", M5cmd}, {"g0", "Rapid move", g0cmd}, /*{"m30", "Not Implemented", m30cmd}, {"m47", "Not Implemented", m47cmd}, {"m48", "Not Implemented", m48cmd}, {"m49", "Not Implemented", m49cmd}, {"m98", "Not Implemented", m98cmd}, {"m99", "Not Implemented", m99cmd}, {"m1", "Not Implemented", m1cmd}, {"m3", "Not Implemented", m3cmd}, {"m4", "Not Implemented", m4cmd}, {"m5", "Not Implemented", m5cmd}, {"g10", "Not Implemented", g10cmd}, {"g17", "Not Implemented", g17cmd}, {"g20", "Not Implemented", g20cmd}, {"g21", "Not Implemented", g21cmd}, {"g40", "Not Implemented", g40cmd}, {"g50", "Not Implemented", g50cmd}, {"g90.1", "Not Implemented", g90p1cmd}, {"g91.1", "Not Implemented", g91p1cmd}, {"g90", "Not Implemented", g90cmd}, */ {"g1", "Linear Interpolation - move straight at current feed rate", g1cmd}, {"g2", "Helical Interpolation CW (Arc, circle)", g2cmd}, {"g3", "Helical Interpolation CCW (Arc, circle)", g3cmd}, {"liss", "Run Lissajous pattern generator", lisscmd}, {"flags", "Report System Flags", flags_report_cmd}, {"inputs", "Report State of Input bits", report_ins_cmd}, {"target", "Identify computer device", target_cmd}, // {"ttest", "Add a task to prove we can, or not", tasktestcmd}, // {"dro on", "Turn dro readout on", drooncmd}, // {"dro off", "Turn dro readout off", drooffcmd} }; //const int numof_menu_items = sizeof(kbc2) / sizeof(kb_command); //const int numof_menu_items_sc = sizeof(input_syntax_check) / sizeof(kb_command); //const int numof_menu_items_ce = sizeof(command_execute) / sizeof(kb_command); int numof_menu_items; void menucmd (struct singleGparam * a) { pc.printf("At menucmd function - listing commands:-\r\n"); for(int i = 0; i < numof_menu_items; i++) pc.printf("[%s]\t\t%s\r\n", command_list_ptr[i].cmd_word, command_list_ptr[i].explan); pc.printf("End of List of Commands\r\n"); } bool isalpha (int c) { if ((c >= 'a') && (c <= 'z')) return true; if ((c >= 'A') && (c <= 'Z')) return true; return false; } void nudger (int code) { // Using <Ctrl>+ 'F', 'B' for Y, 'L', 'R' for X, 'U', 'D' for Z // 6 2 12 18 21 4 // struct Gparams dest; struct pirbufgrain dest; dest.x = last_position.x.dbl; dest.y = last_position.y.dbl; dest.z = last_position.z.dbl; dest.f_rate = feed_rate; switch (code) { case 6: // 'F' move -Y dest.y -= 0.1; break; case 2: // 'B' move +Y dest.y += 0.1; break; case 12: // 'L' move +X dest.x += 0.1; break; case 18: // 'R' move -X dest.x -= 0.1; break; case 21: // 'U' move +Z dest.z += 0.1; break; case 4: // 'D' move -Z dest.z -= 0.1; default: break; // pc.printf("Nuffink to do in nudger\r\n"); } // end of switch move_to_XYZ (dest); } ////class CLI { /* void command_line_interpreter () Purpose: */ void command_line_interpreter (void const * name) { const int MAX_PARAMS = 10, MAX_CMD_LEN = 120; static char cmd_line[MAX_CMD_LEN + 4]; static struct singleGparam params[MAX_PARAMS + 1]; static int cl_index = 0, lastalpha = 0; // pc.printf("Got to cli, Starting cli\r\n"); if (true) { command_list_ptr = command_execute; numof_menu_items = sizeof(command_execute) / sizeof(kb_command); } else { command_list_ptr = input_syntax_check; numof_menu_items = sizeof(input_syntax_check) / sizeof(kb_command); } while (true) { while (pc.readable()) { int ch; if (cl_index > MAX_CMD_LEN) { // trap out stupidly long command lines pc.printf ("Keyboard Error!! Killing stupidly long command line"); cl_index = 0; } ch = tolower(pc.getc()); if (ch == '\r' || ch >= ' ' && ch <= 'z') pc.printf("%c", ch); else { // Using <Ctrl>+ 'F', 'B' for Y, 'L', 'R' for X, 'U', 'D' for Z cl_index = 0; // 6 2 12 18 21 4 pc.printf("[%d]", ch); nudger (ch); } if(ch != '\r') // was this the 'Enter' key? cmd_line[cl_index++] = ch; // added char to command being assembled else { // key was CR, may or may not be command to lookup cmd_line[cl_index] = 0; // null terminate command string if(cl_index) { // If have got some chars to lookup int i, wrdlen; for (i = 0; i < numof_menu_items; i++) { // Look for input match in command list wrdlen = strlen(command_list_ptr[i].cmd_word); if(strncmp(command_list_ptr[i].cmd_word, cmd_line, wrdlen) == 0) { // If match found bool negflag = false; int state = 0, paramindex; double fracmul; // pc.printf("Found match for word [%s]\r\n", kbc[i].wrd); for(paramindex = 0; paramindex < MAX_PARAMS; paramindex++) { // Clear out whole set of old parameters ready for anything new on this line params[paramindex].i = 0; // for integer parameters params[paramindex].c = 0; // for last alpha char, helps tie 'X' to '-23.5' etc params[paramindex].dbl = 0.0; // for floating point parameters params[paramindex].ul = 0; params[paramindex].changed = false; } paramindex = 0; // read any parameters from command line here // Using parameters[0] as count of parameters to follow while (wrdlen <= cl_index) { ch = cmd_line[wrdlen++]; if(isalpha(ch)) lastalpha = ch; if(ch == '-') negflag = true; if(ch == '+') negflag = false; switch (state) { case 0: // looking for start of a number string if(isdigit(ch)) { // found first digit of a number string paramindex++; if(paramindex > MAX_PARAMS) { wrdlen = cl_index; // exit condition pc.printf("WARNING - too many parameters, ignoring extra\r\n"); } else { params[paramindex].i = ch - '0'; params[paramindex].c = lastalpha; state = 1; // Found first digit char of number string } } break; case 1: // looking for end of a number string if(isdigit(ch)) { // accumulating integer from string params[paramindex].i *= 10; params[paramindex].i += ch - '0'; } else { // found non-digit terminating number if (ch == '.') { state = 2; fracmul = 0.1; params[paramindex].dbl = (double)params[paramindex].i; } else { params[0].i++; // count of validated parameters state = 0; // Have read past last digit of number string if(negflag) { params[paramindex].i = -params[paramindex].i; negflag = false; } params[paramindex].dbl = (double)params[paramindex].i; } } break; case 2: // looking for fractional part of double if(isdigit(ch)) { // accumulating fractional part from string params[paramindex].dbl += (double)((ch - '0') * fracmul); fracmul /= 10.0; } else { // found non-digit terminating double precision number params[0].i++; // count of validated parameters state = 0; // Have read past last digit of number string if(negflag) { params[paramindex].i = -params[paramindex].i; params[paramindex].dbl = -params[paramindex].dbl; negflag = false; } } break; default: break; } // end of switch state } // end of while wrdlen < cl_index // pc.printf("Found match to [%s] with %d parameters\r\n", command_list_ptr[i].wrd, paramindex); command_list_ptr[i].f(params); // execute command i = numof_menu_items + 1; // to exit for loop } } // End of for numof_menu_items if(i == numof_menu_items) pc.printf("No Match Found for CMD [%s]\r\n", cmd_line); } // End of If have got some chars to lookup pc.printf("\r\n>"); cl_index = lastalpha = 0; } // End of else key was CR, may or may not be command to lookup } // End of while (pc.readable()) // pc.printf("cli yielding\r\n"); osThreadYield(); // Using RTOS on this project } } ////} cli;