Shop OBEX P1 Docs P2 Docs Learn Events
PBASIC-like DEBUG for Spin — Parallax Forums

PBASIC-like DEBUG for Spin

Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
edited 2012-09-13 16:08 in Propeller 1
One of the things I've always missed when programming the Propeller is the ability to write something like DEBUG DEC x, CR in my program and, when it executes, have a terminal window open automatically to display the output. So for the past few days, I've been working on a way to do just that in Spin. The attached zip contains two programs: ide_debug.exe and Parallax's propellent.exe. These should be unzipped into the same folder on your Windows system. When ide_debug.exe is executed, it will start the Propeller Tool, if it isn't already running, and place a Debug button in its upper right-hand corner. When clicked, the Debug button will process the file that's showing and load it into RAM for execution, then open Parallax Serial Terminal, change its com port settings, and enable it. When center-clicked, the action is the same, except that the processed program is loaded into EEPROM.

The "processing" mentioned above gives special treatment to comments that begin with '.debug. These comments are parsed to produce calls to the Propeller Serial Terminal object. The format of these comments is very much like that of the DEBUG statement in PBASIC. For example, consider the following statement:
'.debug "The result is: ", dec5 x, #CR

This will output The result is: , followed by the value of x as a decimal number in a five-character-wide field, followed by a carriage return.

You do not need to declare the PST object or start it, but you can it you want. The processor will look for an OBJ declaration and a call to start, but if it doesn't find one one or the other, it will automatically fill them in. This makes it possible to have, essentially, two versions of your program in a single source: one without the debug facility that loads and executes normally without any extra baggage, and one with debug statements that can be processed in when they are needed. It all depends upon which method you use (F10/F11 or Debug) to load and run your program.

The arguments to '.debug are separated by commas, just as they are in PBASIC. They are as follows:
DEC{n} {?} <expression>: Print the value of the expression as a decimal number. If a decimal constant immediately follows DEC (e.g. DEC5), the number will be output in a field of at least the designated width, padded as needed with leading spaces. If a question mark is interposed (e.g. DEC ? value), the number will be output as value = 12345, followed by a carriage return.

HEX{n} {?} <expression>: Print the value of the expression as a hexadecimal number. If a decimal constant immediately follows HEX (e.g. HEX5), the number will be output in a field of at least the designated width, padded as needed with leading zeroes. If a question mark is interposed (e.g. HEX ? value), the number will be output as value = 12A5, followed by a carriage return.

BIN{n} {?} <expression>: Print the value of the expression as a binary number. If a decimal constant immediately follows BIN (e.g. BIN16), the number will be output in a field of at least the designated width, padded as needed with leading zeroes. If a question mark is interposed (e.g. BIN ? value), the number will be output as value = 1010100, followed by a carriage return.

IHEX{n} {?} <expression>: Same as HEX except that the value is preceded by a dollar sign ($).

IBIN{n} {?} <expression>: Same as BIN except that the value is preceded by a percent sign (%).

REP <char>\<n>: Repeat the character char n times.

"<string>": Anything between quotes is sent as a string to PST. (No need to use Spin's string pseudo-function, either! In fact, you should not.) Single-character strings are sent as single characters; multi-character strings, as zero-terminated Spin strings. Strings can have escape codes embedded in them. An escape code begins with a backslash (\) and is followed by a single character:
\\: Use this to include an actual backslash.
\": Use this to embed a double-quote within the string.
\a: Alarm (terminal BELL).
\b: Backspace.
\t: Tab
\n: Newline (linefeed)
\r: Carriage return

#<control character name>: Control character names are a little different from the ones in PBASIC (since I never liked some of them much):
#CLS: Clear the screen.
#HOME: Home the cursor to the upper left-hand corner.
#MOVXY: Move the cursor to a position indicated by the x and y numbers to follow in the comma-separated list.
#LEFT: Move the cursor left one character position.
#RIGHT: Move the cursor right one character position.
#UP: Move the cursor up to the previous line.
#DOWN: Move the cursor down to the next line.
#BELL: Ring (beep) the terminal bell.
#BS: Backspace the cursor.
#TAB: Move cursor to the next tab position.
#LF: Linefeed.
#CLREOL: Clear to the end of the line.
#CLRDN: Clear all lines below this one.
#CR: Carriage return.
#MOVX: Move cursor to position x (next comma-separated argument) in the current line.
#MOVY: Move cursor to line y (next comma-separated argument).

In addition, if you prefer to use the control codes defined in PST instead (e.g. #CS), you can. Any names not included above or in PST will cause a compile error.

<any expression>: The least-significant eight bits of any expression not meeting the above formats are sent as a single character. So, for example,
'.debug 65

sends the latter "A".

The sequence of events that occurs when you press Debug is as follows:
1. The Propeller Tool creates an archive of the visible file and all of its objects and saves it to a temporary directory.
2. ide_debug unzips the archive, reads the readme file to determine which is the Top Level spin file.
3. It opens the Top Level file and strips all the comments except for those that begin with '.debug.
4. It then checks for a declaration of PST in the OBJ section and, finding none, creates one.
5. It checks for a call to PST's start method in the first PUB routine and, finding none, creates one. It also adds a delay ahead of the call to start to ensure that PST has time to open and initialize and makes sure that the baud rate is set to 38400.
6. It scans the Top Level program for all instances of '.debug, parses each one, and adds the appropriate calls to its own PRIvate method (__debug) or directly to PST. These are added at the same indent level as the '.debug comment, one per line.
7. Once this is done, the PRIvate method __debug is tacked on to the end of the processed Top Level program, and the Top Level program is saved back to the temporary directory from whence it came.
8. Once saved, propellent.exe is invoked to compile and load the program.
9. Assuming Propellent does not encounter any errors and loads successfully, an F12 command is sent to the Propeller Tool to open PST.
10. Once PST is open, the Com Port setting is changed to conform to the target port, the Baud Rate is set to 38400, and the Enable button is clicked.
11. From there, the debug output should spew forth -- all from a single button click!

Here's a sample program:
CON

  _clkmode      = xtal1 + pll16x
  _xinfreq      = 5_000_000

PUB  start | i

  '.debug "Testing the Spin DEBUG function:\r\r"
  repeat i from -10 to 10
    '.debug "  The value of i is ", dec3 i, ", and i-squared is ", dec i*i, ".", #CR

Here's the output it produces:
Testing the Spin DEBUG function:

  The value of i is -10, and i-squared is 100.
  The value of i is  -9, and i-squared is 81.
  The value of i is  -8, and i-squared is 64.
  The value of i is  -7, and i-squared is 49.
  The value of i is  -6, and i-squared is 36.
  The value of i is  -5, and i-squared is 25.
  The value of i is  -4, and i-squared is 16.
  The value of i is  -3, and i-squared is 9.
  The value of i is  -2, and i-squared is 4.
  The value of i is  -1, and i-squared is 1.
  The value of i is   0, and i-squared is 0.
  The value of i is   1, and i-squared is 1.
  The value of i is   2, and i-squared is 4.
  The value of i is   3, and i-squared is 9.
  The value of i is   4, and i-squared is 16.
  The value of i is   5, and i-squared is 25.
  The value of i is   6, and i-squared is 36.
  The value of i is   7, and i-squared is 49.
  The value of i is   8, and i-squared is 64.
  The value of i is   9, and i-squared is 81.
  The value of i is  10, and i-squared is 100.

And, for the curious, although you will never see it in practice, here is the processed program that does the actual output:
OBJ

  __dbg : "Parallax Serial Terminal"


CON
  _clkmode      = xtal1 + pll16x
  _xinfreq      = 5_000_000

PUB  start | i

  waitcnt(cnt + clkfreq)
  __dbg.start(38400)
  '.debug "Testing the Spin DEBUG function:\r\r"
  __dbg.str(string("Testing the Spin DEBUG function:",13,13))
  repeat i from -10 to 10
    '.debug "  The value of i is ", dec3 i, ", and i-squared is ", dec i*i, ".", #CR
    __dbg.str(string("  The value of i is "))
    __debug(0, 3, 0, i)
    __dbg.str(string(", and i-squared is "))
    __debug(0, 1, 0, i*i)
    __dbg.char(".")
    __dbg.char(13)
    

PRI __debug(__cmd, __mod, __pre, __val) | __field, __tval

  if (__pre)
    __dbg.str(__pre)
  if (__cmd == 3)
    __dbg.char("$")
  elseif (__cmd == 4)
    __dbg.char("%")
  case __cmd
    0:
      __tval := ||__val                                    
      __field~                                                
      repeat while __tval > 0                               
        __field++
        __tval /= 10
      __field #>= 1                                          
      if __val < 0                                          
        __field++                                             
      if __field < __mod                                      
        repeat (__mod - __field)                              
          __dbg.char(" ")                                      
      __dbg.dec(__val) 
    1, 3:
      __field := ((>|__val #> 1) + 3) >> 2
      __dbg.hex(__val, __mod #> __field)
    2, 4:
      __field := >|__val #> 1
      __dbg.bin(__val, __mod #> __field)
  if (__pre)
    __dbg.char(13)

Now for the caveats:
1. This program is very alpha. I'm sure it has bugs. If you find any, please report them in this thread.
2. The program processes debug statements in the Top Level file only.
3. If you make an error in a debug statement that produces bad code for Propellent (e.g. an undeclared variable), Propellent will note the error in a dialog box, but you may not be able to tell where it came from. So program with care!

Enjoy! :)
-Phil

Comments

  • Dave HeinDave Hein Posts: 6,347
    edited 2012-08-26 04:55
    Phil,

    That's looks great, but I'm curious why you wouldn't just create a method that processes formatted strings. This way you could just do something like this.

    debug2(string( " The value of i is %3d, and i-squared is %d\r"), i, i*i)

    Dave
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-08-26 08:43
    Dave,

    That format is familiar to C and Perl users, but less accessible to beginning programmers or those coming from a PBASIC background. Moreover, it's just easier to type something free-form if you're in a hurry. Then there's that pesky string pseudo-function again. Plus, the fact that Spin does not allow variable-length argument lists makes the implementation a bit awkward. And it would not permit part-time debugging or one-click compile-load-and-debug the way my implementation does.

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-08-27 11:03
    I've added a repeat function to the code, consistent with the REP formatter in PBASIC, and explained in the first post. Here's an example, and the output it produces:
    CON
    
      _clkmode      = xtal1 + pll16x
      _xinfreq      = 5_000_000
    
    PUB  start | i
    
      '.debug "Testing the Spin DEBUG function:\r\r"
      repeat i from -10 to 10
        '.debug rep "*"\||i, "  The value of i is ", dec3 i, ", and i-squared is ", dec i*i, ".", #CR
    
    Testing the Spin DEBUG function:
    
    **********  The value of i is -10, and i-squared is 100.
    *********  The value of i is  -9, and i-squared is 81.
    ********  The value of i is  -8, and i-squared is 64.
    *******  The value of i is  -7, and i-squared is 49.
    ******  The value of i is  -6, and i-squared is 36.
    *****  The value of i is  -5, and i-squared is 25.
    ****  The value of i is  -4, and i-squared is 16.
    ***  The value of i is  -3, and i-squared is 9.
    **  The value of i is  -2, and i-squared is 4.
    *  The value of i is  -1, and i-squared is 1.
      The value of i is   0, and i-squared is 0.
    *  The value of i is   1, and i-squared is 1.
    **  The value of i is   2, and i-squared is 4.
    ***  The value of i is   3, and i-squared is 9.
    ****  The value of i is   4, and i-squared is 16.
    *****  The value of i is   5, and i-squared is 25.
    ******  The value of i is   6, and i-squared is 36.
    *******  The value of i is   7, and i-squared is 49.
    ********  The value of i is   8, and i-squared is 64.
    *********  The value of i is   9, and i-squared is 81.
    **********  The value of i is  10, and i-squared is 100.
    

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-09-13 16:08
    I've uploaded a new version. 'No major features: just an expansion of the ways #<control character name> can be used, since it wasn't working with REP.

    -Phil
Sign In or Register to comment.