View Full Version : An appeal on behalf of the hard of thinking.

12-23-2009, 02:06 AM
At this time of year my thoughts turn to those, like myself, who have difficulties understanding zero-terminated strings, pointers and the like. Often un-noticed and unacknowledged by society during the year, such people are afraid to let their predicament be widely know for fear of ridicule, abuse and - quite simply - the shame and stigma of the condition. Without you being aware, one of these people might be a member of your family, a friend or a colleague - hiding their lack of understanding behind a brave face and only letting their condition be visible in the comfort of their own home, or in the company of fellow sufferers.

Can you, in this time of festive cheer, spare a few seconds of your busy time to help such a person? It might take you only moment, but could change the life of a sufferer. No money need change hands.

"MY NAME IS HUGH. I DON'T UNDERSTAND STRINGS AND THINGS" (despite Mike Green's valiant attempts to educate me).

There I have said it. I am 'out' and asking for help....

OK, I am "p-reading" some bytes from FSRW:

sdfat.mount (3)
r := sdfat.popen(string("TEST.txt"), "r")
r := sdfat.pread(SDbuf,3)

repeat r

to read the contents of a file that contains only "12A" as text.

The above code results in '0312A |o±' being displayed on the LCD so:
- The file opens for read OK
- Three bytes were inputted
- The three bytes of SDBuf were sent as a string to the LCD

I am guessing that the gibberish after the data I read is due to the string not being zero-terminated, etc.

As a simple question: "if a text file contains an ASCII representation of a number (e.g. "1234"), how can I input and process this as a number from an SD card using FSRW?". I am happy raising numbers to a power of ten to give place value, etc., but it is the getting of the value that is the problem.

Help! Please?!

Hugh - the thinking woman's Geoffrey Pyke.

Oldbitcollector (Jeff)
12-23-2009, 02:12 AM

A couple us did this "SD Trainer" a while back and if you learn by example, you'll find it useful. :)

www.warrantyvoid.us/files/SD_trainer.zip (http://www.warrantyvoid.us/files/SD_trainer.zip)


New to the Propeller?

Visit the: The Propeller Pages (http://www.warrantyvoid.us/tiki-index.php?page=Propeller) @ Warranty Void.

12-23-2009, 02:14 AM
The argument for lcd.str has to be a pointer to the string, e.g. lcd.str[ @sdbuf ]. What it's doing the way you wrote it is taking the first 2 bytes of the string, turning them into a Hub address, and printing whatever it finds there. You also need to do that for sdfat.pread( @SDbuf,3 ), because it's also putting the data you read in some random place for the same reason. If you don't read a zero, when you print the string it may print past the end of what you read because there is no null terminator; you fix that by inserting one, e.g. sdbuf[ 3 ] := 0.

12-23-2009, 04:30 AM

Thank you, Gentlemen!

What I am trying to do is increment a file-name counter, reading the value of the previous filename from a file named "Test.txt". With your help I now have a variable that contains an integer for the file name. For the sake of an example, that value is 12

If I do this:

sdfat.popen(fileNum, "w")

I get a file named '12' on the SD card. However, being Mr Picky, I'd like to include a file extension - which is where it seems to get tricky!

The intention of the following cod was to take the integer variable, turn it into a (zero-terminated) string and then append a "*.CSV" extension.

Fi := num.dec(filenNum)
byte[@Fi] := "."
byte[@Fi] := "C"
byte[@Fi] := "S"
byte[@Fi] := "V"

sdfat.popen(Fi, "w")

It doesn't quite work. Any suggestions?

I am back to logging four parameters from my car ECU. Yesterday it was snowing and the rpm rarely got above 2.000 rpm (see attached!)

Hugh - the thinking woman's Geoffrey Pyke.

12-23-2009, 05:08 AM

That was funny.....here is my attempt to help

A string can be thought of as a one dimensional array of BYTES. However, since a string can be of a random length it would be wasteful and inflexible to just fix the length of the array. Therefore a TRICK is used to INDICATE the ending of a string. We just use an additional byte and fill it with a value of 0.
Thus any actions performed on this one-dimensional array of bytes proceed from the START of the array and progress one byte a time always checking to see if the byte contains a value Zero·or not. If it does then the processing finishes.
Now in Spin (as in other languages) a variable can be referred to directly by name but their type (i.e. size) has to be known (e.g. byte, word, long).
However, if you need to refer to a collection (i.e. array) of variables then you can do this by referring to the ADDRESS in memory of the first one in the collection. After that you can access the next element and the one after that and so forth by adding an offset to the ADDRESS of the first element in the collection
So for instance if we think of memory as slots and we know that each slot holds ONE LETTER then if we know that the sentence we want to access STARTS at slot number 10 then we can access the entire sentence by getting the first letter from slot number 10, then the next one from slot number 10+1 and the third letter from slot number 10+2 etc.
So how do we know when the sentence has ended. Well, we check for the slot that contains a NULL letter (i.e. value of 0).
By the way this is not ‘0’ it is rather the value 0. Remember that letters have ASCII codes but the Ascii Code 0 is not considered as a·letter rather it is the NULL character.
So in order to define a string we need the ADDRESS of the first character of the letter and then make sure that the last character is the NULL (0) character.
To provide the address of the first character you can do it in one of two ways.
1-···· Use an actual memory address e.g. $10A7….. but this become untenable unless you have put the string there your self. However, normally the compiler will put the string where it deems suitable and you cannot know the address easily.
2-···· Since the compiler knows where the address is why not let it figure it out.
So we do this by specifying a name to the beginning of the string and then use the @ operator to tell the compiler that it is not the value inside the variable that we want rather the address in memory of the variable.
So when we have a variable named MyVariable and we use the name then we are telling the compiler we want the value stored at that variable. If we use the @ operator in front of the name e.g. @MyVariable then in fact we are telling the compiler to use the MEMORY POSITION of the variable not what is in that memory.
So then to operate on a string we need to store the string in memory somewhere and give a name to the FIRST byte of that string. Then also we need to make sure that the last byte of the string is assigned a 0.
This is usually done in the DAT section e.g.
··· MyString· byte· “this is the body of the string”,0·· ‘notice the last byte is a zero
In the above we have told the compiler that the first byte is called MyString. Also we told it that all the stuff that comes afterwards is BYTEs. Then we gave values to the bytes as the characters specified. The compiler knows to convert the characters to their Ascii values and to store these numbers in the consecutive bytes. Then we expressly stored a 0 in the byte that follows all these.
Now later when we want to process the string in some form or another. For example in the method of the FullDuplexSerial called Str() we give the method the ADDRESS of the string’s first byte @MyString. The method then knows how to proceed with the string since it now knows the end is also the byte with the 0 value.
The above is good if you already have stored the string in a memory at a known position (i.e. in the DAT section). This is also useful and efficient especially if you are going to use the string many times. However, sometimes you need to use a string only once. In this case you can create the string ON THE FLY.
You do this by instructing the compiler to create the string for you and put it where it wants in RAM but also to make sure it has a NULL (0) terminator.
This is achieved by the String() function in Spin. For example String(“my string”). This function also returns the address where it put the string.
So for example to use the FullDuplexSerial object’s Str() method you can do this.
Debug.Str(@MyString)·· ‘ using the preset string in the Dat section above
Debug.Str(String(“test string”)) ‘using the on the fly method.
I hope this has been clear enough.

12-23-2009, 06:05 AM

Thanks also for the help! Been through the "Fundamentals" twice. Always hit the wall trying to keep the address vs content straight in my peble mind. Also remembering that strings are ASCII codes sometimes messes with me. Any how thanks for your help and sorry for interrupting this thread.


12-23-2009, 08:24 AM

The code you posted would not work.

1- The Dec() method makes a number out of a string not a string out of·number. you need to use the ToStr() method.
2- The variable Fi would point to the to the beginning of the string (assuming you used the ToStr() method not Dec() method)
··· and when you say byte[@Fi] := "." you are making a byte in memory··become the value "." but it is not the string.
3-So what you need is byte[Fi] := "."
4- BUT..... that still does not work since you are not APPENDING you are OVERWRITING the string since Fi points to the first
··· character of the string.
5- what you need is byte[Fi + L] := "." where L is the length of the string. So you need a way of finding the length of the string.
6- for the next one you need to increment by 1, so you say byte[Fi+L+1] := "C", byte[Fi+L+2] := "S" etc.
7- L can be determined by going through the string until you encounter a 0 character eg
··· L:=0
··· repeat
······ if byte[Fi+L] == 0
········· quit
······ L++
7- Use L:= StrSize(Fi) to determine the length of the string.

I hope this helps.


Post Edited (SamMishal) : 12/23/2009 10:51:21 AM GMT

12-23-2009, 08:29 AM

It would be useful to know what the types of num, Fi, and fileNum are; please post more of your code (or attach it to your post).

12-23-2009, 03:06 PM
Thanks Samuel,
The 'as is' code (before I make any changes based on what I have learned) looks like:

Byte r, q, pwr, ct, factor, fileName
Byte Dataout[20], Fi[10]

PUB Main

ct: =0
fileName := 0

lcd.str(string("Mounting SD...")) ' Tell the user what is happening
r := sdfat.mount(3) ' Mount the drive (based on pin 3) and receive a number wih the result
lcd.dec(r) ' Display (briefly) the result

r := sdfat.popen(string("TEST.txt"), "r") ' Open the file containing the reference number
lcd.dec(r) ' Display the status returned
r := sdfat.pread(@SDbuf,3) ' Read (up to) three bytes of the reference number: get
' the number of bytes read
lcd.dec(r) ' Display the number of bytes read
SDBuf[r+1] := 0 ' Add a zero termination

q :=(@SDBuf)

repeat r ' For each byte read from the TEST.txt file...
q:=SDBuf[ct]-48 ' Numeric value is ASCII value - 48
pwr := (r- ct -1) ' 10 to power of..
factor := 1
repeat pwr
factor := factor * 10
fileName := fileName + (q * factor)

filename++ ' At this point we have an integer that is the value in
' the Test.txt file.
lcd.dec((fileName)) ' increment it by one: this will be the filename we will next use.
sdfat.pclose ' Close the TEST.txt file opened for write
sdfat.popen(string("TEST.txt"), "d") ' Open for delete the TEST.txt file - we are about to update the
' reference
sdfat.pclose ' Close the TEST.txt file for delete.
sdfat.popen(string("TEST.txt"), "w") ' Open the Text.txt file for write
bytemove(@DataOut,num.dec(fileName),3) ' Write the updated file reference number to the file
sdfat.pclose ' Close the file
Fi := num.dec(filename)
byte[@Fi][1] := "."
byte[@Fi][2] := "C"
byte[@Fi][3] := "S"
byte[@Fi][4] := "V"

sdfat.popen(Fi, "w") ' Create the new file, e.g., "13.csv"

In other words, I want the prop to wake-up and write to a new file each time it is powered, i.e., "1.csv", "2.csv" ... "n.csv", using a file on the SD drive to store the most recently used number.

Yes, the above code is a bit clunky (particularly where it reads the reference from the file and turns it to an integer) but this allowed me to verify each step.

If I understand correctly

Fi := num.dec (filename)

ends up with an area of memory that, for fileName = 12, would look like:· 49|50|0

So I need to make·"Fi" look like 49|50|46|67|83|86|0 ("12.CSV") before I can use it as a filename.(?)



Hugh - the thinking woman's Geoffrey Pyke.

12-23-2009, 03:08 PM
SamMishal said...

4- BUT..... that still does not work since you are not APPENDING you are OVERWRITING the string since Fi points to the first· character of the string.

I think I confused myself with the mention of an offset in the Prop manual.


Hugh - the thinking woman's Geoffrey Pyke.

12-23-2009, 03:58 PM
Hugh said...

If I understand correctly

Fi := num.dec (filename)

ends up with an area of memory that, for fileName = 12, would look like: 49|50|0

What kind of object is num?

You might consider adding a couple of helper functions:

pub Copy( p, q )
' copy string q to string p.
' q points to a null-terminated string.
' p points to a buffer long enough to contain string q.
bytemove( p, q, strsize(q) + 1 )

pub Append( p, q )
' append string q to string p
' p and q point to null-terminated strings.
' p must point to a buffer big enough to hold the concatenated strings.
Copy( p + strsize(p), q )

Then you should be able to just say

Copy( @fi, num.dec(filename) )
Append( @fi, string(".CSV") )

12-23-2009, 04:06 PM
Hugh said...

Fi := num.dec (filename)

ends up with an area of memory that, for fileName = 12, would look like:· 49|50|0

So I need to make·"Fi" look like 49|50|46|67|83|86|0 ("12.CSV") before I can use it as a filename.(?)
What is num ???? is it the Numbers object?

If it is then num.Dec() is not the right thing to use....???

Any way if it is and num.Dec creates a string out of the FileName, then Fi is now a pointer (i.e. address) to the
first character in the string.

Now assuming that the BUFFER was sized to be longer than the actual string then you need to do this
L := 2
byte[Fi+L] := "."
byte[Fi+L+1] := "C"
byte[Fi+L+2] := "S"
byte[Fi+L+3] := "V"
byte[Fi+L+4] := 0
This would work assuming that you ALWAYS have Filename being no longer than 2 digits. If it can be longer than 2 digits
then you need to change L to be the length of the string created.
(see other posting below)

ALSO....and this is important.....the area in memory where num.Dec() creates the string must have been sized sufficiently so as when you add those 4 characters you are not running over another memory area that may corrupt the program.

P.S. NOTICE..... it is byte[Fi+L]··or byte[Fi][L]· BUT· NOT·· byte[@Fi][L] or byte[@Fi+L]· ...... this because the value
in Fi is the address you want and thus you want the value of Fi not its address which is not the address you need.

You need the address of the string created by num.Dec() which is the value returned by the method and is stored
in Fi. So when you need to access the string you use Fi not @Fi since the address you want is now stored in Fi.
@Fi will give us the address of the variable Fi and not the address of the string.

Post Edited (SamMishal) : 12/23/2009 10:49:49 AM GMT

12-23-2009, 05:32 PM

I always find enlightening to have a peek at the bytes produced by a compiler, contained in a file.. in memory. To write them down to paper also helps. There is always an area where one of us is lacking: parsing algebraic expressions and transforming them to polish notation is one area where I am mostly lost. :-(.

Visit some of my articles at Propeller Wiki:
MATH on the propeller propeller.wikispaces.com/MATH (http://propeller.wikispaces.com/MATH)
pPropQL: propeller.wikispaces.com/pPropQL (http://propeller.wikispaces.com/pPropQL)
pPropQL020: propeller.wikispaces.com/pPropQL020 (http://propeller.wikispaces.com/pPropQL020)
OMU for the pPropQL/020 propeller.wikispaces.com/OMU (http://propeller.wikispaces.com/OMU)

12-23-2009, 05:43 PM
Spin is even better than I thought ..... there is a function in Spin called StrSize() ... I did not notice it before.
This function returns the length of a ZString· (i.e. zero terminated string).

So my previous code can now be modified easily to
·· L := StrSize(Fi)
·· byte[Fi+L] := "."
·· byte[Fi+L+1] := "C"
·· byte[Fi+L+2] := "S"
·· byte[Fi+L+3] := "V"
·· byte[Fi+L+4] := 0·· 'note you need this to maintain a o terminated string

so disregard the previous suggestion for finding the size of a string. Also there is no need to worry
about the size. Just use the StrSize() function as shown above.

This way you always can start appending the ".CSV" to the end of the string regardless of the
value in FileNum. FileNum can now be any number you want and as long as the num.Dec()
converts it to a string and returns the pointer which you store in Fi then you can use the above
code to append the extension as you need.

Of course you also have to ensure that the buffer where num.Dec() is storing the created string is
long enough to hold the extra 4 bytes without running into code.

SPIN is GREAT ..... the StrSize(), String() and StrComp() are of great help when dealing with strings.


Post Edited (SamMishal) : 12/23/2009 10:48:07 AM GMT

12-23-2009, 05:48 PM
Thanks, I'll give this a go when I get home, but most importantly I understand what I will be attempting, why I will be doing it and what results I expect - much more helpful than just having a solution.

(The Num object is "Simple_Numbers".)

Thank you!

Hugh - the thinking woman's Geoffrey Pyke.

12-23-2009, 05:55 PM
Hugh said...(The Num object is "Simple_Numbers".)

Aahh.... ok now I see... I did not know about this object. It is quite good.
Yes then what num.Dec() does is exactly what you need. Also I checked and
the object creates a Buffer of 64 bytes to hold the string so it is GOOD.

Then Just use the modified code above.

Hugh said...Thanks, I'll give this a go when I get home, but most importantly I understand what I will be attempting, why I will be doing it and what results I expect - much more helpful than just having a solution.

EXACTLY...... Give a man a fish and he will eat for one day and teach him to fish he will drown.....http://forums.parallax.com/images/smilies/tongue.gif

I am glad you understand it..... can you now explain it to me...http://forums.parallax.com/images/smilies/smilewinkgrin.gif

No seriously I am GLAD you do understand it. Now you can do WHATEVER you need.


12-23-2009, 06:30 PM
If you're going to use Fi as a pointer, you should change its type from byte to word or long.

12-23-2009, 10:34 PM
mpark said...
If you're going to use Fi as a pointer, you should change its type from byte to word or long.
Yes...mpark is correct... I did not pay attention to that.

You should define the Fi variable as a Long in your Var section not as a Byte Fi[10]· but rather as a Long Fi.


You can remove it from the Var section and have it as a Local variable in Main...like this

Pub Main | Fi

All variables that are to be Pointers....i.e. a variables that·are going to be used to store the Address
of other variables ..... SHOULD be Longs.....you can get by with a WORD instead since the Propeller
has a maximum of 32K RAM and thus·Word would suffice....but definately not a Byte.

If you use it as a local variable it would be long automatically anyway.


12-24-2009, 02:22 AM
Thanks (for your time, for your intellectual capital and for your patience) - it works perfectly now!

Hugh - the thinking woman's Geoffrey Pyke.