Shop OBEX P1 Docs P2 Docs Learn Events
WIP: The C code for 3D printer firmware: — Parallax Forums

WIP: The C code for 3D printer firmware:

davidsaundersdavidsaunders Posts: 1,559
edited 2015-04-19 22:24 in Propeller 1
I am getting a good start on a rewrite in C, and it is coming along at a decent pace.


Here is the core of the G-Code parser:
char newcode;
char codebuf[256];


struct {     /* structure of G-Code command parameters.*/
  long gc;
  long x;
  long y;
  long z;
  long e;
  long f;
  long p;
  long s;
  long dmul;
  long status;
} GCodePar;


/**************************************************************************
* int GetGParams(long offset)                                             *
*                                                                         *
* Parses the G-Code parameter string and fills in the GCodePar struct for *
* this command.                                                           *
*                                                                         *
* PARAMETERS:                                                             *
* long offset : The offset in the codebuf buffer where the parameters     *
*               begin                                                     *
*                                                                         *
* RETURNS:                                                                *
*   The offset in the G-Code buffer after the end of the parameters.      *
*                                                                         *
* TODO:                                                                   *
*   DONE: Add the ability to skip past comments at the end of line.       *
**************************************************************************/
int GetGParams(long offset){
  static char tstr[8] = {0,0,0,0,0,0,0,0};
  int  tmp;
  
  if (codebuf[offset] >= 0x30 && codebuf[offset] <= 0x39){
    offset++;
    for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
    GCodePar.gc = str2inum(tstr);
  }    
    
  
  while (codebuf[offset] < 0x21) offset++; /*Skip spaces.*/
  while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]){
    switch (codebuf[offset++] | 0x20){
      case 'x':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.x = str2inum(tstr);
        break;
      case 'y':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.y = str2inum(tstr);
        break;
      case 'z':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.z = str2inum(tstr);
        break;
      case 'e':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.e = str2inum(tstr);
        break;
      case 'f':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.f = str2inum(tstr);
        break;
      case 'p':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.p = str2inum(tstr);
        break;
      case 's':
        for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp);
        GCodePar.s = str2inum(tstr);
        break;
      case ';':
        while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
    }      
  }  
  
  return offset;
}


/**************************************************************************
**************************************************************************/
int ExecMCode(){
  switch (GCodePar.gc) {
    case 0:           //All stop.
      M0(); break;
    case 2:           //
      M2(); break;
    case 82:          //Only absolute supported for extruder.
      break;
    case 104:         //Set extruder tempurature.
      M104(); break;
    case 106:         //fan on.
      M106(); break;
    case 107:         //Fan Off.
      M107(); break;
    case 109:         //Set extruder temp (we always wait).
      M104(); break;
    case 116:         //wait for all to be ready.
      break;          //We always wait for ready, so do nothing.
  }
      
  return 0;
}


/**************************************************************************
* int ExecGcode(void)
**************************************************************************/
int ExecGCode(){
  switch (GCodePar.gc){
    case 0:
    case 1:
      G0(); break;
    case 4:
      G4(); break;
    case 21:
      GCodePar.dmul = inch; break;
    case 22:
      GCodePar.dmul = mm; break;
    case 28:
      G28(); break;
    case 90:            //G90 Only absolute positioning supported.
      break;
  }    
  return 0;
}


/**************************************************************************
* int GCode(void)                                                         *
*                                                                         *
* This function is the outer level of the simple G-Code parser.           *
*                                                                         *
* PARAMETERS:                                                             *
* The contents of the character buffer, codebuf.                          *
*                                                                         *
* TODO:                                                                   *
*   DONE: Add the ability to skip past comments at the end of line.       *
**************************************************************************/
int GCode(void){
  char tc;
  short offset;
  offset = 0;

  while (codebuf[offset]){  /*Continue until next char is NULL.*/
    while (codebuf[offset] < 0x21) offset++; /*skip leading spaces.*/
    if ((codebuf[offset++] == 0xD) || (codebuf[offset++] == 0x0A))   /*checkj for end of line*/
      while (GCodePar.status);      /*Wait for command to finish running.*/

    switch (codebuf[offset++] | 0x20) {                 /*Determine if we are using a G command or M command.*/
      case 'g':
        offset = GetGParams(offset); ExecGCode(); break;
      case 'm':
        offset = GetGParams(offset); ExecMCode(); break;
      case ';':
        while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
    }    
  }
  return 0;
}
And I am only one day into the C version, so I would say that PASM and C make it easy. I think that the same will be true of PropBASIC once I get to know it better, though one step at a time.

It does not include the small single command execution functions, as I am still working on them, total size so far (including everything so far done), is just over 5KB compiled.

The code is yet to be tested, though I feel it will work. Please point out any bugs, errors, or other you see in this code.

Comments

  • JasonDorieJasonDorie Posts: 1,930
    edited 2015-04-17 16:20
    Why not make str2inum just terminate when it hits a non-numeric character, and change all the cases to just call it directly from the source buffer instead of doing the copy first? All those duplicate look like a lot of unnecessary code.

    J
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 17:42
    JasonDorie wrote: »
    Why not make str2inum just terminate when it hits a non-numeric character, and change all the cases to just call it directly from the source buffer instead of doing the copy first? All those duplicate look like a lot of unnecessary code.

    J
    That does make sense, I guess I was not thinking about that, oops. I am aware of a few other areas duplicate code that I do want to improve on once it is working, though first thing is get it working.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-17 17:43
    What do you think of this for GetGParams instead? note the variable creatively named "renameMe" lol. I don't know what each of those struct fields refers to so i can't come up with a good name for the pointer. in any case, I think this could should be smaller. I can't compile it without stubs for all the missing functions though.
    /**************************************************************************
    * int GetGParams(long offset)                                             *
    *                                                                         *
    * Parses the G-Code parameter string and fills in the GCodePar struct for *
    * this command.                                                           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * long offset : The offset in the codebuf buffer where the parameters     *
    *               begin                                                     *
    *                                                                         *
    * RETURNS:                                                                *
    *   The offset in the G-Code buffer after the end of the parameters.      *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GetGParams (long offset) {
        static char tstr[8] = {0, 0, 0, 0, 0, 0, 0, 0};
        int         tmp;
    
        if (codebuf[offset] >= 0x30 && codebuf[offset] <= 0x39) {
            offset++;
            for (tmp = 0; (tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A; ++tmp);
            GCodePar.gc = str2inum(tstr);
        }
    
    
        while (codebuf[offset] < 0x21) offset++; /*Skip spaces.*/
    
        while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]) {
            int switchVar = codebuf[offset++] | 0x20;
            if (';' == switchVar)
                while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
            else {
                long *renameMe;
                bool validChar = true;
                switch (switchVar) {
                    case 'x':
                        renameMe = &GCodePar.x;
                        break;
                    case 'y':
                        renameMe = &GCodePar.y;
                        break;
                    case 'z':
                        renameMe = &GCodePar.z;
                        break;
                    case 'e':
                        renameMe = &GCodePar.e;
                        break;
                    case 'f':
                        renameMe = &GCodePar.f;
                        break;
                    case 'p':
                        renameMe = &GCodePar.p;
                        break;
                    case 's':
                        renameMe = &GCodePar.s;
                        break;
                    default:
                        validChar = false;
                }
    
                if (validChar) {
                    for (tmp = 0; (tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A; ++tmp);
                    *renameMe = str2inum(tstr);
                }
            }
        }
    
        return offset;
    }
    

    I also used a bool in there because I typically run C++, not C. If you're running C, you can of course change it to whatever variable type you please.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 18:37
    Ok here is a bit of an improvement:
    long GCstr2inum(long o, long *r){
      long rv, tmp, cnt, mp, bmp;
      char *s, tc;
      s = &codebuf[o];
      
      for (tmp = 0, cnt = 0; (tc = s[tmp]) >= 0x30 && tc <= 0x39; tmp++, cnt++);
      for (mp = 1; cnt; cnt--, mp * 10) rv = (s[cnt] - 0x30) * mp;
      rv <<= 8;
      if (s[tmp] == '.')       // Check for fractional part.
        for (tmp++, mp = 10; s[tmp] <= 0x30 && s[tmp] <= 0x39 && mp <= 100; tmp++, mp * 10)
          rv = (256/mp) * (s[tmp] - 0x30) + rv;
    
      *r = tmp + o;
      return rv;  
    }  
     
    /**************************************************************************
    * int GetGParams(long offset)                                             *
    *                                                                         *
    * Parses the G-Code parameter string and fills in the GCodePar struct for *
    * this command.                                                           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * long offset : The offset in the codebuf buffer where the parameters     *
    *               begin                                                     *
    *                                                                         *
    * RETURNS:                                                                *
    *   The offset in the G-Code buffer after the end of the parameters.      *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GetGParams(long offset){
      int  tmp;
      
      if (codebuf[offset] >= 0x30 && codebuf[offset] <= 0x39){
        offset++;
        GCodePar.gc = GCstr2inum(offset, &offset);
      }    
        
      
      while (codebuf[offset] < 0x21) offset++; /*Skip spaces.*/
      while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]){
        switch (codebuf[offset++] | 0x20){
          case 'x':
            GCodePar.x = GCstr2inum(offset, &offset); break;
          case 'y':
            GCodePar.y = GCstr2inum(offset, &offset); break;
          case 'z':
            GCodePar.z = GCstr2inum(offset, &offset); break;
          case 'e':
            GCodePar.e = GCstr2inum(offset, &offset); break;
          case 'f':
            GCodePar.f = GCstr2inum(offset, &offset); break;
          case 'p':
            GCodePar.p = GCstr2inum(offset, &offset); break;
          case 's':
            GCodePar.s = GCstr2inum(offset, &offset); break;
          case ';':
            while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
        }      
      }  
      
      return offset;
    }
    
    
    /**************************************************************************
    * int ExecMCode(void)
    **************************************************************************/
    int ExecMCode(){
      switch (GCodePar.gc) {
        case 0:           //All stop.
          StopSteppers();StopExtruder();
          break;
        case 2:           //End G-Code prog.
         StpTo(0,0,0);
         StopSteppers(); StopExtruder();
         break;
        case 82:          //Only absolute supported for extruder.
          break;
        case 104:         //Set extruder tempurature.
           ExTemp(GCodePar.s); break;
        case 106:         //fan on.
          /*FanSet(GCodePar.s)*/; break;
        case 107:         //Fan Off.
          /*FanSet(0)*/; break;
        case 109:         //Set extruder temp (we always wait).
           ExTemp(GCodePar.s); break;
        case 116:         //wait for all to be ready.
          break;          //We always wait for ready, so do nothing.
      }
          
      return 0;
    }
    
    
    /**************************************************************************
    * int ExecGcode(void)                                                     *
    *                                                                         *
    * Perform an Gxxx command.                                                *
    **************************************************************************/
    int ExecGCode(void){
      switch (GCodePar.gc){
        case 0:
        case 1:
          G0(); break;
        case 4:
          WaitMS(GCodePar.p); break;
        case 21:
          GCodePar.dmul = inch; break;
        case 22:
          GCodePar.dmul = mm; break;
        case 90:            //G90 Only absolute positioning supported.
          break;
      }    
      return 0;
    }
    
    
    /**************************************************************************
    * int GCode(void)                                                         *
    *                                                                         *
    * This function is the outer level of the simple G-Code parser.           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * The contents of the character buffer, codebuf.                          *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GCode(void){
      char tc;
      short offset;
      offset = 0;
    
      while (codebuf[offset]){  /*Continue until next char is NULL.*/
        while (codebuf[offset] < 0x21) offset++; /*skip leading spaces.*/
        if ((codebuf[offset++] == 0xD) || (codebuf[offset++] == 0x0A))   /*checkj for end of line*/
          while (GCodePar.status);      /*Wait for command to finish running.*/
    
        switch (codebuf[offset++] | 0x20) {                 /*Determine if we are using a G command or M command.*/
          case 'g':
            offset = GetGParams(offset); ExecGCode(); break;
          case 'm':
            offset = GetGParams(offset); ExecMCode(); break;
          case ';':
            while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D && codebuf[offset] != 0) ++offset;
        }    
      }
      return 0;
    }
    
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 18:41
    What do you think of this for GetGParams instead? note the variable creatively named "renameMe" lol. I don't know what each of those struct fields refers to so i can't come up with a good name for the pointer. in any case, I think this could should be smaller. I can't compile it without stubs for all the missing functions though.
    /**************************************************************************
    * int GetGParams(long offset)                                             *
    *                                                                         *
    * Parses the G-Code parameter string and fills in the GCodePar struct for *
    * this command.                                                           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * long offset : The offset in the codebuf buffer where the parameters     *
    *               begin                                                     *
    *                                                                         *
    * RETURNS:                                                                *
    *   The offset in the G-Code buffer after the end of the parameters.      *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GetGParams (long offset) {
        static char tstr[8] = {0, 0, 0, 0, 0, 0, 0, 0};
        int         tmp;
    
        if (codebuf[offset] >= 0x30 && codebuf[offset] <= 0x39) {
            offset++;
            for (tmp = 0; (tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A; ++tmp);
            GCodePar.gc = str2inum(tstr);
        }
    
    
        while (codebuf[offset] < 0x21) offset++; /*Skip spaces.*/
    
        while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]) {
            int switchVar = codebuf[offset++] | 0x20;
            if (';' == switchVar)
                while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
            else {
                long *renameMe;
                bool validChar = true;
                switch (switchVar) {
                    case 'x':
                        renameMe = &GCodePar.x;
                        break;
                    case 'y':
                        renameMe = &GCodePar.y;
                        break;
                    case 'z':
                        renameMe = &GCodePar.z;
                        break;
                    case 'e':
                        renameMe = &GCodePar.e;
                        break;
                    case 'f':
                        renameMe = &GCodePar.f;
                        break;
                    case 'p':
                        renameMe = &GCodePar.p;
                        break;
                    case 's':
                        renameMe = &GCodePar.s;
                        break;
                    default:
                        validChar = false;
                }
    
                if (validChar) {
                    for (tmp = 0; (tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A; ++tmp);
                    *renameMe = str2inum(tstr);
                }
            }
        }
    
        return offset;
    }
    

    I also used a bool in there because I typically run C++, not C. If you're running C, you can of course change it to whatever variable type you please.
    I do see what you are doing, using a variable to store the state of valid vs non valid char, though I think that would make it bigger.

    And for true/false boolean type data it is better to use a standard integer datatype (where non-zero is true, and zero is false), as that works with all C compilers, and the few early C++ compilers that do not support the bool type.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 18:45
    @SwimDude0614:
    Also note that the members of the structure refer to the parameters used in G-Code, here is a reference:
    http://reprap.org/wiki/G-code#Replies_from_the_RepRap_machine_to_the_host_computer

    You will see that the parameters each begin with a letter, these letters are the same as the names of the members of the structure.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-17 18:47
    I do see what you are doing, using a variable to store the state of valid vs non valid char, though I think that would make it bigger.

    I can't say for sure, but I'd bet with it's better than writing the for-loop 7 times.
    And for true/false boolean type data it is better to use a standard integer datatype (where non-zero is true, and zero is false), as that works with all C compilers, and the few early C++ compilers that do not support the bool type.

    If you're using C, absolutely. If you're using C++... what system are you running that has such an old C++ compiler? PropGCC may not be bleeding edge new, but it sure isn't that old :P
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 18:53
    If you're using C, absolutely. If you're using C++... what system are you running that has such an old C++ compiler? PropGCC may not be bleeding edge new, but it sure isn't that old :P
    LOL, yea there is not a compiler that old for the Prop (the prop is 7 times younger than a C++ compiler with that limitation).
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-17 19:21
    I have decided to use LMM code for most everything for the initial version, I will pair it down to figure out what to put in COGC later, just get it working for now.

    A bit of an update:

    Here it the main portion of the stepper control thread (the actual pin output is not shown here).
    static void bckx(long d);
    static void fwdx(long d);
    static void bcky(long d);
    static void fwdy(long d);
    static void bckz(long d);
    static void fwdz(long d);
    static void LineTo(long x, long y, long z);
    
    static long cx, cy, cz;
    static long mx, my, mz, dn;
    
    static void CogLp(void){
      while(1){
        if (!dn) LineTo(mx,my,mz);
      }
    }      
    
    static void LineTo(long x, long y, long z){
      long tmp0;
      int dx, sx;
      int dy, sy; 
      int err, e2;
     
      err = (dx>dy ? dx : -dy)/2;
      
      dx = (tmp0 = x - cx) >= 0 ? tmp0 : -tmp0; //abs(x-cx);
      sx = cx < x ? 1 : -1;
     
      dy = (tmp0 = y - cy) >= 0 ? tmp0 : -tmp0; //abs(y-cy);
      sy = cy < y ? 1 : -1;
     
      for(;;){
        if (cx==x && cy==y) break;
        e2 = err;
        if (e2 >-dx) {
           err -= dy; cx += sx; 
           if (sx)  sx > 0 ? fwdx(1) : bckx(1);
        }
        if (e2 < dy) {
          err += dx; cy += sy;
          if (sy) sy > 0 ? fwdy(1) : bcky(1); 
        }
      }
      if (z > 0) fwdz(z); else if (z < 0) bckz(-z);
      dn = 1;
    }  
    
    
    void StpTo(long x, long y, long z){
      mx = x; my = y; mz = z;
      dn = 0;
      while (!dn);  //Wait till complete before returning.
      
      return;
    }  
    
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-18 04:04
    Just a bit of a status update, there are still a few things left to do. I still need to get the extruder code finished (hence its absence here), I still need co comment everything (the part I am terrible about, always have been), and I need to go through and pair down any redundant code.

    I also need to add NTSC output for a complete status display.

    steppers.h:
    void StpInit(void);
    void StopSteppers(void);
    void StpTo(long x, long y, long z);
    
    #define stps 0x36C9   //The four steps, as 4 bits each, in order.
    

    Steppers.c:
    #include "steppers.h"
    #include "simpletools.h"
    
    static void bckx(long d);
    static void fwdx(long d);
    static void bcky(long d);
    static void fwdy(long d);
    static void bckz(long d);
    static void fwdz(long d);
    static void LineTo(long x, long y, long z);
    
    static long cx, cy, cz;
    static volatile long mx, my, mz, dn;
    static volatile short CurXStep, CurYStep, CurZStep;
    
    static void CogLp(void){
      DIRA = 0x00000FFF;
      while(1){
        if (!dn) LineTo(mx,my,mz);
      }
    }
    
    void StpInit(void){
      CurXStep = 0;
      cog_run(CogLp,128);
      return;
    }     
    
    static void LineTo(long x, long y, long z){
      long tmp0;
      int dx, sx;
      int dy, sy; 
      int err, e2;
     
      err = (dx>dy ? dx : -dy)/2;
      
      dx = (tmp0 = x - cx) >= 0 ? tmp0 : -tmp0; //abs(x-cx);
      sx = cx < x ? 1 : -1;
     
      dy = (tmp0 = y - cy) >= 0 ? tmp0 : -tmp0; //abs(y-cy);
      sy = cy < y ? 1 : -1;
     
      while (1){
        if (cx==x && cy==y) break;
        e2 = err;
        if (e2 >-dx) {
           err -= dy; cx += sx; 
           if (sx)  sx > 0 ? fwdx(1) : bckx(1);
        }
        if (e2 < dy) {
          err += dx; cy += sy;
          if (sy) (sy > 0) ? fwdy(1) : bcky(1); 
        }
      }
      if (z > 0) fwdz(z); else if (z < 0) bckz(-z);
      dn = 1;
      return;
    }  
    
    
    void StpTo(long x, long y, long z){
      mx = x; my = y; mz = z;
      dn = 0;
      while (!dn);  //Wait till complete before returning.
      
      return;
    }
    
    
    void StopSteppers(void){
      dn = 1;
      return;
    }  
    
    
    static void bckx(long d){
      long t;
      
      if (CurXStep <= 0) CurXStep = 3; else CurXStep--;
      t = (stps >> (CurXStep << 2));
      OUTA = (t | 0xFFFFFFF0) & (OUTA | t);
      return;
    }
    
    
    static void fwdx(long d){
      long t;
      
      if (CurXStep >= 3) CurXStep = 0; else CurXStep++;
      t = (stps >> (CurXStep << 2)) & 0x0F;
      OUTA = (t | 0xFFFFFFF0) & (OUTA | t);
      return;
    }
    
    
    static void bcky(long d){
      long t;
      
      if (CurYStep <= 0) CurYStep = 3; else CurYStep--;
      t = (stps >> (CurYStep << 2)) << 4;
      OUTA = (t | 0xFFFFFF0F) & (OUTA | t);
      return;
    }
    
    
    static void fwdy(long d){
      long t;
      
      if (CurYStep >= 3) CurYStep = 0; else CurYStep++;
      t = ((stps >> (CurYStep << 2)) & 0x0F) << 4;
      OUTA = (t | 0xFFFFFF0F) & (OUTA | t);
      return;
    
    }
    
    
    static void bckz(long d){
      long t;
      
      if (CurZStep <= 0) CurZStep = 3; else CurZStep--;
      t = (stps >> (CurZStep << 8)) << 8;
      OUTA = (t | 0xFFFFF0FF) & (OUTA | t);
      return;
    }
    
    
    static void fwdz(long d){
      long t;
      
      if (CurZStep >= 3) CurZStep = 0; else CurZStep++;
      t = ((stps >> (CurZStep << 2)) & 0x0F) << 8;
      OUTA = (t | 0xFFFFF0FF) & (OUTA | t);
      return;
    }
    

    Print3D.c:
    /*
      Prinnt3D.C
      Main program for 3D printer firmware.   This file just sets
      everything up for everything, and contains the G-Code processor.
    */
    #include <propeller.h>
    #include "steppers.h"
    #include "extrude.h"
    
    #define inch 220
    #define mm 100
    
    char newcode;
    char codebuf[256];
    
    int setup();
    int GCode();
    
    struct {     /* structure of G-Code command parameters.*/
      long gc;
      long x;
      long y;
      long z;
      long e;
      long f;
      long p;
      long s;
      long dmul;
      long status;
    } GCodePar;
    
    
    /**************************************************************************
    **************************************************************************/
    int main()
    {
      setup();
    
      while(1)
      {
        if (newcode)
        {
          GCode();
        }    
      }  
    }
    
    /**************************************************************************
    *  setup()                                                                *
    *                                                                         *
    *  Initializes some values shared between cogs, and starts the cogs used  *
    *  to control everything.                                                 *
    **************************************************************************/
    int setup() /* Setup everything and get the cogs started. */
    {
      StpInit();
      return 0;
    }
    
    
    long WaitMS(long w){
      long tmp,tmpend;
      
      tmp = CNT;
      tmpend = (CLKFREQ / 1000) * w + tmp;
      while (CNT < tmpend);
      return 0;
    }  
    
    
    long GCstr2inum(long o, long *r){
      long rv, tmp, cnt, mp, bmp;
      char *s, tc;
      s = &codebuf[o];
      
      for (tmp = 0, cnt = 0; (tc = s[tmp]) >= '0' && tc <= '9'; tmp++, cnt++);
      for (mp = 1; cnt; cnt--, mp * 10) rv = (s[cnt] - 0x30) * mp;
      rv <<= 8;
      if (s[tmp] == '.')       // Check for fractional part.
        for (tmp++, mp = 10; s[tmp] <= '0' && s[tmp] <= '9' && mp <= 100; tmp++, mp * 10)
          rv = (256/mp) * (s[tmp] - '0') + rv;
    
      *r = tmp + o;
      return rv;  
    }  
     
    /**************************************************************************
    * int GetGParams(long offset)                                             *
    *                                                                         *
    * Parses the G-Code parameter string and fills in the GCodePar struct for *
    * this command.                                                           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * long offset : The offset in the codebuf buffer where the parameters     *
    *               begin                                                     *
    *                                                                         *
    * RETURNS:                                                                *
    *   The offset in the G-Code buffer after the end of the parameters.      *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GetGParams(long offset){
      int  tmp;
      
      if (codebuf[offset] >= '0' && codebuf[offset] <= '9'){
        offset;
        GCodePar.gc = GCstr2inum(offset, &offset);
      }    
        
      
      while (codebuf[offset] < 0x20) offset++; /*Skip spaces.*/
      while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]){
        switch (codebuf[offset++] | 0x20){
          case 'x':
            GCodePar.x = GCstr2inum(offset, &offset); break;
          case 'y':
            GCodePar.y = GCstr2inum(offset, &offset); break;
          case 'z':
            GCodePar.z = GCstr2inum(offset, &offset); break;
          case 'e':
            GCodePar.e = GCstr2inum(offset, &offset); break;
          case 'f':
            GCodePar.f = GCstr2inum(offset, &offset); break;
          case 'p':
            GCodePar.p = GCstr2inum(offset, &offset); break;
          case 's':
            GCodePar.s = GCstr2inum(offset, &offset); break;
          case ';':
            while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset;
        }      
      }  
      
      return offset;
    }
    
    
    /**************************************************************************
    * int ExecMCode(void)
    **************************************************************************/
    int ExecMCode(void){
      switch (GCodePar.gc) {
        case 0:           //All stop.
          StopSteppers();StopExtruder();
          break;
        case 2:           //End G-Code prog.
         StpTo(0,0,0);
         StopSteppers(); StopExtruder();
         break;
        case 82:          //Only absolute supported for extruder.
          break;
        case 104:         //Set extruder tempurature.
           ExTemp(GCodePar.s); break;
        case 106:         //fan on.
          /*FanSet(GCodePar.s)*/; break;
        case 107:         //Fan Off.
          /*FanSet(0)*/; break;
        case 109:         //Set extruder temp (we always wait).
           ExTemp(GCodePar.s); break;
        case 116:         //wait for all to be ready.
          break;          //We always wait for ready, so do nothing.
      }
          
      return 0;
    }
    
    
    /**************************************************************************
    * int ExecGcode(void)                                                     *
    *                                                                         *
    * Perform an Gxxx command.                                                *
    **************************************************************************/
    int ExecGCode(void){
      switch (GCodePar.gc){
        case 0:
        case 1:
          StpTo(GCodePar.x,GCodePar.y,GCodePar.z); break;
        case 4:
          WaitMS(GCodePar.p); break;
        case 21:
          GCodePar.dmul = inch; break;
        case 22:
          GCodePar.dmul = mm; break;
        case 90:            //G90 Only absolute positioning supported.
          break;
      }    
      return 0;
    }
    
    
    /**************************************************************************
    * int GCode(void)                                                         *
    *                                                                         *
    * This function is the outer level of the simple G-Code parser.           *
    * We ignore any GCode that is not supported, this should not make any     *
    * difference as long as you make sure to use absolute movement.           *
    *                                                                         *
    * PARAMETERS:                                                             *
    * The contents of the character buffer, codebuf.                          *
    *                                                                         *
    * TODO:                                                                   *
    *   DONE: Add the ability to skip past comments at the end of line.       *
    **************************************************************************/
    int GCode(void){
      char tc;
      short offset;
      offset = 0;
    
      while (codebuf[offset]){  /*Continue until next char is NULL.*/
        while (codebuf[offset] < 0x21) offset++; /*skip leading spaces.*/
        if ((codebuf[offset++] == 0xD) || (codebuf[offset++] == 0x0A))   /*check for end of line*/
          while (GCodePar.status);      /*Wait for command to finish running.*/
    
        switch (codebuf[offset++] | 0x20) {                 /*Determine if we are using a G command or M command.*/
          case 'g':
            offset = GetGParams(offset); ExecGCode(); break;
          case 'm':
            offset = GetGParams(offset); ExecMCode(); break;
          case ';':
            while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D && codebuf[offset] != 0) ++offset;
        }    
      }
      return 0;
    }
    

    extrude.h:
    void StopExtruder(void);
    void ExTemp(long t);
    
    [/code]
  • JasonDorieJasonDorie Posts: 1,930
    edited 2015-04-18 14:11
    I'm curious why you need Fwd() / Bck() functions when most stepper controllers require step and direction pins? Since the direction won't change in a line move, just set the direction, then toggle the step pin until you get there - checking for direction IN the inner loop is wasting cycles, unless I'm missing something.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-18 14:28
    JasonDorie wrote: »
    I'm curious why you need Fwd() / Bck() functions when most stepper controllers require step and direction pins? Since the direction won't change in a line move, just set the direction, then toggle the step pin until you get there - checking for direction IN the inner loop is wasting cycles, unless I'm missing something.
    I am not using a controller chip, I am doing it correctly and dirrectly putting the values for four of the lines on the 4 output pins, these go through a current sink (could use a amplifier as well), and the common is tied to the appropriate hot level (in this case +7v). I am using ULN2803's, as this application only needs small unipolar steppers, that take less than 200MA each.

    The same code should work with bipolar steppers, if you use bidirectional current driver like the L293D.

    There is no good reason to use one of the overpriced stepper controllers. They take more code to control, cost more, and do not provide any good advantage for most applications. If you need more current make some drivers using transistors, resistors, and diodes.

    IN SHORT:
    I am directly controlling the steppers, not using an external controller.
  • JasonDorieJasonDorie Posts: 1,930
    edited 2015-04-18 19:28
    In that case, I would just put your pin combinations in an array and keep an index into it. Then your "step" function just adds a delta count to the index in the array, modulo it's length (AND'd if it's a power of two, which it always will be). Then your routine becomes:
    Move( int Steps )
    {
      CurStep += Steps;
      CurStep &= 7;  // half step array
      Outa = (outa & ~OutPins) | StepArray[CurStep];
    }
    

    You wouldn't even have to test if the delta was non-zero (though doing so might save you some time). The code is all just lookups and simple logic and arithmetic ops which will translate to assembly really well, and you'll only need a single function.
  • idbruceidbruce Posts: 6,197
    edited 2015-04-18 20:26
    David
    The same code should work with bipolar steppers, if you use bidirectional current driver like the L293D.

    There is no good reason to use one of the overpriced stepper controllers. They take more code to control, cost more, and do not provide any good advantage for most applications. If you need more current make some drivers using transistors, resistors, and diodes.

    IMHO

    That line of thought will severely limit the usefulness of your effort. I would guess that most of the people who have participated in your discussions, either use or would use stepper drivers, as compared to sending pulses to the individual coils.

    However I can understand your line of thinking, because I once thought that way :)
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 04:38
    idbruce wrote: »
    David



    IMHO

    That line of thought will severely limit the usefulness of your effort. I would guess that most of the people who have participated in your discussions, either use or would use stepper drivers, as compared to sending pulses to the individual coils.

    However I can understand your line of thinking, because I once thought that way :)
    As full stepping with precise control, and the lowest price for something reliable is what is needed, it is the best solution.

    The unipolar motors I am using combined with the drive screws I am using provide a drive rate of 404 steps per mm (513 steps per revolution, with 20 TPI screws). That is a lot better than the possible resolution with the extruder heads I have made. Currently the best usable resolution is approximately 0.05mm (40.4 steps). So I am designing for way better resolution than current low cost extruders can handle.

    Now if you needed to produce a true analog wave, then a stepper driver would make sense. And I can see the situations where that could be useful.

    Do not get me wrong, I do intend to add support for stepper drivers in a later version, for those people that already have them. And I do see the a
  • idbruceidbruce Posts: 6,197
    edited 2015-04-19 05:28
    David

    I suppose that I should have added....

    I can surely relate to fully supporting your own needs on the first run, which is my goal also.

    I also don't want you to misunderstand me... I think what you are doing is great, but I would like to see a lot of people using your firmware, instead of a select few.

    I have done a LOT of research on stepper motors and stepper drivers. The main problem of driving steppers without a chopper drive, is definitely current control. When using this type of control scheme, users eventually end up wanting more speed and torque, as they start building a better machine. To accommodate higher voltages, they must then purchase and install costly and large power resistors. For the amount of money that would go into purchasing (4) power resistors for one stepper motor, they could have easily purchased a cheap chopper driver.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 06:08
    idbruce wrote: »
    David

    I suppose that I should have added....

    I can surely relate to fully supporting your own needs on the first run, which is my goal also.

    I also don't want you to misunderstand me... I think what you are doing is great, but I would like to see a lot of people using your firmware, instead of a select few.

    I have done a LOT of research on stepper motors and stepper drivers. The main problem of driving steppers without a chopper drive, is definitely current control. When using this type of control scheme, users eventually end up wanting more speed and torque, as they start building a better machine. To accommodate higher voltages, they must then purchase and install costly and large power resistors. For the amount of money that would go into purchasing (4) power resistors for one stepper motor, they could have easily purchased a cheap chopper driver.
    I can definitely understand that. And I do thank you for your input.

    I do intend to support alternative forms of HW, in future versions. I know that some will wish to expand the design to use larger steppers, and have a larger print volume, and this would likely mean going to BiPolar Nema 17 or bigger devices, that at very least require H-Bridge type drivers, and do have an advantage when using a more advanced stepper driver. Then there is the issue of acceleration, while there is a good range with simply directly driving the coils, you can get more with a true analog output. So yes I do intend to expand the support in the future.

    The main goal of the first run is to get it working well, upload the code along with the full schematics and a simple set of instructions to build the entire 3D printer. Basically for the first version get a 3D printer that can be built for under $150 out there.

    The initial design will include two build options for the controller HW, one using a Propeller Project Board USB (Basically a updated Propeller Proto Board USB) with the components soldered in and point to point wired, then one for those that are not competent with soldering using a breadboard with a P8X32A-D40 for the prop, optionally either a PropPlug or a serial connection (I prefer direct serial, though that is not as easy to come by anymore).

    I do intend to eventually provide more alternatives in the stepper drivers, many of the design elements, and the type of boards.

    I am also working on some host side software, though that is taking some time. That is:
    *:A simple 3D modeler in FreeBASIC (as portability for Graphics applications is better with FreeBASIC), supporting StL, OBJ (wavefront), and a new layered model format designed for use with 3D printing.
    *:A simple slicer, that produces G-Code with options for exactly what the printer does and does not support, with a bit limited of infill options at first, also in FreeBASIC.
    *:The direct control software, also in FreeBASIC for consistency.

    _______________________________________

    Though at at the moment Serial IO on the Propeller in C is giving me a headache, I am sure I will get it figured out.
  • idbruceidbruce Posts: 6,197
    edited 2015-04-19 06:14
    David
    Though at at the moment Serial IO on the Propeller in C is giving me a headache, I am sure I will get it figured out.
    [/qoute]

    What's the problem? Are you parsing characters or strings? Is the problem on the sending or the receiving end?
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 09:41
    idbruce wrote: »
    David
    Though at at the moment Serial IO on the Propeller in C is giving me a headache, I am sure I will get it figured out.
    [/qoute]

    What's the problem? Are you parsing characters or strings? Is the problem on the sending or the receiving end?
    The problem is figuring out what serial driver to use, and the varying API of them on the Propeller. I am actually considering just taking the serial commands from PropBASIC as compiled, converting to gas style assembly and including that in the compile. Though I am not sure.

    So no trouble with the implementation, just with the decision.

    I may end up just using FDSerial, we will see. Though it seems as though it is going to be a lot of work to get rid of the excess code in FDSerial, stuff that is useless for the application.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-19 15:22
    Though it seems as though it is going to be a lot of work to get rid of the excess code in FDSerial, stuff that is useless for the application.

    If you're willing to try C++, PropWare's UART implemenations are fairly efficient. If you need buffered output, start an instance in a new cog and use a Queue for inter-cog communication.

    Based on the "Hello Demo" program in PropWare, code sizes (compiled w/ LMM) are approximately:
    • PropWare: 5636
    • Simple: 4780
    • TinyStream: 3244
    • TinyIO: 3756
    • FdSerial: 5980
    • Libpropeller: 7608
    These numbers are based on propeller-load, so they are not perfectly accurate. But, I think (hope) they give a somewhat fair representation.

    Note that PropWare's size can be easily dropped to 4784 by switching from printf to the stream interface (pwOut << "Hello, world! " << i << ' ' << i << '\n'; ), but you loose control over the padding and hex format. You can get that formatting back by calling print with its optional second argument. All of the methods on a PropWare:: Printer instance can be used interchangeably to fit your code size needs.

    At a minimum, you can get PropWare's SimplexUART class down to 4340 bytes by calling send_array directly instead of using a Printer instance. If you want to write all the formatting code yourself, this is a great way to go about it. See the SimplexUART_Demo example.

    Speed comparisons can be found in this thread.

    If you want to use PropWare's classes but are not using PropWare's build system, simply copy the following into your project: PropWare.h, pin.h, port.h, everything under PropWare/uart/ and (optionally) everything under PropWare/printer/
  • idbruceidbruce Posts: 6,197
    edited 2015-04-19 17:22
    UH OH.... Post #21..... I don't remember saying that :) David must have me confused with David :)
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-19 17:24
    idbruce wrote: »
    UH OH.... Post #21..... I don't remember saying that :) David must have me confused with David :)

    LOL! Fixed :P
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 17:44
    If you're willing to try C++, PropWare's UART implemenations are fairly efficient. If you need buffered output, start an instance in a new cog and use a Queue for inter-cog communication.

    Based on the "Hello Demo" program in PropWare, code sizes (compiled w/ LMM) are approximately:
    • PropWare: 5636
    • Simple: 4780
    • TinyStream: 3244
    • TinyIO: 3756
    • FdSerial: 5980
    • Libpropeller: 7608
    These numbers are based on propeller-load, so they are not perfectly accurate. But, I think (hope) they give a somewhat fair representation.

    Note that PropWare's size can be easily dropped to 4784 by switching from printf to the stream interface (pwOut << "Hello, world! " << i << ' ' << i << '\n'; ), but you loose control over the padding and hex format. You can get that formatting back by calling print with its optional second argument. All of the methods on a PropWare:: Printer instance can be used interchangeably to fit your code size needs.

    At a minimum, you can get PropWare's SimplexUART class down to 4340 bytes by calling send_array directly instead of using a Printer instance. If you want to write all the formatting code yourself, this is a great way to go about it. See the SimplexUART_Demo example.

    Speed comparisons can be found in this thread.

    If you want to use PropWare's classes but are not using PropWare's build system, simply copy the following into your project: PropWare.h, pin.h, port.h, everything under PropWare/uart/ and (optionally) everything under PropWare/printer/
    OUCH, nearly double my code size is what that would do. Currently it is compiling to 5811 bytes in size(CMM, I think that LMM is just about 9KB if I remember correctly), that is with everything except for serial support. I definitely do not want to nearly double that just to add serial.

    I do thank you for the suggestion though. I may just polish up the code, finish commenting everything, upload an archive with all the current source, add a C file that only contains a description of what is required of the Serial IO, and leave that part to everyone else. Though testing will be limited, with having to use canned G-Code to test with.

    If I can come up with a good small serial driver in pure C, that is fast enough I will get the C version out along with the SPIN and PropBASIC versions.

    _____________________________________________________________________________________________________________________
    Where things stand at this time:
    There are conditionals in place to change the type of outputs used to control the HW..

    Currently the code is setup for direct control of the stepper coils, for the filament feed in the extruder there is the option of using either a continuous rotation servo, or a stepper with the existing code. Obviously more conditional compilation statements can be added along with more code to control more possibilities (even things like CNC spool motor control would be simple to add).

    Though I am pretty much done with the 3D printer implementation itself.

    I still have to add more kinds of stepper drivers, as well as support for more options. I also need to add support for reading the resistance of the thermistor used in the extruder (do not know how to do that with out assembly, and the C version should be pure C [I am using a simple 2 pin three resistor delta-sigma adc]).

    It may take me a couple of days to Finnish commenting and organizing the completed C code (plus I have to upload it to my 3D printer a couple more times to be sure that everything is working). Though I intend to have something very usable uploaded soon, that as long as you are willing to bring your own serial.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 17:45
    OUCH, nearly double my code size is what that would do. Currently it is compiling to 5811 bytes in size(CMM, I think that LMM is just about 9KB if I remember correctly), that is with everything except for serial support. I definitely do not want to nearly double that just to add serial.

    I do thank you for the suggestion though. I may just polish up the code, finish commenting everything, upload an archive with all the current source, add a C file that only contains a description of what is required of the Serial IO, and leave that part to everyone else. Though testing will be limited, with having to use canned G-Code to test with.

    If I can come up with a good small serial driver in pure C, that is fast enough I will get the C version out along with the SPIN and PropBASIC versions.

    _____________________________________________________________________________________________________________________
    Where things stand at this time:
    There are conditionals in place to change the type of outputs used to control the HW..

    Currently the code is setup for direct control of the stepper coils, for the filament feed in the extruder there is the option of using either a continuous rotation servo, or a stepper with the existing code. Obviously more conditional compilation statements can be added along with more code to control more possibilities (even things like CNC spool motor control would be simple to add).

    Though I am pretty much done with the 3D printer implementation itself.

    I still have to add more kinds of stepper drivers, as well as support for more options. I also need to add support for reading the resistance of the thermistor used in the extruder (do not know how to do that with out assembly, and the C version should be pure C [I am using a simple 2 pin three resistor delta-sigma adc]).

    It may take me a couple of days to Finnish commenting and organizing the completed C code (plus I have to upload it to my 3D printer a couple more times to be sure that everything is working). Though I intend to have something very usable uploaded soon, that as long as you are willing to bring your own serial driver cog.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-19 18:01
    OUCH, nearly double my code size is what that would do.

    There's a lot of boiler plate code in there. For instance, changing the PropWare test from
    pwOut.printf("Hello, world! %03d 0x%02X\n", i, i);
    
    pwOut.printf("Hello, world! %03d 0x%02X\n", i, i);
    putChar('\r');
    

    Only increased code size by about 1.5 kB. And change Simple's section from
    printi("Hello, world! %03d 0x%02x\n", i, i);
    

    to
    printi("Hello, world! %03d 0x%02x\n", i, i);
    pwOut.print('\r');
    

    increased code by about 2.1 kB.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-19 18:31
    There's a lot of boiler plate code in there. For instance, changing the PropWare test from
    pwOut.printf("Hello, world! %03d 0x%02X\n", i, i);
    
    pwOut.printf("Hello, world! %03d 0x%02X\n", i, i);
    putChar('\r');
    

    Only increased code size by about 1.5 kB. And change Simple's section from
    printi("Hello, world! %03d 0x%02x\n", i, i);
    

    to
    printi("Hello, world! %03d 0x%02x\n", i, i);
    pwOut.print('\r');
    

    increased code by about 2.1 kB.
    Either way you are using a formatted text function, something that is completely unneeded and big.

    A better implementation for the purpose would be a driver that only has char *gets(char *c) and int getchar(void) for input functions, then int putchar(int c) and int puts(char *s) for output functions. That way your Hello World becomes:
    #include "whateverserial.h"
    
    int main(){
      puts("Hello World!\n");
    }
    

    That would get rid of all the extra unneeded code.

    For sending the text representation of an integer using hex for the output you would use:
    int PrintHex(int n){
      int tmp;
    
      for (int t = 28; t; t -= 4)
        if ((tmp = (n >> t) & 0x0F) <= 9) putc(tmp + '0');  else putc((tmp - 10) + 'A');
    

    And similarly simple code to display integers in other formats, also not much more for a floating point value (if you use them).

    You see I like good small code.
  • DavidZemonDavidZemon Posts: 2,973
    edited 2015-04-19 19:27
    Either way you are using a formatted text function, something that is completely unneeded and big.

    A better implementation for the purpose would be a driver that only has char *gets(char *c) and int getchar(void) for input functions, then int putchar(int c) and int puts(char *s) for output functions. That way your Hello World becomes:
    #include "whateverserial.h"
    
    int main(){
      puts("Hello World!\n");
    }
    

    That would get rid of all the extra unneeded code.

    It's entirely up to you to use only what you need. The linker in GCC will ensure that unused functions are not included. That's why when I said "Note that PropWare's size can be easily dropped to 4784 by switching from printf to the stream interface." The stream interface uses templates so that only the absolute bare minimum code is pulled in. The compiler does the optimization for you to figure out which methods are needed and which aren't. Same exact thing when you use the overloaded Printer::print function (since the << operator just calls Printer::print).

    Of course, if you don't trust GCC, just use PropWare::SimplexUART directly. It does not have any formatting functions - just send (for up to 16-bits), send_array (arrays of 8-bit or smaller variables with known length), put_char (limited to 8-bit) and puts (null-terminated strings).
  • jazzedjazzed Posts: 11,803
    edited 2015-04-19 22:24
    Hi.

    The SimpleText library provides these little functions among others:



    void
    putDec (int value)



    Print string representation of a decimal number to the debug port.


    void
    putBin (int value)



    Print string representation of a binary number to the debug port.


    void
    putHex (int value)



    Print string representation of a hexadecimal number to the debug port.




    These exist because became clear to me long ago that the overhead of a formatted print function is usually unrecoverable. However, you will see that the printi() function is one of the smallest formatted print functions available for Propeller GCC.

    The SimpleText library is quite powerful, easy to use, and what Parallax wanted.
Sign In or Register to comment.