More serial stuff

fathom

New Member
I've done a search but found nothing really relevent but I'm sure I've not been looking hard enough.

So far I've managed to get my pair of 433Mhz radio modules to work properly. I started with.

TX
do
serout 2, N2400_4, ("UUU") ' preamble
pause 5 ' wait to allow rx to settle
serout 2, N2400_4, ("abc", b0) ' send the data
loop
RX
do
serin 1, N2400_4, ("abc", b0) ' receive string
high 2 'on led
pause 500
low 2
loop
This proved that my link worked and I was able to get about 100M range using a 1/4 wave whip aerial. I did get some funny looks carrying a breadboard about and managed about 100M before the data started dropping out.

I plan to use the link to send different types of data down it in this case about 8 channels of hi/lo inputs and two analogue channels to ultimately drive a pair of servos.

This is my tx code so far which is tested and working.

do
if pin0 = 1 then gosub ch1
if pin1 = 1 then gosub ch2
loop

ch1:
serout 2, N2400_4, ("UUU") 'preamble
pause 5 'pause
serout 2, N2400_4, (11, b0) 'data
return

ch2:
serout 2, N2400_4, ("UUU") 'preamble
pause 5 'pause
serout 2, N2400_4, (22, b0) 'data
return
RX
do
gosub clear 'clear o/p
serin 2, N2400_4, ("UUU"), b1 'wait for data
select case b1
case 11
gosub ch1
case 22
gosub ch2
end select
loop

ch1:
toggle 1
pause 250
return

ch2:
toggle 2
pause 250
return

clear:
low 1
low 2
This works but I would prefer to have the outputs remain high all the time the input channel remain high however due to the pauses the outputs pulse how would I achieve this?

I would also like to send the analogue information down the link too I've tried a few thing but without success. I read somewhere about using something like

serout 2, N2400_4, (b1, b2, b3, b4 etc)
I had a go but without success any ideas?
 

westaust55

Moderator
Untested but as a differnt structure try:

For TX
Code:
do
if pin0 = 1 then 
b0 = 1
ELSE IF pin1 = 1 then 
b0 = 2
ENDIF

serout 2, N2400_4, ("UUUUU") 'preamble
pause 5 'pause
serout 2, N2400_4, ("ABC", b0) 'data
loop
For RX
Code:
do
serin 2, N2400_4, ("ABC"), b1 'wait for data
gosub clear 'clear o/p
select case b1
case 1
high 1
case 2
high2
end select
loop

clear:
low 1
low 2 
return
For your last question:
Yes, you can have multiple values in one SEROUT command as you indicate. I have done three or four in the past. A few will work okay but if there are a lot in a sequence things may get out of synch and the receiver end may result in corrupt data.

I have often operated at lower baud rates if speed is not too critical. at 600 baud, I have had better results.
 

fathom

New Member
Thank you "westaust55" I had another play with the code and now the output stays high when the input button is pressed. I also slowed the baud rate down which should reduce packet dropout.

This is what it is now and this works and is tested

TX
do
if pin0 = 1 then
b0 = 1
elseif pin0 = 0 then
b0 = 0
endif
if pin0 = 1 then
b1 = 1
elseif pin0 = 0 then
b1 = 0
endif

serout 2, N600_4, ("UUUUU")
pause 5
serout 2, N600_4, ("ABC", b0,b1)
loop
For the RX
do
serin 2, N600_4, ("ABC"), b0,b1

if b0 = 1 then
high 0
elseif b0 = 0 then
low 0
endif

if b1 = 1 then
high 0
elseif b1 = 0 then
low 0
endif

loop
This does what I want it's a bit laggy as the baud rate is slower but the transmission is more reliable. Next try servo control!!
 

fathom

New Member
I've added in some code to control one servo. There is some code on the receiver side to ignore less than 75 and greater than 225 as it is outside the servo operating range. However there is quite a bit of "hunting" on the servo when the ADC value is altered on the tx side. Any ideas on how to reduce this effect?

The updated program the highlighted section is the servo control bit.

main:

if pin0 = 1 then
b0 = 1
elseif pin0 = 0 then
b0 = 0
endif

if pin1 = 1 then
b1 = 1
elseif pin1 = 0 then
b1 = 0
endif

readadc 1,b2

serout 2, N600_4, ("UUUUU")
pause 5
serout 2, N600_4, ("ABC", b0,b1,b2)

goto main
The receiver side

servo 4,75
main:
serin 2, N600_4, ("ABC"),b0,b1,b2

if b0 = 1 then
high 0
elseif b0 = 0 then
low 0
endif

if b1 = 1 then
high 1
elseif b1 = 0 then
low 1
endif

if b2 < 75 then goto main
if b2 >225 then goto main

servopos 4,b2


goto main
 

SAborn

Senior Member
Try sending and receiving the data using ASCII as it will filter a lot of the crap on the airways and give a better response.

You just need to add the # before the variable number, it also pays to send a non ASCII packet between each ascii packet to ensure the receiving end sees each start / stop bit.


Example.........

serout 2, N600_4, ("ABC", $aa,#b0,$aa,#b1,$aa,#b2)

Example.......

serin 2, N600_4, ("ABC"),#b0,#b1,#b2

Its worth a try and you might find you will get a greater distance than 100m.

Also change your preamble to something other than Ascii code like hex etc.

Example ... $aa,$aa,$aa

As you do serout twice counting your preamble, and serin once, how do you know what you receive on serin..Your preamble or the data??

It is best to have the preamble all in the 1 serout string and not in 2 like you have.
 

westaust55

Moderator
I've added in some code to control one servo. There is some code on the receiver side to ignore less than 75 and greater than 225 as it is outside the servo operating range. However there is quite a bit of "hunting" on the servo when the ADC value is altered on the tx side. Any ideas on how to reduce this effect?
Be aware that not all servo's work over the precise range from 75 to 225.
It is best to test the limits at least for each batch of like servos you purchase/use.

I have some purchased at the same time form Jaycar which operate from about 60 to 200 with a "1" variance between the servo's. For those I have, sending a value (pulse width) outside the acceptable range and the servo does nothing.
 

westaust55

Moderator
Try sending and receiving the data using ASCII as it will filter a lot of the crap on the airways and give a better response.

You just need to add the # before the variable number, it also pays to send a non ASCII packet between each ascii packet to ensure the receiving end sees each start / stop bit.


Example.........

serout 2, N600_4, ("ABC", $aa,#b0,$aa,#b1,$aa,#b2)

Example.......

serin 2, N600_4, ("ABC"),#b0,#b1,#b2

Its worth a try and you might find you will get a greater distance than 100m.

Also change your preamble to something other than Ascii code like hex etc.

Example ... $aa,$aa,$aa

As you do serout twice counting your preamble, and serin once, how do you know what you receive on serin..Your preamble or the data??

It is best to have the preamble all in the 1 serout string and not in 2 like you have.
@SABorn
If you look closely at what fathom and I have posted, the preamble at the start of each transmission is "UUUUU" to help get things in synch.
The "ABC" is just a qualifier to ignore the "garbage until the "ABC" is seen.

If you are going to use a line such as:
serout 2, N600_4, ("ABC", $aa,#b0,$aa,#b1,$aa,#b2)​

then you need more variables in the input command such as
SYMBOL garb = b13
:
:
serin 2, N600_4, ("ABC"),#b0,garb1,#b1,garb1, #b2​

IMHO, there is no need for further synching bit streams between a few consecutive actual data variables
 
Last edited:

SAborn

Senior Member
westaust55;151300If you are going to use a line such as: [INDENT said:
serout 2, N600_4, ("ABC", $aa,#b0,$aa,#b1,$aa,#b2)[/INDENT]

then you need more variables in the input command such as
SYMBOL garb = b13
:
:
serin 2, N600_4, ("ABC"),#b0,garb1,#b1,garb1, #b2​

IMHO, there is no need for further synching bit streams between a few consecutive actual data variables
Westy you are wrong, as when you use ascii for the data, the receiving end will ignore anything other than ascii data, so the $aa will be seen as garbage and disregarded.
No need for extra variables as you claim.

Its a system i have used for years for data transfere with very little corrupt data problems.

It also gave me a instant double the distance.
 

fathom

New Member
Got a working solution. I took on SABorns' advice about the
serout 5, N1200_4, ("ABC"), #b0,$88,#b1,$88,#b2,$88 etc
This does provide a lot more of a stable connection and the outputs do respond quicker than is I sent a preamble and then data. I haven't yet tried it for range though.

The transmitting side simply sets b0 high or low depending on the key press and adds this into the data stream. The ADC loads it value from 1 to 255 into b2 and also adds it into the data stream.

The TX code.

main:

if pin1 = 1 then 'if/then/else
b0 = 1 'if pin1 is high b0 = 1
elseif pin1 = 0 then 'if pin1 is low b0 = 0
b0 = 0
endif

if pin2 = 1 then 'if/then/else
b1 = 1 'if pin2 is high b1 = 1
elseif pin2 = 0 then 'if pin2 is low then b1 = 0
b1 = 0
endif

readadc 0,b2 'read ADC value into b2

serout 2, N1200_4, ("ABC", $88,#b0,$88,#b1,$88,#b2) 'serial data out

goto main
The receiving side takes the serial data stream and for the "switch" outputs it reads the state of b0 and b1 and reflects this on output 0 & 1 respectively.

The servo side I tried just loading into b2 value straight into SERVOPOS but it prompted a lot of jitter probably because the ADC value was being sent in separate parts it probably didn't give the servo enough time to settle. I had an idea that the b2 value could be stored in say b3 for a preset period of time say 2 seconds and then would be updated every 2 seconds from b2 such as a data buffer but couldn't quite figure out how to do it.

My solution was use SELECTCASE so that segments of b2 would be taken in 42 bit segments and each of those goes to a subroutine and drives the servo to that position. This is quite good as my joystick only uses 90 degrees of the travel of the pot so this will allow me to use the full travel of the servo only having a short travel of the joystick pot.

Anyway here is the code.

servo 5,150 'initialise the servo
main:
serin 2, N1200_4, ("ABC"),#b0,#b1,#b2 'read data from the TX

if b0 = 1 then 'if b0 is high then switch on
high 0 'output 0
elseif b0 = 0 then 'if b0 is low then switch off
low 0 'output 0
endif

if b1 = 1 then 'if b1 is high then switch on
high 1 'output 1
elseif b1 = 0 then 'if b1 is low then switch off
low 1 'output 1
endif

select case b2 'select case wrt b2
case 0 to 20
gosub pt
case 21 to 62
gosub pt1
case 63 to 104
gosub pt2
case 105 to 148
gosub fd
case 149 to 190
gosub sd2
case 191 to 233
gosub sd1
case 234 to 255
gosub sd
endselect

goto main

pt: 'if b2 is between 0 and 20
servopos 5,80 'set servo to left
return

pt1:
servopos 5, 100 'if b2 is between 21 and 62
return 'set servo to left + 1

pt2:
servopos 5, 125 'if b2 is between 63 and 104
return 'set servo to left + 2

fd:
servopos 5, 148 'if b2 is between 105 to 148
return 'set servo to ahead

sd2:
servopos 5, 175 'if b2 is between 149 to 190
return 'set servo to right + 2

sd1:
servopos 5, 200 'if b2 is between 191 and 233
return 'set servo to right + 1

sd:
servopos 5, 218 'if b2 is between 233 and 255
return 'set servo to right
Thanks for the help chaps.
 

geoff07

Senior Member
Sorry to preach but I have a few comments re code clarity (which always helps reduce errors). There is a bit of an art to writing good code that can be applied to Basic even though that is not the best language for clarity, however it is a very good language for Picaxe so we have to use it.

a) comments that simply repeat the code e.g. "select case b2 'select case wrt b2" don't add anything except clutter. Perhaps better to say e.g. "set servo angle according to received data" (if that is what it means).

b) using the colon delimiter can make code a lot easier to follow without changing the meaning e.g.

select [case] b2 'set servo angle according to received data
case 0 to 20 :gosub pt1
case 63 to 104 :gosub pt2
case 105 to 148 :gosub fd
case 149 to 190 :gosub sd2
case 191 to 233 :gosub sd1
case 234 to 255 :gosub sd
endselect

The [case] is redundant and possibly confusing

c) if the procedure names were meaningful as well the whole thing would read almost in English and errors would leap off the page

d) if you replace main/goto main with Do/loop then you would have no gotos, which is a desirable goal as it removes the temptation to do a quick jump back to main from an arbitrary place. When programs get large this more than anything makes them unmaintainable.

e) with a bit of careful thought you might be able to replace the entire select statement and all the gosubs with a simple lookup. You would perform an arithmetic function on the b2 value to create a simple index value and then use LOOKUP offset,(data0,data1...dataN),variable to decide what servo value to use. Not hard to do and much easier to maintain. And it might fit a smaller chip.
 

fathom

New Member
e) with a bit of careful thought you might be able to replace the entire select statement and all the gosubs with a simple lookup. You would perform an arithmetic function on the b2 value to create a simple index value and then use LOOKUP offset,(data0,data1...dataN),variable to decide what servo value to use. Not hard to do and much easier to maintain. And it might fit a smaller chip.
Thanks for the comments Geoff.

I struggle with code I always have and it doesn't come naturally and I have to really think about it. I don't really understand what LOOKUP is about and the manual assumes you already know. Could you clarify what you said, I would love to understand how to use PICAXE basic more efficiently but the above quote doesn't help either....
 

geoff07

Senior Member
The idea is to make the computer do the work. Plus, using an approach like this means that if it works for one case it works for all, unlike when individual cases are spelled out and each could have its own error.

What you want to create is a number between 0 and 5, that selects a second number from a list and puts it in a variable which then controls the servo.

Starting with

select [case] b2 'set servo angle according to received data
case 0 to 20 :gosub pt1
case 63 to 104 :gosub pt2 ' 41 interval
case 105 to 148 :gosub fd '43
case 149 to 190 :gosub sd2 '41
case 191 to 233 :gosub sd1 '42
case 234 to 255 :gosub sd '21
endselect

you want an expression which is 0 if b2 is between 0 and 20, 1 if b2 is between 63 and 104, etc. You can do this by the following pseudo-code:

a) check if below 21, if so output is 0
b) else subtract 20, then divide by 42, and add 1 (to round up). The answer is the number of 42s in the no and the fractional part is lost due to integer arithmetic
c) use that output to look up the servo data

sample code:

symbol B_temp = b? 'temporary byte
symbol B_servpos = b?? 'servo position

if b0 <21 then : B_temp = 0 'remove the 20 offset
else : B_temp = B_temp - 20: B_temp = B_temp / 42 :inc B_temp 'calculate position
endif
lookup B_temp,(80,100,125,148,175,200,218),B_servpos 'convert to servo data
servpos, 5,B_servpos 'actuate servo

These three statements replace 38 in your original - assuming I got it right, but you can check that out if you want, I haven't tested it. I'm not sure your intervals are entirely correct though. You might want to check for error conditions (out of range, corrupt data etc.). This is one of the ways in which Picaxe Basic is very powerful.

I'm not sure what your $88s do, this is async comms where each character has its own start and stop bits. Once the preamble has been found so it can start reading from the right place it should work fine. The radio (if it is a superhet or a super-regen) has nothing in it that can 'lock' onto a pulse train - it just passes the level of the carrier to the serin async character reader, which then acts on the start bit. It does help to ensure that the level is the right way up (high or low according to whether the tx is idling high or low). It would be interesting to see some experimental data about link performance. My experience is that corrupt data through interference is the main problem, for which a checksum might be helpful.

My version uses 38 bytes instead of 164!

Happy programming!
 

fathom

New Member
I'm not sure what your $88s do, this is async comms where each character has its own start and stop bits. Once the preamble has been found so it can start reading from the right place it should work fine. The radio (if it is a superhet or a super-regen) has nothing in it that can 'lock' onto a pulse train - it just passes the level of the carrier to the serin async character reader, which then acts on the start bit. It does help to ensure that the level is the right way up (high or low according to whether the tx is idling high or low). It would be interesting to see some experimental data about link performance. My experience is that corrupt data through interference is the main problem, for which a checksum might be help
If you just pull the data pin high on the TX module on the RX module you just get a high output for a few seconds before it decays away even though the TX is still transmitting so ultimately you can only really send a small packet of data. Also I get a lot of interference from other household items also using 433MHz unfortunately there isn't much you can do about this. Initially a preamble was sent to the RX to "wake up" before the actual data was sent but from SAborn he suggested using a hex code ($88) between each ascii code to act as a rolling preamble to "wake up" the receiver so that data transmission is more reliable as the receiver wont be falling off as the data required is being sent. Since the hex code isn't read its ignored but the relevant ascii code is. This is discussed in previous answers.

We do something similar at work with our secure voice systems where we send a preamble, usually a few tones through the link before voice transmission, not only does this give this audio feedback that the link is secure but it also gives the clocks on the TX/RX's a chance to synchronise properly. Otherwise if the preamble is switched off you lose the first second or so of voice. Here by sending the $88 does the same thing but since these are cheap eBay 433MHz modules and not a robust RF link it requires a little bit of an arse kicking to keep it in check.

I'll have a play with the lookup code later as I really need to work it out on paper first so I can follow the line of how it works.
 

hippy

Technical Support
Staff member
There are two issues with RF serial, getting the receiver conditioned so it can receive the data sent and synchronising to the actual data.

I've previously likened the preamble to the master of ceremonies ringing a bell to get guests to quieten down prior to someone speaking, the qualifier being an announcement of who is speaking to get people to pay attention to what then arrives.

When data isn't being transmitted the receiver drifts about, picks up noise, the "U" preamble mutes that noise and SERIN will be throwing away any junk it has received because it won't match the qualifier. That in itself won't synchronise what SERIN is receiving so there's usually a pause after the preamble so the SERIN will clear any data it erroneously received before the real qualifiers arrive.

While receiving SEROUT sent data the receiver will usually see an imbalance of 0's and 1's and longer runs of 0's and 1's than is ideal for keeping the receiver conditioned, after some time the conditioning deteriorates, more noise can creep in and data become corrupted. Adding additional "U"'s within the data stream provides alternating 0's and 1's which brings conditioning back.

To ensure synchronisation of SERIN, that it reads data bytes from when their start bits appear, one can add additional pauses after sending additional "U" preamble just as we did initially.

How sending $88 / $AA / "U" helps achieve synchronisation is a little difficult to analyse, the byte will be read by SERIN to determine the end of the #b0 data etc. I wouldn't have expected it to achieve what appears to be its desired effect.

Key to using SERIN and SEROUT over RF is conditioning, synchronising, then sending data quickly enough that the receiver doesn't have time to lose conditioning, so sending raw bytes rather than numeric bytes would normally seem to be better as far fewer bytes would be sent. What's actually going on in any particular case would involve looking at the actual data stream and a lot of analysis.

On paper using SEROUT and SERIN and dumb RF modules isn't a good idea but a mechanism has been found which makes it good enough in many cases and various tricks can be found to make it slightly better. It is however all a bit hit and miss, what works in one case may not work in another, and why something works or doesn't can be hard to fathom without a lot of analysis.

To take all the quesswork out of it we have introduced the AXE213 and associated products. The NKM2401 packages bytes up and sends them over the air with optimal preamble, qualifiers, encoding and additional checksum to ensure that data received is what was sent without having to worry about the complexity of doing it oneself in PICAXE code. No one has to use the NKM2401 or AXE213 but it will most likely perform better than SEROUT and SERIN code will and is much easier to use.
 
Last edited:

geoff07

Senior Member
the NKM2401 sounds interesting and worth a close look.

I think there is a terminology thing here also. To me the receiver (rx) is simply the dumb radio chip that turns a carrier wave into a voltage, and has no knowledge of what it means. I'm using RR3-433s, there are many others. There is not much in such a circuit that can be 'conditioned' - just a fixed oscillator and a filter - either there is a signal or there isn't. I don't think mine even have agc (automatic gain control). Same with the associated tx, it is simply an oscillator and sends a carrier if there is a voltage on a pin, and silence otherwise.

All the magic goes on in the software UART inside the Picaxe that is feeding the serin command. I wouldn't have called this a receiver, though is is certainly receiving digital input. Obviously this has to train itself to spot the start and stop bits and decode the data bits.

This asynchronous approach is pretty basic as hippy says and it is for cases where async was inadequate that Manchester coding and other techniques were developed. Though unlike async, the resulting synchronous transmission is sending all the time and therefore is a much higher energy and spectrum hog so needs to be used appropriately. If it isn't sending all the time i.e. you use tx-enable, then for sure it will need some time to resync when the signal begins again and it looks for its own start/stop code.
 

fathom

New Member
Code done again with a lookup table on the RX side, had to fiddle with the maths a little to make it work properly but now I understand how lookup tables work, thank you Geoff.

TX side is much the same but with : added which makes it a bit more readable.

symbol ch1 = b0
symbol ch2 = b1
symbol joy1 = b2

do

if pin1 = 1 then:ch1 = 1 'if pin1 is high then ch1 is 1
elseif pin1 = 0 then:ch1 = 0 'if pin1 is low then ch1 is 0
endif

if pin2 = 1 then:ch2 = 1 'if pin2 is high then ch2 is 1
elseif pin2 = 0 then:ch2 = 0 'if pin2 is low then ch2 is 0
endif

readadc 0,joy1 'put ADC value into joy1

serout 2, N600_4, ("ABC" ,$88,#ch1,$88,#ch2,$88,#joy1) 'output data serially

loop
The RX side with about a third of the amount of code!

symbol ch1 = b0
symbol ch2 = b1
symbol joy1 = b2
symbol serv1 = b3

servo 5,150 'initialise the servo

do

serin 2, N600_4, ("ABC"),#ch1,#ch2,#joy1 'read data from the TX

if ch1 = 1 then:high 0 'if ch1 high then output 1 is high
elseif ch1 = 0 then:low 0 'if ch1 low then output 1 is low
endif

if ch2 = 1 then:high 1 'if ch2 high then output 1 is high
elseif ch2 = 0 then:low 1 'if ch2 low then output 1 is low
endif

if joy1 < 36 then:joy1 = 0 'if joy1 is less then 36 make joy1 = 0
else joy1 = joy1 / 42:inc joy1 'if greater joy1 / 7 and inc result + 1
endif

'1 2 3 4 5 6 7 joy1 result 1 - 7
lookup joy1,(80,100,125,148,175,200,218), serv1 'update serv1 result
servopos 5, serv1 'serv1 to position servo

loop
 

hippy

Technical Support
Staff member
I'm no expert in RF but it seems most receiver modules are doing a fair bit just from looking at the PCB - see the image at the top of the datasheet for the receiver in the RFA001 pair ...

http://www.rev-ed.co.uk/docs/RFA001_RX.pdf

I believe the conditioning for the receiver is to do with data-slicing which is used to accurately detect the on-off transitions of the transmitter which in comparison is usually a more simpler device, an oscillator, either on or off. "Conditioning" probably isn't the correct technical term. There's some details in the following Maxim web page but I won't pretend to understand it all, and I expect that's mostly done by the IC which is usually on the RF receiver module ...

http://www.maxim-ic.com/app-notes/index.mvp/id/3671

In essence though RF modules are indeed very dumb; the transmitter oscillates or doesn't, the receiver turns what's received into an oscillation present or not signal.

SERIN is also completely dumb. It sits there waiting for a signal which looks like a start bit ( for Nxxxx baud, a rising edge ), then uses timing to determine when the mid-point of 8 data bits would fall and turns the input signal high or low at that time into a byte.

There's an extra wrapper of qualifiers around that but that simply checks each byte received is the one which is expected, if so advance, otherwise start looking again.

All-in there's not much that's smart going on. The cleverness was really from the people who first worked out what needs to happens to make it all work by way of PICAXE code mainly on the transmitting side. Rev-Ed had done some earlier RF work upon which some ideas were based plus Tarzan and Greg Newton were involved in the early days that I'm aware of while others contributed and refined the ideas ( which is still ongoing as we see here ); apologies for not being able to name them all. Manuka has probably been the most vocal and supportive advocate of 433MHz and similar and a driving force for using 433MHz with PICAXE, so special merit badge there ( to go with the one for services to the breadboard industry ).

On a separate point - One thing to bear in mind if RF data transfer is sluggish it could be an issue of timing; SERIN starting after the sending SEROUT has. In that case it would take two transmission for the receiver to get one packet received. The sender should not send before the receiver is ready to receive what's sent. You can test that by sending an incrementing value. If the received data increments by two rather than one the transmitter is sending too soon or the receiver is taking too long to be ready.
 
Top