serin serout and words

Mark.R

Member
Im just starting to have a play (as they say) with sending some data from Words in one picaxe to words in another, is this possible. From what I've researched so far you can't but you can send bytes...so...

From my understanding you cant have

serout <pin>, <baud>, (w0, w1, w2)

but you can have

serout <pin>, <baud>, (b0, b1, b2, b3, b4, b5)

is this correct, if so how would you get these bytes back into words once transmitted to the second picaxe using something like

serin <pin>, <baud>, b0, b1, b2, b3, b4, b5

can anyone help or point me to a further thread.

Thanks
 

Mark.R

Member
Actually now I've had time to think about it I'm probably being a bit dense. If serin has clocked data into b0 & b1 say, that would already have put the data into w0......right??
 

cpedw

Senior Member
Yes, right. You don't even need to know whether b0 is the low end or the high end of w0. It gets into the correct place provided you don't alter the order b0, b1, b2, b3, b4, b5 between serout and serin.
 

Mark.R

Member
If I send b0, b1, b2, b3, b4, b5 can I then receive them into say b20, b21, b22, b23, b24, b25 or do they have to match between serout & serin?
 

inglewoodpete

Senior Member
If I send b0, b1, b2, b3, b4, b5 can I then receive them into say b20, b21, b22, b23, b24, b25 or do they have to match between serout & serin?
Asynch serial data transmission is typically an 8-bit protocol: hence sending bytes is by far the most commonly supported way of transporting data using this medium.

Once a byte is transmitted on a wire it is just a byte on a wire(!) and the receiving end must know what speed the data is being sent at and what the received data means. Is the data a byte integer (0-255) or is it an ASCII character? If it is text, is it English text or another language? So the receiver must "know" what the data represents and how it is to be reassembled. The data might be transmitted by a PICAXE but the receiver could a PC, MAC, Raspberry Pi or another PICAXE (or a hundred other devices). The received data can go into any register or array etc. That's for you, the programmer to decide.
 

Mark.R

Member
Just to confirm I can now send the three words w10, w11 & w12 as b20, b21, b22, b23, b24, b25 and receive them into b0, b1, b2, b3, b4, b5 for w0, w1 & w2 and the data comes across a treat.
 

Mark.R

Member
If I've got a program that uses words/bytes up to b39 (w19) but only using 9 I/O pins am I stuck with using a larger chip than needed say 20X2 as the 18M2/20M2 only goes up to b27 (w13)

Code attached due to limit is from the wood burner part of a heating controller I'm designing. This dose all work though always room for improvement, just wondering if there's away of making the word/byte usage count smaller? Any other comments appreciated, I've tried to comment and lay this one out better than other versions in other posts.
 

Attachments

AllyCat

Senior Member
Hi,

IMHO that should EASILY fit into an M2, probably a 14M2. You've "overlooked" the six "spare" System Words in the M2s (SW_1 to SW_6) which immediately get you to the equivalent of 38 bytes, but much more can be saved or re-allocated. Personally I always reserve the first few bytes or words for "GeneralPurpose " use, sometimes even for "flags".

You don't appear to have used the Subroutine(s) and those variables very efficiently. Note that * 2 / 512 is equivalent to / 256 (except that it deletes the top bit), which means the result will fit into a single byte. I'm not sure why there are 2 subroutines for sensor 1, but a "snippet" from the code might be:
Code:
symbol flags = b0    ; If required
symbol tempb = b1
symbol tempw = w1
symbol tempwA = w2
symbol temp2C = SW_2     ; Similar for sensors 1 and 3
symbol cold2 = b22          ; Similar for sensors 1 and 3
; .......
Read_Sensor_2:           ; SENSOR No.2 (WATER FLOW TEMPERATURE)
   Low CS_2                ; Select MAX31855 No.2 to start conversation
   gosub ReadSensors
   cold2 = tempb
   temp2C = tempw
   High CS_2
; ......
ReadSensors:
  tempw = 0                                  ; Clear variable ready for data
  for COUNTX = 0 to 15                ; BITS transfer 31 to 16 MSB first
     tempw = tempw * 2 + MISO      ; Shift << 1 and write bit data
     pulsout SCK, 1                              ; Pulse Clock
   next COUNTX
   tempwA = 0                                   ; Clear variable ready for data           
   for COUNTX = 0 to 15                   ; Transfer BITS 15 to 0 MSB First [was compt]
      tempwA = tempwA * 2 + MISO    ; Shift << 1 and write bit data
      pulsout SCK, 1                           ; Pulse Clock
   next COUNTX
   tempb = tempw * 2 / 512                 ; Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
   tempw = tempwA * 2 / 32              ; Thermocouple temp convertion to deg C (shifts: << 1 and >> 5)
return
Or possibly you only need half of that ReadSensors subroutine, which you call twice (with different divisors and assigments in the higher level subroutine), and avoid the need for tempwA.

Cheers, Alan.
 
Last edited:

inglewoodpete

Senior Member
I have had applications that have required far more variables than the named bxx and wxx ones.
  • Good structuring of your code is essential: use subroutines to separate out repeated blocks of code and functional blocks of code.
  • This will then allow you to identify registers that are used temporarily within these subroutines and reuse them (even by giving them different symbol names if you use symbols for them).
  • Use a letterbox system (Poke and Peek) for storing the contents or registers that are not in constant use. Poke them into specific RAM locations, freeing up registers for reuse. Then when the values are needed again, usually in a subroutine, Peek them back (and Poke them away if you modify their value/s).
If done well, I expect you will have unused registers at the end of the exercise :) .

Edit - You could easily recover 8 word variables (w4 to w11) using the Poke and Peek method and use a two-level subroutine structure to do the calculations. BTW, I'd be using more descriptive symbols for the variables, too: CJ_Val; TC_Val; CJ_TempC; TC_TempC - much less confusing when you revisit the code next week or next year.
 
Last edited:

Mark.R

Member
Thank you for the feedback guys.

Alan, your right about the having the two subroutines for Sensor 1, now I've gone back and looked I realise that I don't need it as the program will always "return" from where it came from where ever in the program that might be, was having an inexperienced dense moment again! I will sort this.

With ref to the M2 system words I cant seem to find reference to these in the M2 manual?

--------

Pete, this peaking and poking is a bit over my head at the moment but will look into it and do some more reading and I take on board your comment about the symbol naming.
 
Last edited:

AllyCat

Senior Member
Hi,

Yes, as Lance said, Manual/Section 2, at about page 12 or 16 (depending on version):

Variables - System
The M2 parts have 8 word variables which are reserved for system hardware use.
However if that piece of system hardware is not used within a program the
variables may be used as general purpose variables.


In most cases only SW_7, the time variable (another often-overlooked feature), is used by the "system" in M2s.

The PEEK and POKE commands give access to many more bytes of "RAM" (almost 500 in most M2s,) as does the @bptr variable. The bptr and @bptr concept is most useful, but a little more difficult to understand.

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

No. And (at least with PE5) I have encountered a few "bugs" when assigning byte variables to the SW_n variables.
So they're best reserved for variables that need to have simple Word values.
After all, there are almost 500 byte variables accessible via POKE/PEEK and @bptr in most M2s (or around 100 in the 08M2).

Cheers, Alan.
 

Aries

New Member
I started my computing career with the Univac 1108, using assembler language. There, we had the concept of registers (which could do arithmetic etc) and locations or addresses (where you could store and recover data). In Picaxe, there is a similar concept - the word (w0, etc) and byte (b0, etc) variables can do arithmetic (so they are like registers), and the other locations (up to address 511 in the case of the M2) can store values. If you want to do arithmetic on values in locations, you first load them into registers, then do the arithmetic and then store the result wherever you want to. For values that are being used frequently, you may want to reserve a register, but it is never a requirement to do so. Therefore, if you find yourself running out of registers, put the less-frequently-used values into other locations until you need them. For example:
Code:
peek, MyLocation1,b0 ' load the value in MyLocation1 (an address in the range up to 511) into register b0
b0 = b0 * 2 + 1   ' do arithmetic on it
poke, MyLocation2,b0 ' store the value of b0 into MyLocation2 (an address in the range up to 511)
MyLocation1 and MyLocation2 may be the same location (if you want to change the value which is stored there) or different locations (if you want to put the result somewhere else).

bptr is a special Picaxe variable whose value is the address of a location (NOT the value stored in that location). @bptr is "the value stored in the location whose address is in bptr". @bptr functions for most purposes like a register - you can do arithmetic on it. So:
Code:
bptr = MyLocation1   ' set bptr to the address MyLocation1
@bptr = @bptr + 1  ' add 1 to the value stored in MyLocation1
will change the value stored in MyLocation1.
 

Mark.R

Member
Thank you both I'll spend a bit of time looking into seeing if I can modify my code to use RAM where possible and save the words for those that need them.
 

Mark.R

Member
I've managed to get the code to now fit on a 14M2 by using ll the system words where I don't need to have access to the words bytes/bits and it works fine. I looked into the use of poke/peek and couldn't work out how I could make it work and save on using the normal variables to read from and then right to after, you'd still need spares to do this??

I've attached the code if interested with the comments above bits removed and a re-think on some of the symbol names as suggested.
 

Attachments

AllyCat

Senior Member
Hi,

A very neatly documented program, but I still maintain that you could use many less variables and not need the S_Wn registers at all. But first a "coding error":
Code:
SBY_Cont_2:
   ;When flue temperture is below setpoint clear OLED display to prevent burn in
if TC_TEMP1 <=10 then            ;Flue temp less than or equal to 100C then
    goto Control_Loop1
end if
Return
You are using a GOTO to exit the subroutine instead of the Return! In this particular example it probably won't cause the program to fail, but it's poor practice. Here, the "subroutine" is unnecessary, the "gosub SBY_Cont_2" can be simply replaced by the single line :
" if TC_TEMP1 <=10 then goto Control_Loop1 ; Flue temp less than or equal to 100C " .

Also, WARN and ASCII_0 appear to be never used and probably many other variables can be re-used or "shared". WARN if needed could probably be a "flag" (within b0) and ASCII_0 doesn't need to be stored, so you can replace it with ASCII_1 (i.e. used twice) in the BINTOASCII command.

But you didn't note my comments in #8. You don't need anything like 12 words to store the temperatures, I think the following is probably sufficient:
Code:
Symbol Flags    =  b0
Symbol CJ_VAL1    = b1        ; MAX31855 Raw VAL temperature cold junction
Symbol TC_VAL1    = w1       ; MAX31855 Raw VAL temperature thermocouple
Symbol CJ_TEMP1    = b11    ; MAX31855 No.1 deg C temperature cold junction
Symbol TC_TEMP1    = w2        ; MAX31855 No.1 deg C temperature thermocouple
Symbol CJ_TEMP2    = b12     ; MAX31855 No.2 deg C temperature cold junction
Symbol TC_TEMP2    = w3        ; MAX31855 No.2 deg C temperature thermocouple
Symbol CJ_TEMP3    = b13    ; MAX31855 No.3 deg C temperature cold junction
Symbol TC_TEMP3    = w4        ; MAX31855 No.3 deg C temperature thermocouple
Symbol VAL_AV    = b10        ; Average of all 3 cold junction temps for case temperature
Symbol WARN = bit.0
Symbol SH1 = bit.1             ; Sensor Health 1 , etc.
The SND_Data: routine will need to be updated and of course a "common" routine to read and store the temperatures.

Cheers, Alan.
 

Mark.R

Member
Morning chaps (where I am anyway) thanks for looking and being constructive in the criticism, it's very helpful in the learning.

But you didn't note my comments in #8. You don't need anything like 12 words to store the temperatures, I think the following is probably sufficient:
Your right I didn't but now I've seen what you've done I will as get what you mean.
 
Last edited:

Mark.R

Member
Been through the code again which I've attached and as you can see I've had to go back up to my 20X2 even with taking things like the output Deg Celsius down to Bytes instead of using up Words I'm still over the overall word count for a 14M2 and now I'm not using words for the temperatures, if the temp goes over 255C I think it is it starts doing funny things at I've lost the Thousands place on the ASCII conversion part? Everything else seems to work ok, on first testing anyway.

I'm starting to think I should have used 1 MAX31855 for the thermocoupe on the flue and then DS18B20's on the water pipes!!
 

Attachments

AllyCat

Senior Member
Hi,

You're far from the first person to claim that you have "Run out of Memory (RAM)" in a PICaxe, but it is almost never the case. I have to smile because this project is a "gentle relaxation" from my present "Temperature Sensing" project which has 5 input Words of data, 18 "calibration" Words and the equations (not the program code) to calculate the results solidly fill about half a page in the sensor's data sheet. Admittedly, it's an example of "extreme programming", but I will consider to have "failed" if it doesn't eventually all fit into a single 08M2. ;)

However, I don't expect you to use (or even understand) all the nuances of that other thread, but I do try to use my words carefully for these "simpler" projects. No, your BINTOASCII won't work correctly because it needs 5 output variables and what I actually suggested was to use: bintoascii DSP_VAL, ASCII_1,ASCII_1,ASCII_2,ASCII_3,ASCII_4 (look at the digits carefully), which is a very direct example of "re-using" a variable.. That's the TENsThousands of degrees C digit, which is not required to be displayed (and probably the Thousands can go as well). :)

But, the main point (as also mentioned by Lance and Pete in this thread) is that you don't need to retain the "Raw" variables once they have been used. They can be re-used again (and again) in the program, if they even needed to be created in the first place (which IMHO they don't). So the 28 Bytes and 7 System_Words of variables in an M2 should easily be sufficient. But if not, there are still the 484 bytes of "indirect" RAM variables which can be used as well (I'm using a total of about 64, or half of those in an 08M2, for my project above).

Cheers, Alan.
 

Aries

New Member
Here's a small example of how you can sensibly save on the use of byte and word variables (what I referred to above as registers):

You have three routines ReadSensor1-3 which all do essentially the same thing, but with three different sensors. You put the results into six different word variables (one cold junction and one thermocouple for each sensor). You then convert these into byte variables, and never use the original word variables again. So - you don't need to use different variables for each sensor reading. I would suggest something like:
Code:
Read_Sensor_1:
   ;SENSOR No.1 (FLUE TEMPERATURE)
  Low CS_1                    ;Select MAX31855 No.1 to start conversation
  gosub CommonReadSensor
  high  CS_1                    ;De-Select MAX31855 No.1 to stop conversation

  CJ_TEMP1 = CJ_VAL1 /256            ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP1 = TC_VAL1 /16            ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

Read_Sensor_2:
   ;SENSOR No.2 (WATER FLOW TEMPERATURE)
  Low CS_2                    ;Select MAX31855 No.2 to start conversation
  gosub CommonReadSensor
  high  CS_2                    ;De-Select MAX31855 No.2 to stop conversation

  CJ_TEMP2 = CJ_VAL1 /256            ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP2 = TC_VAL1 /16            ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

Read_Sensor_3:
   ;SENSOR No.3 (WATER RETURN TEMPERATURE)
  Low CS_3                    ;Select MAX31855 No.3 to start conversation
  gosub CommonReadSensor  
  high  CS_3                    ;De-Select MAX31855 No.3 to stop conversation

  CJ_TEMP3 = CJ_VAL1 /256            ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP3 = TC_VAL1 /16            ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

CommonReadSensor:
  TC_VAL1 = 0                ;Clear variable ready for data
   for COUNTX = 0 to 15            ;BITS transfer 31 to 16 MSB first
  TC_VAL1 = TC_VAL1 * 2 + MISO    ;Shift << 1 and write bit data
  pulsout SCK, 1                ;Pulse Clock
   next COUNTX
  CJ_VAL1 = 0                ;Clear variable ready for data           
   for COUNTX = 0 to 15            ;Transfer BITS 15 to 0 MSB First [was compt]
  CJ_VAL1 = CJ_VAL1 * 2 + MISO        ;Shift << 1 and write bit data
  pulsout SCK, 1                ;Pulse Clock
   next COUNTX
  return
You now don't need the word variables for TC_VAL2/3 or CJ_VAL2/3 and you have lost nothing. In fact, because you never use TC_VAL1 or CJ_VAL1 again, you might as well re-use those word vriables elsewhere too.
 
Last edited:

AllyCat

Senior Member
Hi,

Compare my subroutine "ReadSensors:" back in #8 , with "CommonReadSensor:" in #24. ;)

However, since #8 you have added the "Sensor Health" facility, where you appear to be testing the low byte of the Cold Raw data (?) , so will need to modify the subroutine slightly. Since you're not apparently using the "WARN" flag, then you can include that in some additional code before Returning from the Readsensors: routine, such as:
Code:
......
   tempb = tempw * 2 / 512                 ; Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
   tempw = tempw * 16 and $FF        ; 4 left shifts to eliminate the MSBs of the low byte.
   WARN  = tempw max 1                  ; Detection of fault bits on MAX31855
   tempw = tempwA * 2 / 32              ; Thermocouple temp convertion to deg C (shifts: << 1 and >> 5)
return
then at the next higher subroutine level add an appropriate line to copy the flag, like: SH1 = WARN and in the SensorHealth: subroutine test the corresponding flag with something like: if SH1 = 1 then .... .

Note that I only changed the "cold" variables from Words to Bytes, because a byte cannot store a value above 255, which might be insufficient for the flue temperature (or if not, why are you "wasting" Word variables on it) ? If you want to "mix" the display of Byte and Word variables, then just copy the byte(s) to a word variable, before sending it to the subroutine which contains the BINTOASCII.

Cheers, Alan.
 

Mark.R

Member
Hi,

This is neat

Code:
Read_Sensor_1:
;SENSOR No.1 (FLUE TEMPERATURE)
Low CS_1 ;Select MAX31855 No.1 to start conversation
gosub CommonReadSensor
high CS_1 ;De-Select MAX31855 No.1 to stop conversation

CJ_TEMP1 = CJ_VAL1 /256 ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
TC_TEMP1 = TC_VAL1 /16 ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

Read_Sensor_2:
;SENSOR No.2 (WATER FLOW TEMPERATURE)
Low CS_2 ;Select MAX31855 No.2 to start conversation
gosub CommonReadSensor
high CS_2 ;De-Select MAX31855 No.2 to stop conversation

CJ_TEMP2 = CJ_VAL1 /256 ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
TC_TEMP2 = TC_VAL1 /16 ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

Read_Sensor_3:
;SENSOR No.3 (WATER RETURN TEMPERATURE)
Low CS_3 ;Select MAX31855 No.3 to start conversation
gosub CommonReadSensor
high CS_3 ;De-Select MAX31855 No.3 to stop conversation

CJ_TEMP3 = CJ_VAL1 /256 ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
TC_TEMP3 = TC_VAL1 /16 ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

CommonReadSensor:
TC_VAL1 = 0 ;Clear variable ready for data
for COUNTX = 0 to 15 ;BITS transfer 31 to 16 MSB first
TC_VAL1 = TC_VAL1 * 2 + MISO ;Shift << 1 and write bit data
pulsout SCK, 1 ;Pulse Clock
next COUNTX
CJ_VAL1 = 0 ;Clear variable ready for data
for COUNTX = 0 to 15 ;Transfer BITS 15 to 0 MSB First [was compt]
CJ_VAL1 = CJ_VAL1 * 2 + MISO ;Shift << 1 and write bit data
pulsout SCK, 1 ;Pulse Clock
next COUNTX
return

I was then going to ask about the fact that the sensor fail comes from the CJ_VAL (Cold Raw) for each sensor, how can I still use the bx from each one if I use the same Wx for each sensor, it will just have the value of the last one written to init? Don't worry about the WARN it is now gone, wasn't being used and was a throwback from a previous idea so should have been took out. I use the three individual fault bits to but individual fault messages up?

Looks like this may cover that but need to study both more

Code:
......
tempb = tempw * 2 / 512 ; Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
tempw = tempw * 16 and $FF ; 4 left shifts to eliminate the MSBs of the low byte.
WARN = tempw max 1 ; Detection of fault bits on MAX31855
tempw = tempwA * 2 / 32 ; Thermocouple temp convertion to deg C (shifts: << 1 and >> 5)
return

Thanks again, your being very patient with an over adventurous armature.
 

Aries

New Member
I can't see any other references to CJ_VALx in the program you attached. However, just suppose you did need them for some purpose. As InglewoodPete said, use what he called a "letterbox" system, or what I called "locations" - that is: store the values you might need into separate storage locations, and then pick them up as and when you need them. Using my example code from above, and changing the common TC and CJ variable names in the ReadSensor routines:
Code:
symbol CJ_VAL1 = 28
symbol TC_VAL1 = CJ_VAL1 + 2
symbol CJ_VAL2 = TC_VAL1 + 2
symbol TC_VAL2 = CJ_VAL2 + 2
symbol CJ_VAL3 = TC_VAL2 + 2
symbol TC_VAL3 = CJ_VAL3 + 2


Read_Sensor_1:
   ;SENSOR No.1 (FLUE TEMPERATURE)
  Low CS_1                    ;Select MAX31855 No.1 to start conversation
  gosub CommonReadSensor
  high  CS_1                    ;De-Select MAX31855 No.1 to stop conversation

  poke CJ_VAL1,word CJ_VAL  ; store raw value
  poke TC_VAL1,word TC_VAL  ; store raw value

  CJ_TEMP1 = CJ_VAL /256            ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP1 = TC_VAL /16            ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return


Read_Sensor_2:
   ;SENSOR No.2 (WATER FLOW TEMPERATURE)
  Low CS_2                    ;Select MAX31855 No.2 to start conversation
  gosub CommonReadSensor
  high  CS_2                    ;De-Select MAX31855 No.2 to stop conversation

  poke CJ_VAL2,word CJ_VAL  ; store raw value
  poke TC_VAL2,word TC_VAL  ; store raw value

  CJ_TEMP2 = CJ_VAL1 /256            ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP2 = TC_VAL1 /16            ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return

etc

; then, if you want to recover the values for individual TC or CJ values:

  peek CJ_VAL1,word CJ_VAL
etc
...

CommonReadSensor:
  TC_VAL = 0                ;Clear variable ready for data
   for COUNTX = 0 to 15            ;BITS transfer 31 to 16 MSB first
  TC_VAL = TC_VAL * 2 + MISO    ;Shift << 1 and write bit data
  pulsout SCK, 1                ;Pulse Clock
   next COUNTX
  CJ_VAL = 0                ;Clear variable ready for data           
   for COUNTX = 0 to 15            ;Transfer BITS 15 to 0 MSB First [was compt]
  CJ_VAL = CJ_VAL * 2 + MISO        ;Shift << 1 and write bit data
  pulsout SCK, 1                ;Pulse Clock
   next COUNTX
  return
A quick bit of explanation:
The word and byte variables (what I called registers) occupy the first 28 bytes (or locations) of RAM on an M2 chip (different for other types). So, the first available RAM location (or letterbox) is at address 28 (0-27 being the registers). I have put CJ_VAL1 at location 28. As it is a word, it occupies 2 bytes. The next location is at address 30 but, rather than write that explicitly, I have said it is CJ_VAL1 + 2. This makes it easier to move things around without having to re-define all the addresses individually.

POKE is the mechanism for putting the values of registers into locations, so the "poke CJ_VAL1,word CJ_VAL" statement puts the WORD contents of register CJ_VAL into location CJ_VAL1 (two bytes, because you are storing a word).
PEEK is the reverse operation, to read the value from a location into a register.
 

Mark.R

Member
So what your saying is that if you poked 0 you'd be writing to w0 and poke 27 would be b27 of w13 these are the only ones you have direct easy access to the WORDS, BYTES & BITS of from then on up to whatever the limit is you have to poke to and then have one of the lower accessible ones to peek it back into. So, I could POKE w0 into 32 say (which on an X2 chip would be w16) and leave it there but would need to PEEK it back into say w1 to use it and then overwrite w1 with the next peek?
 

Aries

New Member
So what your saying is that if you poked 0 you'd be writing to w0 and poke 27 would be b27 of w13 these are the only ones you have direct easy access to the WORDS, BYTES & BITS of from then on up to whatever the limit is you have to poke to and then have one of the lower accessible ones to peek it back into. So, I could POKE w0 into 32 say (which on an X2 chip would be w16) and leave it there but would need to PEEK it back into say w1 to use it and then overwrite w1 with the next peek?
"poke 0,xxx" would write into b0 (one byte into address 0)
"poke 0,word xxx" would write into w0 (one word into addresses 0 and 1)
"poke 27,xxx" would write into b27 which is, as you say, the top half of w13.
Addresses 0 to 27 (on M2) are the same as the word and byte variables, so they can be accessed either way, both for reading and writing. There are times when it is easier to use peek and poke rather than direct references.

If you did poke 32,word w0 then unless you overwrote address 32 or 33 with something else (including on an X2 chip by using w16= or b28=), the value would stay there until you powered off or reset the chip. If you want to recover it, you can peek it into whatever word or byte variable you want. On an X2, you can refer to it directly using its word or byte name. The general principle is that you reserve all the addresses which are word or byte variables (for use as word or byte variables, what I was calling registers) and start the peekable and pokeable RAM after that.

If you want to delve deeper into using RAM, then there is bptr; the concept is a bit more difficult to grasp, so I would start by using RAM to save the values you don't need very often. Also, as Alan suggested, keep w0 and w1 as general-purpose variables, because they have the extra capability of bit referencing as well as byte and word.
 

Mark.R

Member
Evening chaps,

The attached isn't tested as yet as I'm away from the test hardware but the attached does compile, fits on a 14M2 and uses no system words. I've ended up using w0 (I head left this one unused to start with as suggested..but) as a common us variable, I use this to receive the data from each MAX31855 and then POKE it out the way and then peek back into w0 when I need to use and of the TC_VALx for sensor health's.

I think this would be much easier if PE6 would allow you to have all the RAM locations have w.. or b.. numbers

See what you think and if you think I'm getting it at all yet
 

Attachments

inglewoodpete

Senior Member
The attached isn't tested as yet as I'm away from the test hardware but the attached does compile, fits on a 14M2 and uses no system words. I've ended up using w0 (I head left this one unused to start with as suggested..but) as a common us variable, I use this to receive the data from each MAX31855 and then POKE it out the way and then peek back into w0 when I need to use and of the TC_VALx for sensor health's.
There are many ways to structure the code in a microcontroller. Some allow fast execution, some use up all the available resources and some appear to need more. The task is always to manage the resources available, which you have achieved. Well done!
I think this would be much easier if PE6 would allow you to have all the RAM locations have w.. or b.. numbers.
It's never quite as simple as that. To have large amounts of ram addressable as registers would result in the PICAXE running considerably slower (or at inconsistent speeds, depending on which registers you were to use). This relates to how the PE 'tokenises' your code for downloading and running with the PICAXE firmware. My understanding is that the M2s use 5-bit tokens, limiting the range of register locations and modes that can be referenced efficiently.
 

Aries

New Member
I think this would be much easier if PE6 would allow you to have all the RAM locations have w.. or b.. numbers
If you're thinking that other languages (like Basic or C) allow every location to be used for arithmetic, then I'm afraid it's not true. If you delve into the underlying code, it uses a small set of registers (which you can't actually use yourself) and continually peeks or pokes to load or save the values. As InglewoodPete says, that would make execution much slower. The Univac had 36-bit words and 256K WORDS of RAM, but only 48 registers (3 types of 16 each).
 
Last edited:

AllyCat

Senior Member
Hi,

Yes, the modern PICaxes are quite "generous" in the number of Registers available. Recently a forum member was still coding for a PICaxe 18A which must be 15 or 20 years old now? It has only 14 registers and more significantly, 256 bytes of Program space - that's only 1/8 or even 1/16 of that available in most M2s. So I thought it would be an amusing challenge to fit in code that had been considered "too large" for an M2. Actually, the first "problem" was that the 18A only supports 16 subroutine calls (that's the number of individual CALLs, not the number of subroutines) which is only 1/16 or less of an M2. That's hardly sufficient to send just the Text Strings from the EEPROM. :(

Therefore a few compromises were required: The "Welcome" (boot) text can be generated within the OLED display so that was removed. Also the "Alarm" messages for Sensor Fail and Over Temperature, but otherwise everything did fit into the 256 code bytes and those 14 Register bytes. There is some indirect (PEEK / POKE) RAM but it wasn't needed ;) Only two word variables are allocated, the "Working" or Temporary Word and the Flue Temperature. I would expect the "Cold Junction" and Water Temperatures to be less than the 255 degrees (C) that can be stored in a byte and the number of bytes was kept low by doing many of the calculations and tests "On the Fly" and then re-using the bytes for other purposes.

So here's my attempt. It can be "conditionally compiled" to use the 18A in the simulator (note the scant RAM/Register space compared with an M2)
by commenting out the "#define MORECODE" and selecting the 18A in the "Show more PICAXE types" of the PE6 Workspace Explorer pane. I've labelled four locations to set Breakpoints, so that simulated data from the MAX31855 can be entered into the B1 and W1 registers (in degrees C). When "MORECODE" is selected, the additional "Alert" features should be avilable and the code (now about 300 bytes) can be edited and downloaded to a modern PICaxe. I've left in a few "Comments" from the original program to how memory space has been saved. Most of the PAUSEs have been commented-out to save codespace but might be needed on real hardware.

Hmm, that's over the Forum's 10,000 character limit again. :( I'll put the program code in the next post.

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

So here's the code:
Code:
;Wood Burner Temperature V3.0
;Mark Richardson
;Picaxe 14M2
;3 x MAX31855 Thermocouple
;3 x External K-Type Thermocouples
;Thermocouple Sensor Fault Detection  **
;High Water Temperature Warning   **
;1 x AXE134 4 Line OLED Module
;Serial COMS out to Main Controller.
;Serial COMS out to Terminal
;#############################################
#define MORECODE            ; Comment out for 18A
#ifdef MORECODE
#picaxe 14m2
Symbol MISO        = pinB.5    ; Serial Data in from MAX31855's 
#else
#picaxe 18A
Symbol MISO        = pinc.1    ; Changed to an input pin for the 18A
#endif

; CHIP PIN SYMBOL LIST
Symbol OLED        = C.0        ; Serial data out to loacal OLED display (AXE134)
Symbol COM_Tx1    = C.1        ; Serial data out to main heating contoller
Symbol SND_RQT    = PinC.2    ; Serial data request from main controller to send data  
Symbol COM_Tx2    = B.0        ; Serial data out on programing port for Terminal
Symbol CS_1        = B.1        ; MAX31855 No.1 Chip Select
Symbol CS_2        = B.2        ; MAX31855 No.2 Chip Select
Symbol CS_3        = B.3        ; MAX31855 No.3 Chip Select
Symbol SCK        = B.4        ; Serial Clock for MAX31855's
Symbol DISABLES = %1110    ; Disable all MAX31855s
; REGISTERS
symbol health = b0                ; Not needed
symbol tempb = b1
symbol tempw = w1
Symbol TC_TEMP1    = w2        ; MAX31855 No.1 deg C temerature thermocouple
Symbol TC_TEMP2    = b6        ; MAX31855 No.2 deg C temerature thermocouple
Symbol TC_TEMP3    = b7        ; MAX31855 No.3 deg C temerature thermocouple
Symbol VAL_AV        = b8        ; Average of 3 cold junction temps for case temperature
Symbol COUNTX        = b9        ; MAX31855 Clock Counter (can be shared)
Symbol DSP_VAL    = tempw        ; WORD value for OLED display (Flue > 255) 
Symbol LINE        = b10            ; OLED Line number (1 to 4)
Symbol TEXT        = b11            ; ASCII Characters for display
symbol LNTH        = b12            ; OLED Message length
symbol PTR       = b13            ; OLED Read eeprom data pointer
; CONSTANTS
Symbol BAUD        = N2400

   ; EEPROM DATA
EEPROM  00, ("    Wood Burner     ")     ;Store message 0 in EEPROM memory
EEPROM  20, ("Temperature Monitor ")     ;Store message 1 in EEPROM memory
EEPROM  40, ("  Mark Richardson   ")     ;Store message 2 in EEPROM memory
EEPROM  60, ("   MER Electrical   ")     ;Store message 3 in EEPROM memory
EEPROM  80, ("   Flue Temp +")             ;Store message 4 in EEPROM memory
EEPROM 100, ("   Flow Temp +")             ;Store message 5 in EEPROM memory
EEPROM 120, (" Return Temp +")             ;Store message 6 in EEPROM memory
EEPROM 140, ("Av Case Temp +")             ;Store message 7 in EEPROM memory
EEPROM 160, (" -WATER TEMP HIGH-  ")    ;Store message 8 in EEPROM memory
EEPROM 180, (" FLUE SENSOR FAILED ")     ;Store message 9 in EEPROM memory
EEPROM 200, (" FlOW SENSOR FAILED ")     ;Store message 10 in EEPROM memory
EEPROM 220, (" RETURN SENS FAILED ")     ;Store message 11 in EEPROM memory

; PROGRAM
Init:
; Pause 200                          ; Pauses removed to reduce 18A code size
  Low OLED                            ; Initialise OLED output 
; Pause 500                         ; Wait for OLED to initialise
Control_Loop1:
    serout OLED,BAUD,(254,1)        ; Clear OELD Display 
; Pause 500
 ;Used to clear the OLED display if the woodburner in not lit to save the display burn in
    Pause 5000
   gosub Read_Sensor_1            ; Read data from MAX31855 (FLUE TEMP)
   if TC_TEMP1 < 100 then Control_Loop1     ; Flue temp less than or equal to 100C
   serout OLED,BAUD,(253,5)    ; Modified Bootup "Welcome" Text in OLED controller

Control_Loop2:
   VAL_AV = 0
   gosub Read_Sensor_1        ; Read data from MAX31855 (FLUE TEMP)
    if TC_TEMP1 <= 100 then Control_Loop1    ; Flue temp too low
   gosub Read_Sensor_2        ; Read data from MAX31855 (WATER FLOW TEMP)
   gosub Read_Sensor_3        ; Read data from MAX31855 (WATER RETURN TEMP)
    VAL_AV = VAL_AV / 3        ; Max VAL_AV = 85 degrees!
;   gosub Case_Temp            ; Done in ReadSensor
   gosub Display_TMP        ; Display Temp data/messages on OLED 
;   gosub Sensor_Health    ; Done in ReadSensor
;   gosub Over_Heat            ; Done in ReadSensor
    if SND_RQT = 1 then         ; Send Data to main control unit
; Byte data of TC_TEMP1, TC_TEMP2, TC_TEMP3, VAL_AV        
        serout COM_Tx1, BAUD, (b4, b5, b6, b7, b8)             ; Minimised code
;        serout COM_Tx1, BAUD, (b4, b5, b6, 0, b7, 0, b8, 0)    ; Full Words sent  
    endif
Goto Control_Loop2 

Read_Sensor_1:
; SENSOR No.1 (FLUE TEMPERATURE)
    TEXT = 180                        ; In case error is detected
     Low CS_1                            ; Select MAX31855 No.1 to start conversion 
     gosub CommonReadSensor        ; Transfer BITS 15 to 0 MSB first
Breakpoint1:              ;  W1 > 100 to avoid a restart 
    TC_TEMP1 = tempw             ; Store final value
;    high  CS_1            ; De-Select MAX31855 (Done by CommonSensor code)
Return

Read_Sensor_2:
; SENSOR No.2 (WATER FLOW TEMPERATURE)
    TEXT = 200
     Low CS_2                        ; Select MAX31855 No.2 to start conversion
     gosub CommonReadSensor    ; BITS transfer 31 to 16 MSB first
Breakpoint2:                 ; W1 < 90 for no Alert
     TC_TEMP2 = tempw             ; Store final value
;    high  CS_2                    ; De-Select MAX31855 No.2
Return

Read_Sensor_3:
   ; SENSOR No.3 (WATER RETURN TEMPERATURE)
    TEXT = 220
    Low CS_3                        ; Select MAX31855 No.3 to start conversion
      gosub CommonReadSensor    ; BITS transfer 31 to 16 MSB first
Breakpoint3:              ; W1 < 90 for no Alert
      TC_TEMP3 = tempw             ; Store final value
;    high  CS_3                    ; De-Select MAX31855 No.3
Return

CommonReadSensor:
    gosub readsensor
    tempb = tempw / 256
BreakpointC:              ; B1 (Max average 85 degrees)
    VAL_AV = VAL_AV + tempb MAX 255    ; Stored in byte !
    tempb = tempw * 16                    ; Read fault flags

#ifdef MORECODE          ; Program > 256 bytes
Sensor_Health:
   if tempb > 0 then                     ; Detection of fault bits on MAX31855  
        LINE=212                                ; Line 4
         gosub msg20                            ; Go and send message to OLED display
        pause 1000
    else                                        ; Sensor is healthy
Overheat:
        if TC_TEMP2 > 90 OR TC_TEMP3 > 90 then 
; If flow or return water temp greater than 90C then send warning
            LINE=212                    ; Line 4
             TEXT=160                 ; EEPROM location 160
             gosub msg20                ; Go and send message to OLED display
       endif 
   endif                                        ; End of sensor helth
#endif    ; MORECODE

    gosub ReadSensor 
      tempw = tempw / 32                    ; Scale to degrees C
#ifdef MORECODE    
    outpinsb = outpinsb OR DISABLES            ; PINSB for M2 PICaxes
#else
    pins = pins OR DISABLES                ; For 18A
#endif
Return                        ; Enter test values here

ReadSensor:
      tempw = 0                                ; Clear variable ready for data
      for COUNTX = 0 to 15                    ; BITS transfer 
          tempw = tempw * 2 + MISO        ; Shift << 1 and write bit data
          pulsout SCK, 1                        ; Pulse Clock
      next COUNTX
Return

Display_TMP:
;    Gather Temperature data to be displayed on OLED
    tempw = TC_TEMP1        ; Move sensor 1 into DSP_VAL
    LINE = 128                ; Line 1
    TEXT = 80                 ; EEPROM location 80
;    LNTH = 12                ; Length of message
    gosub Display_OLED
    tempw = TC_TEMP2        ; Move sensor 2 into DSP_VAL
    LINE=192                    ; Line 2
    gosub Display_OLED
    tempw = TC_TEMP3        ; Move sensor 3 into DSP_VAL
    LINE=148                    ; Line 3
    gosub Display_OLED
    tempw = VAL_AV            ; Move average case temp into DSP_VAL
    LINE=212                    ; Line 4
    goto display_oled        ; And Use its RETURN    
Display_OLED:
    LNTH = 14                ; Length of message
    gosub msg    
    tempb = " "
    for COUNTX = 1 to 4
        if tempw < 1000 AND tempb = " " then nozero
        tempb = tempw / 1000 + "0"
nozero:
        tempw = tempw // 1000 * 10    
        serout OLED, BAUD,(tempb)
    next COUNTX 
; bintoascii DSP_VAL, ASCII_1,ASCII_1,ASCII_2,ASCII_3,ASCII_4 ; Convert temperature to 5 ascii digits
;    serout OLED, BAUD, ("+",ASCII_1,ASCII_2,ASCII_3,ASCII_4,"C") ; Line data
    serout OLED, BAUD,(" C")
Return    
msg20:
    LNTH = 20            ; Most common length
msg:
   ;Read EEPROM messages and send to OLED   don't corrupt LNTH
    serout OLED, BAUD, (254,LINE)        ; Line select
    PTR = TEXT 
   TEXT = TEXT + LNTH
    do                                            ; Start character loop
        read PTR,tempb                        ; Read value from EEPROM
          serout OLED,BAUD,(tempb)        ; Transmit to serial LCD module
        inc PTR
    loop while PTR < TEXT                ; Next character
    TEXT = TEXT + 6                        ; Exit with TEXT = TEXT + 20        
Return
Cheers, Alan.
 

Mark.R

Member
Blimey, re-coded by a master I see, I will compare my attempt with yours to see how you've done it. Thanks again to all who have helped with this and glad you've enjoyed the challenge of shrinking it down to fit on legacy hardware Alan, I hope to learn more from your work. If starting at the top of this thread and working down to the bottom helps other armature/newbie coders then job done.

Got another question though on the peek/poke front. Am I right in thinking that once you've poked data into a RAM space then you can't do any maths on it until you peek it back out? So this wouldn't work?

Code:
Read_Sensor_1:
   ;SENSOR No.1 (FLUE TEMPERATURE)
  Low CS_1                ;Select MAX31855 No.1 to start conversation
  gosub CommonReadSensor    ;BITS transfer 31 to 16 MSB first
  poke CJ_VAL1,word CJ_TC_VAL ;Store raw value
  gosub CommonReadSensor    ;Transfer BITS 15 to 0 MSB First [was compt]
  poke TC_VAL1,word CJ_TC_VAL ;Store raw value
  high  CS_1            ;De-Select MAX31855 No.1 to stop conversation

  CJ_TEMP1 = CJ_VAL1 /256    ;Cold Junction temp convertion to deg C (shifts: << 1 and >> 9)
  TC_TEMP1 = TC_VAL1 /16    ;Thenmocouple temp convertion to deg C (shifts: << 1 and >> 5)
Return
as the CJ_CAL and TC_VAL are in a RAM location that is a POKE/PEEK one?

But possibly this would?

Code:
Read_Sensor_1:
   ;SENSOR No.1 (FLUE TEMPERATURE)
  Low CS_1                ;Select MAX31855 No.1 to start conversation
  gosub CommonReadSensor    ;BITS transfer 31 to 16 MSB first
  poke CJ_VAL1,word CJ_TC_VAL ;Store RAW value 
  gosub CommonReadSensor    ;Transfer BITS 15 to 0 MSB First [was compt]
  poke TC_VAL1,word CJ_TC_VAL ;Store RAW value
  high  CS_1            ;De-Select MAX31855 No.1 to stop conversation
 
  peek CJ_VAL1,word CJ_TC_VAL    ;Retrieve RAW value for conversion
  CJ_TEMP1 = CJ_TC_VAL /256    ;Cold Junction temp conversion to deg C (shifts: << 1 and >> 9)
  peek TC_VAL1,word CJ_TC_VAL    ;Retrieve RAW value for conversion
  TC_TEMP1 = CJ_TC_VAL /16    ;Thenmocouple temp conversion to deg C (shifts: << 1 and >> 5)
Return
 
Last edited:

AllyCat

Senior Member
Hi,
Am I right in thinking that once you've poked data into a RAM space then you can't do any maths on it until you peek it back out?
No, actually you can, but first you need to remember that PEEK and POKE basically just Read and Write individual Bytes into the RAM (memory) space. To understand better, forget about Symbol names (for now) and work with the Basic Registers (Variables) and Numbers (Constants). So a Basic command is for example: POKE 20 , b2 where the 20 is a "pointer" to memory location number 20. In this case, that's the same as b20 = b2 (i.e. copy the value in b2 into b20), but the difference is that you can keep going past location 27 (b27) up to 127 (08M2), 255 (X2s) or 511 (M2s).

Then, you can extend the command by giving a "list" of variables (and/or constants when POKEing), for example POKE 20 , b2 , b3 which copies b2 to location 20 and b3 to 21 (i.e. in this case, it's the same as w10 = w1) that is my preferred method of copying Words to (or from) the "Extended RAM" area. The WORD qualifier (e.g. POKE 20 , WORD w1) is a convenient way to make the compiler do the same thing (i.e. transfer two bytes to/from the RAM) but there are several "hazards". Firstly it is often slower (and uses more bytes of program space), but also, if you "forget" the WORD qualifier, then the program will still compile and run, but only the Low byte is copied (i.e. POKE 30 , w1 does exactly the same as POKE 30 , b2 ). That's not a problem if w1 happens to contain a value below 256 (which is why it's not flagged as an "error"), but will give "bad" results if the value is larger. Also, the S_Wx registers don't have access separately to their High byte; PE5 flags an error if you try to use them, but PE6 (at least its simulator) just "loses" the High byte even if WORD is included.

Next, the "pointer" can itself be a variable (register) so you can write e.g:. FOR b1 = 10 to 27 : POKE b1 , 0 : NEXT which will clear (i.e. set to zero, or any other chosen value) all the bytes between 10 and 27; that's an example where you might POKE the normal registers area (being much more convenient than listing b10 = 0 : b11 = 0 , ............ , b27 = 0). Any variable can be used as a Pointer, but there is also a "special" one named bptr , which is always the correct "size" for the PICaxe, i.e. it will wrap around (back to zero, which can be tested) when incrementing past 127 in an 08M2 or 511 in the other M2s.

Thus you can write POKE bptr , 123 and PEEK bptr , b1 etc., but once a value has been written into bptr, there is another variable called @bptr , so that the two previous commands can be written as @bptr = 123 and b1 = @bptr . The advantage of this is that @bptr can be included in more complex mathematical operations, for example @bptr = @bptr * 2 will double the value of a byte stored anywhere in RAM, without the need to "read" or "write" (PEEK / POKE) it. But it's important to note that @bptr is a specific variable name, you CANNOT attach the @ to any other variable. There are also "automatic" INCrement and DECrement bptr operations (@bptrinc and @bptrdec) which can be very useful and efficient, but these are too complex for the present time.

Finally, a brief comment on the naming of Symbols, because it's important not to confuse Pointers, Variables and Constants (nor of course Byte and Word variables in byte-oriented operations such as PEEK/POKE , READ/WRITE and SEROUT , etc.). Some programmers use for example a "p" prefix on the names, but I often declare Constants in Capital letters and Variables in lower-case (perhaps with an initial Capital letter). Thus I might Symbolise DATE = 30 (i.e. a pointer to a location in extended memory) and Day = b2 : Month = b3 : Year = b4 . Then in a program, I could write POKE DATE , 10 , 11 , 20 and later read the values back with PEEK DATE , Day , Month , Year (or vice versa). But beware that the Program Editor is not case-sensitive, so DAY and day mean exactly the same thing ; an error will be flagged if you try to declare both, but a single mis-typed character can cause confusion.

Cheers, Alan.
 
Last edited:
Top