Musings re Strings

moxhamj

New Member
I've just downloaded the latest compiler software. I've also been reading the data sheets on the 20x2 and I'm as happy as a pig in mud. I've also realised I'm suddenly on the steep part of the picaxe learning curve again.

I'm wondering if these newer chips might be able to do strings. eg, in MBASIC:
Code:
A$="Hello "
B$="World"
C$=A$+B$
D=5
E$=STR$(D)
F$=A$+B$+E$
PRINT F$

Hello World 5
This is a trivial example, and it is a little more complex than it seems, certainly for smaller picaxes. But the 20x2 has a lot more possibilities.

First is memory. When a program has A$="Hello", the software in the background is allocating some space for A$, as well as filling it with bytes. In MBASIC the strings are dynamic to save memory space, so that might save only 5 bytes with a pointer table where the bytes are stored. But every now and then you have to resort the table and remove any unused strings. Another method is to allocate a fixed number of bytes, eg 32, for each string and terminate the string with ascii 0.

Memory can be cheap, eg this http://www.futurlec.com/Memory/23K256.shtml is a 32k SPI ram chip for only $1.35 (kilobytes too, not kilobits!) It doesn't wear out (unlike eeprom). I wonder if anyone has used serial ram?

First thing that leaps out from the datasheet is the 3V supply, but the 20x2 is 3.3V too so maybe there is a case for migrating designs to 3.3V? All the maths for analog input/outputs might need to change...

Anyway, that was one idea, but then I noticed the 20x2 has 1024 scratchpad ram bytes. Hmm - that is 32 strings of 32 bytes each (or more if strings are compressed with lookup tables). 32 strings may be more than many programs need, especially if some strings are 'working' strings, and some are data strings that are stored in eeprom.

For example, you might print out a message on a LCD display and that would come from eeprom strings. Then you might ask the user to type something on a keyboard, and the prompt string would also come from eeprom as it is read only. But the keyboard input might get stored in a ram string, and then manipulated (eg making it all caps).

So, maybe no external ram is needed? Maybe no external eeprom either as the program size is a lot bigger :)

Ok, how would strings work?

I'm thinking a simple little subroutine, where you pass b0 with the number of the string (0-31) and it works out the address (multiply by 32) and then does a GET on the 32 bytes and fills b1 to b32. Oh, I do like the idea of more than 14 b registers!

Ditto save a string, but in reverse.

Now we could start looking at string routines.
LEFT()
MID()
RIGHT()
INSTR()
UCASE()
LCASE()
STR()
HEX()
VAL()


Some of these already exist but with different syntax, eg Bintoascii is the same as STR(). Just put an ascii 0 at the end as the string terminator.

And some are not that hard to write. I've written all the above in Z80 assembly and it should not be hard to translate eg Left:
Code:
STRINGS_LEFT:	; check string de, returns string in hl, number of bytes in B
	PUSH HL
STRINGS_LEFT_1:
	LD A,(DE)			; GET CHARACTER
	LD (HL),A			; MOVE IT
	INC HL			; HL+1
	INC DE			; DE+1
	DJNZ STRINGS_LEFT_1	; LOOP UNTIL B=0
	LD A,0			; 0 AT THE END OF THIS SHORTER STRING
	LD (HL),A
	POP HL			; RESTORE START
	RET
Some functions are implied in MBASIC but have to be explicitly written, eg
A$=B$

would need to be instead a gosub
b0=3
b1=6
gosub copystring ' copies string in b0 to string in b1

So, a question to the picaxe gurus - is this on the right track? Are there simpler ways of doing things?

Can a keyboard be connected to a 20x2? If so, one can consider a subroutine that reads keyboard input and puts it into a string and continues until the user hits 'Enter'.

I've got all sorts of other ideas. I have had these ideas for some time, indeed I tried putting a lot of this into an 18X but it ran out of code space. But I think the newer chips may well open up a lot more possibilities, especially if there were libraries of generic routines that you could drop into a program.
 
Last edited:

westaust55

Moderator
Can a keyboard be connected to a 20x2? If so, one can consider a subroutine that reads keyboard input and puts it into a string and continues until the user hits 'Enter'.
Hi Dr_A

Yes the 20X2 has two pins which can be used for a standard keyboard input.

You have the KBIN and KEYIN commands available to you. Also the KEYLED (or KBLED) command.There is a conenction diagram for conencting the standard PC keyboard at the end the the page on KEYLED.

KBIN has an optional timeout feature if you do not which to wait forever for keyboard entry.

No X2 parts in my collection yet, but would think storing each character using a pointer is certainly possible and terminate with a string.


Like you thinking about strings.

Wrote machine code program myself for 6502 cpu's to "hook" into BASIC on old computers like OSI C2-4P and CBM C64 years ago to handle strings with my own test to speech algorithms.
 

Dippy

Moderator
It's Sunday and I have a hangover, so I'm going to ask a silly question re the strings:

Is your post proposing how to handle strings?
Or is it some WIBNI added string functions for future PE development?

String handling is quite simple with arrays or if the string space is allocated consecutively in RAM by using ASM or a compiler.
I do a lot of it with PIC but couldn't see an easy conversion to PICAXE.
I also have used external memory to store large data arrays and it works well (albeit relatively slow),

The KBIN looks a really useful command though obv your routine would have to build up the array.

Sorry if I've missed the point, but my excuses have been explained in Line 1 :)
 

hippy

Technical Support
Staff member
Strings are just lists of bytes, with a length indicator or a terminating character, a string variable usually holds a pointer to where that list of bytes starts. String processing routines take a string pointer then process the list of data bytes; or take a number of string and other variables as appropriate.

All that could be implemented on a PICAXE, albeit not as elegantly as in a language which has support for strings and hides the underlying complexity. Your suggestion is certainly one way to go.

The issue with strings is you can either have a fixed, static solution, or a more complicated dynamic solution. The former is ideal when you know what you want, are designing your specific string handling to fit a need, the second better for general purpose handling where you don't know how big a string may need to be or you want to maximise memory use. Unfortunately dynamic string handling gets a bit complicated and if strings are moved around in memory the pointers to start of string need to be kept up to date.

You can however have an array ( also in this dynamically controlled memory ), so a variable holds the number of a string, index into the array to find the start of the string. Everything is then moved into the string handler routines.

Then it's just a matter of writing the string handler routines, and working out how to call the required routines to do what you want. For example ...

myVar$ = "Hello"
myVar$ = UCase$( myVar$ )
myVar$ = myVar$ + myVar$
Print myVar$

could become ...

arg1 = 5
Gosub String_CreateStringOfSizeArg1
myVar = arg1
Poke @ptrinc, "H"
Poke @ptrinc, "e"
Poke @ptrinc, "l"
Poke @ptrinc, "l"
Poke @ptrinc, "o"

arg1 = myVar
Gosub String_Ucase

arg1 = myVar
arg2 = myVar
Gosub String_AppendTwoStringsAndReplaceArg1
myVar = arg1

arg1 = myVar$
Gosub String_PrintUsingSerTxd

Pretty much how you'd do it in assembler but you're forced to use PICAXE commands which are not optimal, may use more code space and require more Gosubs than desired. But basically the task has then been reduced to writing library code for all the routines needed for such string handling. That can be as simplistic, limited or complicated as you want or need. Just the slog of designing, writing and debugging the code. One easy example ( assuming string length held as first byte of string itself) ...

String_UCase:
ptr = arg1 ' arg1 points to pointer to start of string
ptr = @ptr ' Set ptr to start of string
len = @ptrinc ' Get string length
Do while len > 0
ch = @ptr
If ch >= "a" And ch <= "z" Then : ch = ch &/ $20 : End If
@ptrinc = ch
len = len - 1
Loop
Return

The more complicated routines are those which manage the dynamic allocation and keep everything in sync. Having fixed-sized strings, no dynamic data, simplifies it a little but, if well designed and modularised, dynamic string handling isn't that much more complex.
 

hippy

Technical Support
Staff member
Tada !

Not too hard to do. Here's a thrown together dynamic string handler. Converting to upper case etc shouldn't be done on the string, should create a new string to hold the result then swap the old string var for the new, dispose of old string etc if needs be. Otherwise you can't easily do ...

myVar$ = "Hello"
myOtherVar$ = myVar$
myVar$ = myVar$ + Ucase$(myotherVar$) + myOtherVar$
Print myVar$

Needs sitting down and thinking about and doing properly rather than being bashed-out over a cup of tea and a bacon sarnie :)
 

Attachments

moxhamj

New Member
Thanks for the responses! Re Dippy, WIBNI's sometimes turn into "it's already been done, and here's how"

Though Hippy has posted
Code:
Poke @ptrinc, "H"
Poke @ptrinc, "e"
Poke @ptrinc, "l"
Poke @ptrinc, "l"
Poke @ptrinc, "o"
And that is the sort of code I was putting into an 18X and was wondering if there might be a more elegant way using one line of code rather than putting in the characters one at a time? [even assembly languages can fill memory with "Hello World" using one line of code]

Hmm -
eeprom 0,("Hello",0)
eeprom 32,("World",0)

But there are only 256 eeprom bytes on a 20x2 so you can only store 8 strings like that.

(Dynamic allocation seems very complicated)

In a way, the actual code to do Left, Mid, Instr might end up being the simpler thing. The more complicated part might be the way strings are allocated and the most logical structure.

Is there an equivalent of the eeprom command that works for external eeprom chips? (I'm looking at an 8k 24L64 at $1.20).

Is the right instruction writei2c? Or is it hi2cout?

Hmm, in hi2cout it says:

HI2COUT location,(variable,...)
Location is a variable/constant specifying a byte or word address.


The example given is one that fills registers and outputs those, but is it possible to output "Hello" in quotes as the variable?

Or is there another way that is better?

I suppose the '1 letter per line of code' might still be more efficient as the 20x2 has lots of code space but not much eeprom space (relatively).
 

hippy

Technical Support
Staff member
The Poke @ptrinc, "H" is so wrong on a number of levels; the attached code fixes that and it should be a simple @ptrinc = "H".

There's no 'move a block of data into scratchpad' but by using external I2C memory that would be a way. Would be best to use I2C Ram though.

It all comes down to exactly what you need, how best to do it. I'd favour an entirely dynamic string library as otherwise you are limiting possibilities, creating a specific solution, rather than one which can be used by others or is easily extensible. Having 32 strings of 32 characters, or whatever, may be fine for one application, but how do you make that work when there's a need for longer or more strings and there is enough memory available for those ? Sods law says that whatever non-generic design you come up with it won't meet the needs of anyone else or future projects.

Any way you do it you're looking at bashing a square peg into a round hole, working around limitations to get something which isn't supported already, and there will be no perfect solution.
 

westaust55

Moderator
Thanks for the responses!

And that is the sort of code I was putting into an 18X and was wondering if there might be a more elegant way using one line of code rather than putting in the characters one at a time? [even assembly languages can fill memory with "Hello World" using one line of code]

Hmm -
eeprom 0,("Hello",0)
eeprom 32,("World",0)

Is there an equivalent of the eeprom command that works for external eeprom chips? (I'm looking at an 8k 24L64 at $1.20).

Is the right instruction writei2c? Or is it hi2cout?

Hmm, in hi2cout it says:

HI2COUT location,(variable,...)
Location is a variable/constant specifying a byte or word address.
With extermal i2c memory whether EEPROM or RAM (I have used Ramtron F-RAM albeit relatively expensive compared to EEPROM).
FRAM chips are 8 pin, drop in in lieu of the 24LCxxx sereis EEPROM and also use the same slave address as EEPROM.

writei2c will work with any PICAXE with inbuilt i2c comms (eg 18X)
hi2cout will work with any X1 and X2 PICAXE.

Don't forget to use the associated SETUP command first.

the location parameter will be a byte variable for <-16 kbit devices but a word variable for >=64 kbit devices.


you could have a write scenario akin to
Code:
Symbol location = w5
Symbol EEPROM_0 = %10100000

; save a string
location = 500

Hi2csetup  i2cmaster, EEPROM_0, i2cfast, i2cword
hi2cout location, ("H", "e", "l", "l", "o", $00)

; time to read back

location = 500
DO
hi2cin location, (b3)
inc location
pause 1000
LOOP UNTIL b3 = 0
 
Last edited:

moxhamj

New Member
Hippy, yes, square pegs and all that. None of it is as simple as A$="Hello". I guess PIC chips with their limited memory were not designed for strings. Dynamic is a bit complicated - eg
A$="Hello"
B$="World"
C$="Good morning",
B$="Fuzzy wuzzy was a bear, fuzzy wuzzy had no hair, fuzzy wuzzy wasn't very fuzzy, was he?"

There isn't room for the new B$ as the original B$ allocated only 5 bytes. So you ?delete the original 5 bytes, put the new string at the end of the table and update the allocation table. When you run out of room, go through and squish all the bytes removing the spaces left by blank strings and free up some space at the end of the table again. If all the space fills up, print a message to say it is full?

Or, put in some external chips...

That looks good westy. Ok, if we accept that strings take lots of memory (especially arrays of strings) that leads to the need for external eeprom and external ram. Both are cheap though. I presume you hang an eeprom and a ram off the same I2C pins?

Looking at the x2 (and other chips), there seem to be some pins dedicated to I2C so I suppose you use those ones.

Looks like I need to get some x2 chips and eeprom/ram chips and test this out!
 

hippy

Technical Support
Staff member
There isn't room for the new B$ as the original B$ allocated only 5 bytes. So you ?delete the original 5 bytes, put the new string at the end of the table and update the allocation table. When you run out of room, go through and squish all the bytes removing the spaces left by blank strings and free up some space at the end of the table again. If all the space fills up, print a message to say it is full?
Yes, that's the gist of it, except create the new string before deleting the old. For ...

a$ = Ucase$(a$) + UCase$(b$)

Create tmp1$ with copy of a$
Make tmp1$ upper case
Create tmp2$ with copy of b$
Make tmp2$ upper case
Create tmp3$ with tmp1$ and tmp2$ concatenated
Delete tmp1$
Delete tmp2$
Delete original a$
Set a$ to be tmp3$
 
Top