Problem with SERIN and unknown length data

Memran

Member
I am attempting to get two M2's to communicate via serout and serin. But I am having trouble finding a neat solution for having the receiver process inputs of varying lengths. I am using a 16x2 LCD for outputting the data. LCD code is taken from the interfacing circuits document and works fine (so I've left that out of this code listing).

Sender:
Code:
main:
	serout B.0,N2400,(5,"Hello")	
	pause 1000
	serout B.0,N2400,(7,"Goodbye")	
	pause 1000
	goto main
Receiver:
Code:
'initialise
gosub init

'clear
let b1 = 1
gosub wrins

'hide cursor
let b1 = 12
gosub wrins

'display some text to confirm screen works
eeprom 0,("Hello World! :D")
for b3 = 0 to 14
	read b3,b1
	gosub wrchr
next b3

'go to 2nd line
let b1 = 192
gosub wrins

start: 	
	'read length
	serin C.7,N2400,b3
	pause 10
	for b4 = 1 to b3
		'get next character
		serin C.7,N2400,b4
		pause 10
		let b1 = b4
		'write to display
		gosub wrchr
	next b4

	goto start
This does not seem to be the way to do this!

The output from the screen is:
Code:
Hello World! :D
;;;;;;;;;;;;;;;;
Each ";" appears every second, so there is some interaction between the two pics, but it is not doing what I want.

Your help is greatly appreciated! :D
 

nick12ab

Senior Member
How PC know if a PICAXE is conencted

The SERIN command is infamous for its problems - use a PICAXE which supports background serial receive or add delays between each byte sent by the sender. By the time that 'pause 10' has executed on the receiver, most of the bytes have already een lost.
 

Memran

Member
Thanks for the reply!

Adding delays between bytes helped, but I also had to buffer the incoming data.
Here's what I have now:

Here is the new sending code.
Code:
'sends data to other pic
'data structure is [1,2],data...,0
'first byte is either 1, meaning character or 2 meaning instruction for the LCD.
'data follows
'data is terminated by a 0
main:
	'goto start of second line
	write 0,2,192,0
	gosub send

	'store data in memory then send
	write 0,1,"H","e","l","l","o"," "," ",0
	gosub send

	'move curse left two places
	write 0,2,16,16,0    'don't need to do this, but I wanted to see it working
	gosub send
	pause 1000
	
	'goto start of second line
	write 0,2,192,0
	gosub send

	'store data in memory then send
	write 0,1,"G","o","o","d","b","y","e",0
	gosub send	
	pause 1000
	
	goto main
	
send:
	'reset pointer
	let b0 = 0
sendChr:
	'read the data from memory
	read b0,b1
	'send byte
	serout B.0,N2400,(b1)
	'check if we just sent the terminate byte (0)
	if b1 = 0 then
		goto finished
	endif
	'give time to send
	pause 10
	'increment pointer
	let b0 = b0 + 1
	goto sendChr
finished:
	'give time to process data before any
	'other data is sent
	pause 100
	return
This is the new main loop of the receiver. It stores the bytes in memory until it gets a 0, and then it processes it.
Code:
'reset pointer
let b3 = 0
start: 	
	'read byte
	serin C.7,N2400,b4
	
	'check byte for zero value (meaning end of message)
	'and make sure message is not longer than 16 bytes
	if b4 != 0 and b3 < 16 then
		write b3,b4'store value
		let b3 = b3 + 1'increment pointer
	else
		'either end-of-message, or message too long
		
		'first byte is message type (character or instruction)
		read 0,b6
		'don't overrun the buffer!
		let b3 = b3 - 1
		
		'read remaining bytes
		for b5 = 1 to b3
			read b5,b1
			if b6 = 1 then
				'character mode
				gosub wrchr
			else
				'instruction mode
				gosub wrins
			endif
		next
		'reset pointer
		let b3 = 0
	endif

	goto start
As you can see, this is not a very clean solution, but it works!
 

geoff07

Senior Member
A few things to consider:

- sending the number of bytes as the first data byte
- using serin with a qualifier (see the manual) to help synchronise the two ends (a timeout may be necessary in case data is not received)
- sending each byte with a qualifier and a serial number
- upping the data rate so the devices have more time for other things
- using an interrupt to detect the arriving character and then reading that character

serial comms can be done quite reliably, but Picaxe isn't great for strings, so some form of byte count is probably necessary to keep order.
 

westaust55

Moderator
If it is a radio link then qualifiers and a preamble are required. But not for a hard wired conenction.

With the first code, the biggest issue is the need for a time delay between the first byte holding the following number of characters and the actual subsequent bytes.
Time is needed for the PICAXE to received the first byte and set up the loop parameters for the subsequent bytes.
The fact that the program then also loops around for each subsequent byte a further delay is required.
You just need to remember that each PICAXE command takes a finite time duration to execute.
 

Technical

Technical Support
Staff member
Have look at using peek/poke into RAM (instead of read/write into EEPROM), and then when you understand this you can then move on to using bptr as your pointer address (instead of b3) and use @bptr/@bptrinc to save your values directly into RAM within the serin command. This will make your system much faster.

e.g.
bptr = X
serin C.7,N2400,@bptr
 

Memran

Member
I think I understand pointers now, thanks!

This is what my main block looks like now:

Code:
'reset pointer
let bptr = 80
main: 	
	'read byte
	serin C.7,N2400,@bptr
	
	'check byte for zero value (meaning end of message)
	'and make sure message is not longer than 16 bytes
	if @bptr != 0 and bptr < 96 then
		'increment pointer
		let bptr = bptr + 1
	else
		'either end-of-message, or message too long
		
		'keep track of message length
		let b3 = bptr
		'reset pointer for reading from start
		let bptr = 80
		'first byte is message type (character or instruction)
		let b6 = @bptrinc
		
		'read remaining bytes
	readNext:
		let b1 = @bptrinc
		if b6 = 1 then
			'character mode
			gosub wrchr
		else
			'instruction mode
			gosub wrins
		endif
		if bptr >= b3 then
			'reached end of message
			goto endRead
		endif
		goto readNext
	endRead:
		'reset pointer
		let bptr = 80
	endif

	goto main
This does appear to be faster, as I can now remove the pause 10 which I had between each byte in the sending code, however I am still not able to do this:
Code:
serout B.0,N2400,(1,"Serial rocks!")
 

nick12ab

Senior Member
That's odd... looking at the title for my first post in this thread doesn't seem to have anything to do with this thread and I don't remember typing it in... There's a new thread by someone else with a very similar title:
How PC know if a Picaxe is conected?. The title of mine shown below for direct comparison:
How PC know if a PICAXE is conencted. both share the same 'question mark' icon.

The same 'pidgin english' is present in both except in mine 'conected' is spelt even more wrong so the OP of the other thread might have noticed his mistake and somewhat connected it before the 'timeout thingy' ends (the timeout thingy allows small edits to be done quickly after the posting without the 'Last Edited By...' thing appearing).

Just as a test - I'm using Quick Reply to type this so I haven't entered a title or chosen an icon so the icon should be the default post one and no title.
 

hippy

Technical Support
Staff member
The issue is that you are still doing far too much between receiving characters and being ready for receiving the next and that's simply taking too long.

Use of '@bptr' is IMO a red herring as ideally you want to be dealing with one byte at a time so you can simply keep that in a byte variable, read it in, determine what it is, and output it to the LCD or do something else.

To increase data handling throughput ( which is the problem here ) you want to run at fastest speed and you will need to highly optimise updating the LCD to make the entire process as fast as possible. I believe that Ron Hackett shows how to do that in his book; "PICAXE Microcontroller Projects for the Evil Genius".
 

Memran

Member
Yes it really does seem that speed is the issue!
I have something which sort of does what I want, but its a nasty way to do it!

Above my new main method I've set up some parameters to be used:
Code:
'set up mode symbol
symbol mode = b3
let mode = 1 'character mode

'set up baud
symbol baud = b4
let baud = N2400

'set up timeout
symbol timeout = b5
let timeout = 50
This does not work: (it results in strange characters on screen)
Code:
main:
	let bptr = 80
	for b6 = 0 to 20
		serin [timeout,endread],C.7,baud,@bptrinc
	next b6
But this horrible method does what I want:
Code:
main:
	let bptr = 80
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
Then the processing is done in endread as follows:
Code:
endread:
	'set 0 at current pointer to represent end of message
	let @bptr = 0 
	
	let bptr = 80
chrread:	
	let b1 = @bptrinc
	select case b1
	case 0
		'end of message
		goto main
	case 1
		let mode = 1 'character mode
	case 2
		let mode = 2 'instruction mode
	else
		if mode = 1 then
			gosub wrchr
		else
			gosub wrins
		endif
	endselect
	goto chrread
I am surprised that the for loop is so expensive compared to consecutive calls of SERIN. There surely must be a better way than this! :D
 

nick12ab

Senior Member
I am surprised that the for loop is so expensive compared to consecutive calls of SERIN. There surely must be a better way than this! :D
ANY command on a PICAXE is expensive when it comes to being fast. I'd suggest you upgrade to a 20X2 with hardware serial.
 

Memran

Member
Another issue! :(

I just put the same sending code on an 08M2 (instead of a 20M2) and now I get garbage again. The receiving pic has not changed.
 

Memran

Member
This is the code which works on my sender (20M2) and which the receiver (20M2) reads properly with it's latest code: :)
Code:
'sends data to other pic
'254 sets instruction mode
'255 sets character mode
main:
	serout B.0,N2400,(254,1,255,"Hello World!  :D")
	serout B.0,N2400,(254,192,255,"Serial rocks! :D")
	pause 2000
	
	serout B.0,N2400,(254,1,255,"Goodbye World!:(")
	serout B.0,N2400,(254,192,255,"This is slow  :(")
	pause 2000
	
	goto main
However, putting this on an 08M2, (Altering out pin C.1 or C.2 or C.4) results in garbage output to the LCD. I don't know why.
 
Last edited:

nick12ab

Senior Member
By 'garbage characters', I assume you mean Japanese characters? May I suggest that you try the 'CALIBFREQ' command or another 08M2.
 

Goeytex

Senior Member
The proper syntax of the serout command is N2400_X where x is the processor speed. See Manual 2 page 208.
You have left out the X. Not sure what it will default to if omitted.

I believe it to be a good practice to use setfeq at the begining of every program
and of course the the proper syntax for serout.
 

westaust55

Moderator
The proper syntax of the serout command is N2400_X where x is the processor speed. See Manual 2 page 208.
You have left out the X. Not sure what it will default to if omitted.

I believe it to be a good practice to use setfeq at the begining of every program
and of course the the proper syntax for serout.
SERIN and SEROUT commands without the _<freq> subscript to the baudrate parameter defaults to the value for the default clock speed for the particular PICAXE chip.
Default clock speed is 4 MHz on all but the X2 parts.

Thus on an M2 part, N2400 is the same as N2400_4
 

westaust55

Moderator
I am surprised that the for loop is so expensive compared to consecutive calls of SERIN. There surely must be a better way than this! :D
At 2400 baudrate, 1 byte comprising 1 start, 8 data and 1 stop bit takes (10*1000/2400 = ) 4.1 ms
Each bit takes 0.41 ms
The average time for each PICAXE command is 0.25 ms. (Keep in mind these are just typical time duration values)

So in simplistic timing terms having several SERIN commands in a row each has an overhead of 0.25 ms to set up and finally store the data plus the time to receive the data.
There is also a further small time increment while the ptr variable is increments. Without doing timing tests that equates to approx 4.5ms per program line.
The sending program has the same timing.

From some past testing I did on command timing, the GOTO command on an 18M2 to approx four times the duration required for other non M2 parts.
Thus we may be incurring 0.8 ms rather than 0.25 ms for the return to the start of the loop plus the 0.25 ms to

If you now put that SERIN command in a loop, once the loops is established, your receive program reads the first data as above, but now the program must:
Increment the index/counter - say 0.25 ms
Jump back to the start of the loop - say 0.8 ms for M2 parts (compared with 0.25 to 0.4 ms for other parts)

Hence receiving bytes in a loop can incur sufficient delay to miss data being sent.

Try putting a PAUSE 1 or PAUSE 2 (at 4 MHz clock speed) after each SEROUT in the sending code and see if that helps.
If you wish to know more about the timing for various commands and the differences between various PICAXE chips then you could have a read at some details I posted in the past here: http://www.picaxeforum.co.uk/showthread.php?17782
 
Last edited:

manuka

Senior Member
Well said sir! Which "M2" is this ? I confess to not (YET!) needing the enhanced M2 serial, but at first glance it may be worth perhaps trialling an increase in both PICAXEs clocks (from 4 MHz), & lowering their baud rate accordingly. That way the bits travel between suitably more responsive PICAXEs,with "time on their hands" to juggle the data.
 
Last edited:

Goeytex

Senior Member
SERIN and SEROUT commands without the _<freq> subscript to the baudrate parameter defaults to the value for the default clock speed for the particular PICAXE chip.
Default clock speed is 4 MHz on all but the X2 parts.

Thus on an M2 part, N2400 is the same as N2400_4
Thanks,

Concern was for more for readability than anything, as it is common for some asking for help to post incomplete code. With setfreq at the beginning of the program and the freq subscript on the serin command there is no guessing involved.

As far as calibfreq goes. In the last 18 months , I have shipped over 200 boards that use Picaxe chips, and not a single one has ever needed calibfreq to get serial comm to work.

When troubleshooting problems such as was indicated, I tend to first look at what is most likely before what is remotely possible. In this case I think most likely is user related.
 
Last edited:

Memran

Member
Thanks for all replies so far!

It is very interesting about the timings of every instruction. I did not realise just how long some of these instructions can take.

Reading someone else's code can certainly be harder than it needs to be if chunks of code are left out so here are both full listings (with setfreq and baud MHz added):

Sender:
Code:
'initialise frequency
setfreq m4

'set up baud
symbol baud = b4
let baud = N2400_4

'sends data to other pic
'254 sets instruction mode
'255 sets character mode
'1 clears LCD and moves cursor to start of first line
'192 moves cursor to start of second line
main:
	serout B.0,baud,(254,1,255,"Hello World!  :D")
	serout B.0,baud,(254,192,255,"Serial rocks! :D")
	pause 2000
	
	serout B.0,baud,(254,1,255,"Goodbye World!:(")
	serout B.0,baud,(254,192,255,"This is slow  :(")
	pause 2000
	
	goto main
Receiver:
Code:
'initialise
setfreq m4
gosub init

'clear
let b1 = 1
gosub wrins

'set up mode symbol
symbol mode = b3
let mode = 1 'character mode

'set up baud
symbol baud = b4
let baud = N2400_4

'set up timeout
symbol timeout = b5
let timeout = 50

main:
	'receive up to 47 bytes from serial
	'HORRIBLE way of doing it!!!
	let bptr = 80
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'5
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'10
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'15
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'20
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'25
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'30
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'35
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'40
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc'45
	serin [timeout,endread],C.7,baud,@bptrinc
	serin [timeout,endread],C.7,baud,@bptrinc
	
endread:
	'set 0 at current pointer to be checked later
	let @bptr = 0
	'reset pointer for reading
	let bptr = 80
chrread:	
	'get next byte
	let b1 = @bptrinc
	select case b1
	case 0
		'end of message
		goto main
	case 255
		'character mode
		let mode = 1
	case 254
		'instruction mode
		let mode = 2
	else
		'send to LCD
		if mode = 1 then
			gosub wrchr
		else
			gosub wrins
		endif
	endselect
	goto chrread	
	
init: 
	let outpinsB = 0 ' Clear all output lines
	let dirsB = 63 ' Set outpinsB 0-5 as output lines (Stamp only).
	pause 200 ' Wait 200 ms for LCD to reset.
	let outpinsB = 12 ' Set to 8-bit operation.
	pulsout 1,1 ' Send data by pulsing 'enable’
	pause 10 ' Wait 10 ms
	pulsout 1,1 ' Send data again
	pulsout 1,1 ' Send data again
	let outpinsB = 8 ' Set to 4-bit operation.
	pulsout 1,1 ' Send data.
	pulsout 1,1 ' Send data again.
	let outpinsB = 32 ' Set to two line operation
	pulsout 1,1 ' Send data.
	let b1 = 14 ' Screen on, cursor on instruction
	gosub wrins ' Write instruction to LCD
	return

wrchr:
	let b2 = b1 & 240 'mask half of byte
	let b2 = b2 / 4	'shift 2 pins left
	let outpinsB = b2	'output
	high 0		'make sure character mode
	pulsout 1,1		'send
	
	let b2 = b1 & 15	'mask other half of byte
	let b2 = b2 * 4	'shift 2 pins right
	let outpinsB = b2
	high 0
	pulsout 1,1
	
	return

wrins:	
	let b2 = b1 & 240
	let b2 = b2 / 4
	let outpinsB = b2
	low 0			'make sure instruction mode
	pulsout 1,1
	
	let b2 = b1 & 15
	let b2 = b2 * 4
	let outpinsB = b2
	low 0
	pulsout 1,1
	
	return
When both sender and receiver are 20M2's the code works as expected.
If the sender is an 08M2, I get strange characters, random screen blanking and blacking out etc. Following some advice here, if I then change baud to N600_4 on both (20M2 receiver and 08M2 sender) I get the desired results again.

Code:
for 20M2 receiver and 08M2 sender:
sender			receiver
freq	baud		freq	baud		works		
m4	N600_4		m4	N600_4		yes
m4	N1200_4		m4	N1200_4		yes
m4	N2400_4		m4	N2400_4		no	(works if both are 20M2's)
m4	N2400_4		m8	N2400_8		yes
 :	    :		 :	    :
m32	N9600_32	m32	N9600_32	yes
Generally, this makes sense; faster baud requires the processing code to run faster to "keep up". It still does not answer why N2400_4 works on 20M2+20M2 but not 08M2+20M2. To test one of the earlier suggestions, I have used a different 08M2, but this still gives the same results.

I think I can conclude that my code running at 4Mhz can barely handle data incoming at 2400 baud. The 08M2's 2400 baud seems to be a little faster then the 20M2's 2400, and so this is the point where things go wrong. If the frequency on the receiver is increased, or the baud decreased, things work much more smoothly. :)

By selecting 32MHz, N4800 baud on the receiver (slowest baud supported at 32MHz), all M2 pics can talk to it at any frequency (4,8,16,32) at 4800 baud. Essentially, the receiver pic is doing what the LCD firmware IC does! :D

A great learning experience! :D
 
Top