PDA

View Full Version : Converting HSERIN Data to Number



RossWaddell
- 17th November 2015, 00:18
I'm using USART to pass info from one PIC to another. On the slave PIC, I want to read an incoming data stream which will tell me what PWM duty cycle to set on the slave's CCP module. The format of the data sent is this:

S0D3255~

The first character "S" is the 'sync' character (or start of new transmission); the 2nd is the Chip ID (since there will be a few slaves connected to one Master); third character is what option on the slave is going to change; the 4th character is the number of digits in the adjustment value ("255") and the last character is the termination.

I have an ISR attached to the Rc pin of the USART module on my PIC, so it fires overtime a character comes into the buffer. Here's the ISR:



' ************************************************** *************
' [RX_INT - interrupt handler]
' ************************************************** *************
GetBytes:
' TODO: Update code to handle AdjVal > 1 character (an array?)

HSERIN [Serialdata] ' Get the serial data (guaranteed here to have at

IF Serialdata = SyncByte THEN
IF SyncRcvd THEN ' if it's a Sync byte,
state = 0 ' last command was corrupted; reset 'state'
ERROR = 1 ' and indicate error
ENDIF
SyncRcvd = 1 ' indicate Sync was rcvd
ELSE
IF SyncRcvd THEN
IF !ForMe THEN ' if we haven't rcvd the ID
IF Serialdata = ThisChipID THEN 'and this byte matches the ID
ForMe = 1 ' indicate ID rcvd
ELSE
state = 0 ' Not meant for this chip, reset and wait for next msg
ENDIF
ELSEIF !CmdRcvd THEN
CmdVal = SerialData
CmdRcvd = 1
ELSEIF !LenAdjRcvd THEN
LenAdjVal = SerialData
LenAdjRcvd = 1
i = 0
ELSEIF !AdjRcvd THEN
AdjVal = SerialData
myArray[i] = AdjVal
i = i + 1
If i = ArrayLength THEN
AdjRcvd = 1
ENDIF
ELSEIF !MsgRcvd THEN
If SerialData = TxEndChar THEN
MsgRcvd = 1
ENDIF
ENDIF
ENDIF
ENDIF

@ INT_RETURN


In the Main loop:



Main:
IF MsgRcvd THEN GOSUB ProcessCmdAdj ' Process incomming data
.
.
.
GOT Main


And here's where I output the result (using MCSP's Serial Communicator):


'*********** Process received command **************************************
ProcessCmdAdj:
' varResult = myArray[0]*100 + myArray[1]*10 + myArray[2]

HSEROUT ["Cmd: ", CmdVal, _ ' send Command, Length of Adjustment & Adjustment value and CR/LF
", LenAdj: ", LenAdjVal, " [", DEC LenAdjVal, "]", _
", Adj: ", myArray[0], myArray[1], myArray[2], 13, 10]


' ", Adj: ", AdjVal, " [", DEC AdjVal,"]", 13, 10]

state = 0 ' indicate CMD & ADJ has been processed

cmdVal = 0
LenAdjVal = 0
AdjVal = 0

For i = 0 to (ArrayLength - 1) ' clear out array
myArray[i] = 0
NEXT i

RETURN


I can't figure out how to take the 3 characters stored in myArray and convert it to a numerical 255 value that I can then use to set PWM duty cycle. As you can see from the commented-out line above, I tried multiplying the first 2 characters by 100 & 10, but that just gives garbled results. I know the ISR is building the array properly because the above HSEROUT shows me the correct characters in each item of myArray[].

I've never had to do this kind of math/byte manipulation before, so I'd appreciate help here. The full code for the project is here:



' :::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::
' Based on Darrel Taylor's example
' http://www.picbasic.co.uk/forum/showthread.php?t=9932&p=65066#post65066
' :::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::

' MPASM, LabX-1 , PBP3, Using PIC 16F877A @ 4mHz, 9600 Baud,
' USART and MAX232 to MCS Serial Communicator on PC

' ************************************************** *************
' >>>>>>>>>>>>>>>>> Slave IC ID: 0 <<<<<<<<<<<<<<<<<<<<<<<
' ************************************************** *************

ThisChipID CON "0"

DEFINE OSC 4

DEFINE HSER_RCSTA 90h ' enable serial port,
DEFINE HSER_TXSTA 24h ' enable transmit,
DEFINE HSER_BAUD 9600 ' set baudrate to 9600
DEFINE HSER_CLOERR 1 ' automatic clear overrun error

TRISD = %00000000 ' D to outputs for the LEDs
TRISC = %10000000 ' PORTC.7 is the RX input, PORTC.6 is the TX output
ADCON1 = %00001111 ' Set up ADCON1 register no matter what yr doing!!!

'::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::
INCLUDE "DT_INTS-14.bas" ' Base Interrupt System
INCLUDE "ReEnterPBP.bas" ' Include if using PBP interrupts

;-- Place a copy of these variables in your Main program -------------------
;-- The compiler will tell you which lines to un-comment --
;-- Do Not un-comment these lines --
;---------------------------------------------------------------------------
;wsave VAR BYTE $20 SYSTEM ' location for W if in bank0
wsave VAR BYTE $70 SYSTEM ' alternate save location for W
' if using $70, comment wsave1-3

' --- IF any of these three lines cause an error ?? ------------------------
' Comment them out to fix the problem ----
' -- Which variables are needed, depends on the Chip you are using --
wsave1 VAR BYTE $A0 SYSTEM ' location for W if in bank1
wsave2 VAR BYTE $120 SYSTEM ' location for W if in bank2
wsave3 VAR BYTE $1A0 SYSTEM ' location for W if in bank3
' --------------------------------------------------------------------------

' ************************************************** *************
' ASM Interrupt Definitions
' ************************************************** *************

ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR1_INT, ReloadTMR1, ASM, no ; MUST be first
INT_Handler TMR1_INT, _T1handler, PBP, yes
INT_Handler RX_INT, _GetBytes, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM

'::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::
' Variable definition
'::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::

ERROR_LED VAR PORTD.1 ' rename the LED's
LATE_LED VAR PORTD.2
HEART_LED VAR PORTD.0

PORTD = 0

LEDonDELAY CON 4

TxEndChar CON "~"
SyncByte CON "S"

i VAR BYTE

SerialData VAR BYTE
CmdVal VAR BYTE
LenAdjVal VAR BYTE
AdjVal VAR BYTE
ERRORtime VAR BYTE
LATEtime VAR BYTE
HEARTtime VAR BYTE

state VAR BYTE
SyncRcvd VAR state.0 ' sync byte received
ForMe VAR state.1 ' packet is for this device
CmdRcvd VAR state.2 ' command has been received
LenAdjRcvd VAR state.3 ' decimal length of adjustment value to follow
AdjRcvd VAR state.4 ' adjustment factor has been received
MsgRcvd VAR state.5 ' compelte message received
ERROR VAR state.6 ' sync received out of order
LATE VAR state.7 ' command received before last one was processed

state = 0 ' Initialize default values

i = 0

cmdVal = 0
LenAdjVal = 0
AdjVal = 0
serialdata = 0

ArrayLength CON 3
myArray VAR BYTE[ArrayLength]
varResult VAR BYTE

' Example of received command from master chip:
' ============================================
' SyncByte, Chip Id, Cmd Id, Length of modifier, <modifier>, TxEndChar
'HSEROUT ["S", "0", "A", "3", "155", "~"]


;--- Change these to match the desired interrupt frequency -------------------
;--- See http://DarrelTaylor.com/DT_INTS-14/TimerTemplate.html for more Info.
@Freq = 10 ; Frequency of Interrupts in Hz
@Prescaler = 2 ; Timers Prescaler setting
T1CON = $10 ; $30 = Prescaler 1:8, TMR1 OFF
; $00=1:1, $10=1:2, $20=1:4, $30=1:8 -- Must match @Prescaler value

' Enable interrupts
@ INT_ENABLE RX_INT ; enable USART RX_INT interrupts
@ INT_ENABLE TMR1_INT ; enable Timer 1 interrupts
GOSUB StartTimer ; Start Timer 1

Main:
IF MsgRcvd THEN GOSUB ProcessCmdAdj ' Process incomming data

IF ERROR THEN ' if there's been an Error
HIGH ERROR_LED ' Turn ON ERROR LED
ERROR = 0 ' reset the error flag
ERRORtime = LEDonDELAY ' start countdown till LED-OFF
HSEROUT ["Error",13,10]
; HSEROUT ["SyncRcvd: ", DEC SyncRcvd,", SyncIndx: ",DEC SyncIndx, _
; ", ForMe: ", DEC ForMe,", CmdRcvd: ",DEC CmdRcvd, _
; ", AdjRcvd: ",DEC AdjRcvd,", Cmd: ",CmdVal, ", Adj: ",DEC AdjVal,13,10]
ENDIF
IF LATE THEN ' if command took too long
HIGH LATE_LED ' Turn ON LATE LED
LATE = 0 ' reset the LATE flag
LATEtime = LEDonDELAY ' start countdown till LED-OFF
HSEROUT ["Late",13,10]
ENDIF
GOTO Main

'*********** Process received command **************************************
ProcessCmdAdj:
' varResult = myArray[0]*100 + myArray[1]*10 + myArray[2]

HSEROUT ["Cmd: ", CmdVal, _ ' send Command, Length of Adjustment & Adjustment value and CR/LF
", LenAdj: ", LenAdjVal, " [", DEC LenAdjVal, "]", _
", Adj: ", myArray[0], myArray[1], myArray[2], 13, 10]


' ", Adj: ", AdjVal, " [", DEC AdjVal,"]", 13, 10]

state = 0 ' indicate CMD & ADJ has been processed

cmdVal = 0
LenAdjVal = 0
AdjVal = 0

For i = 0 to (ArrayLength - 1) ' clear out array
myArray[i] = 0
NEXT i

RETURN

' ************************************************** *************
' [TMR1_INT - interrupt handler]
' ************************************************** *************
T1handler:
IF ERRORtime > 0 THEN ' if the Error LED is ON
ERRORtime = ERRORtime - 1 ' decrement the count
IF ERRORtime = 0 THEN ' when it reaches 0
LOW ERROR_LED ' turn the Error LED OFF
ENDIF
ENDIF
IF LATEtime > 0 THEN ' if the LATE LED is ON
LATEtime = LATEtime - 1 ' decrement the count
IF LATEtime = 0 THEN ' when it reaches 0
LOW LATE_LED ' turn the LATE LED OFF
ENDIF
ENDIF
HEARTtime = HEARTtime + 1 ' Toggle heartbeat ~.5sec
IF HEARTtime = 7 THEN
HEARTtime = 0
TOGGLE HEART_LED
ENDIF
@ INT_RETURN

' ************************************************** *************
' [RX_INT - interrupt handler]
' ************************************************** *************
GetBytes:
' TODO: Update code to handle AdjVal > 1 character (an array?)

HSERIN [Serialdata] ' Get the serial data (guaranteed here to have at

IF Serialdata = SyncByte THEN
IF SyncRcvd THEN ' if it's a Sync byte,
state = 0 ' last command was corrupted; reset 'state'
ERROR = 1 ' and indicate error
ENDIF
SyncRcvd = 1 ' indicate Sync was rcvd
ELSE
IF SyncRcvd THEN
IF !ForMe THEN ' if we haven't rcvd the ID
IF Serialdata = ThisChipID THEN 'and this byte matches the ID
ForMe = 1 ' indicate ID rcvd
ELSE
state = 0 ' Not meant for this chip, reset and wait for next msg
ENDIF
ELSEIF !CmdRcvd THEN
CmdVal = SerialData
CmdRcvd = 1
ELSEIF !LenAdjRcvd THEN
LenAdjVal = SerialData
LenAdjRcvd = 1
i = 0
ELSEIF !AdjRcvd THEN
AdjVal = SerialData
myArray[i] = AdjVal
i = i + 1
If i = ArrayLength THEN
AdjRcvd = 1
ENDIF
ELSEIF !MsgRcvd THEN
If SerialData = TxEndChar THEN
MsgRcvd = 1
ENDIF
ENDIF
ENDIF
ENDIF

@ INT_RETURN

;---[TMR1 reload - interrupt handler]-----------------------------------------
ASM ; Calculate Timer Reload Constant
ReloadInst = 8 ; # of Intructions used to reload timer
if ((Prescaler == 1)||(Prescaler == 2)||(Prescaler == 4)||(Prescaler == 8))
MaxCount = 65536 + (ReloadInst / Prescaler)
TimerReload = MaxCount - (OSC*1000000/4/Prescaler/Freq)
if ((TimerReload < 0) || (TimerReload > (65535-ReloadInst)))
error Invalid Timer Values - check "OSC", "Freq" and "Prescaler"
endif
else
error Invalid Prescaler
endif
ENDASM

@Timer1 = TMR1L ; map timer registers to a word variable
Timer1 VAR WORD EXT
TimerReload CON EXT ; Get the External Constant
TMR1ON VAR T1CON.0 ; Alias the Timers ON/OFF bit

;---Reload Timer1------
ASM
ReloadTMR1
MOVE?CT 0, T1CON, TMR1ON ; 1 stop timer
MOVLW LOW(TimerReload) ; 1 Add TimerReload to the
ADDWF TMR1L,F ; 1 value in Timer1
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVLW HIGH(TimerReload) ; 1
ADDWF TMR1H,F ; 1
MOVE?CT 1, T1CON, TMR1ON ; 1 start timer
INT_RETURN
ENDASM

;---Start/Stop controls -----
StartTimer:
Timer1 = TimerReload ; Load Timer
TMR1ON = 1 ; start timer
RETURN

StopTimer:
TMR1ON = 0 ; stop timer
RETURN

HenrikOlsson
- 17th November 2015, 06:09
Hi,

I think it's "the usual" case of not treating the data the same way in both ends.

It looks to me like you are sending ASCII but you're treating the received data as binary/raw numbers.
If you send the decimal digit '1', store that in myArray[0] and later multiply that by 100 what you'll get is 4900 because 49 is the ASCII code for '1'.
To do what you want you can use ARRAYREAD with the DEC modifier or simply use the aproach you already have but "offset" the values to account for the ASCII encoding.


varResult = ((myArray[0]-48)*100) + ((myArray[1]-48)*10) + (myArray[2]-48)


Or you could subtract 48 when receiving the digits.

/Henrik.

RossWaddell
- 17th November 2015, 14:58
Hi,

I think it's "the usual" case of not treating the data the same way in both ends.

It looks to me like you are sending ASCII but you're treating the received data as binary/raw numbers.
If you send the decimal digit '1', store that in myArray[0] and later multiply that by 100 what you'll get is 4900 because 49 is the ASCII code for '1'.
To do what you want you can use ARRAYREAD with the DEC modifier or simply use the aproach you already have but "offset" the values to account for the ASCII encoding.


varResult = ((myArray[0]-48)*100) + ((myArray[1]-48)*10) + (myArray[2]-48)


Or you could subtract 48 when receiving the digits.

/Henrik.

Thanks Henrik! I'll give it a go tonight after work.

Preferably, I'd just send the output of the 8-bit ADC module on the Master chip directly to the Slave (so, 0-255) rather than converting it to ASCII in a 3-digit format (e.g. "027"), but then I can't figure out how to process that in the ISR if I'm only getting one character at a time.

HenrikOlsson
- 17th November 2015, 17:10
So why don't you? ;-)

Master:

ADResult VAR BYTE
....
....
HSEROUT [ADResult] '1 byte


Slave:


Duty VAR WORD
HSERIN [Duty] '1 byte


But now, if you're trying to debug this by sending te value of Duty to the PC terminal you need to use the DEC modifier so it converts the value to ASCII for display to us humans, HSEROUT [DEC Duty]

/Henrik.

RossWaddell
- 17th November 2015, 17:30
So why don't you? ;-)

Master:

ADResult VAR BYTE
....
....
HSEROUT [ADResult] '1 byte


Slave:


Duty VAR WORD
HSERIN [Duty] '1 byte


But now, if you're trying to debug this by sending te value of Duty to the PC terminal you need to use the DEC modifier so it converts the value to ASCII for display to us humans, HSEROUT [DEC Duty]

/Henrik.
Thanks again, Henrik. Can I still incorporate this into my ISR? For that to work, the interrupt on Rc would need to fire when 1 byte gets into the buffet, right? So, instead of an array for 3 'characters' (which on their own are 1 byte, right?) I can just grab the next byte after receiving the AdjCmdVal.

HenrikOlsson
- 17th November 2015, 18:12
Hi,
The UART RX interrupt always* fires for each byte that comes into the RX buffer, there's no way to set a "threshold" on when the interrupt fires. After all, the buffer is only two bytes deep. But yes, you could set your code up so that after proper sync, adress, command etc you grab one byte and that's it.

* Well not always but that's details details details.....

/Henrik.

RossWaddell
- 17th November 2015, 19:17
Hi,
The UART RX interrupt always* fires for each byte that comes into the RX buffer, there's no way to set a "threshold" on when the interrupt fires. After all, the buffer is only two bytes deep. But yes, you could set your code up so that after proper sync, adress, command etc you grab one byte and that's it.

* Well not always but that's details details details.....

/Henrik.
Thanks again, Henrik. Much appreciated. I'm going to try all this out tonight.

Art
- 18th November 2015, 05:15
serialbuffer byte [4]
serialbyte var byte
index var byte



‘ ooh looky here… a serial byte arrived
for index = 1 to 3
serialbuffer[index] = serialbuffer[index-1]
next index
serialbuffer[0] = serialbyte



Now instead of checking for the first byte in the serial string,
check for what you call the termination byte.
I don’t know what that is, so now it’s $FE.




if serialbuffer[0] = $FE then
‘ we know the bytes serialbuffer [0,1,2,3] are the desired 4 byte string in reverse
‘ unless only a partial command was received
if serialbuffer[3] = “S” then
‘ we know now that the entire four byte command was received if there was no serial error
endif
endif

Art
- 18th November 2015, 05:18
Now if you only wanted to grab say byte 2 (beginning counting from zero).




counter var byte
counter = 0

'oh looky here.. a serial byte arrived

if serialbyte = “S” then
counter = 0
else
counter = counter + 1
endif

if counter = 2 then
byteyoucareabout = serialbyte
endif



A lot of this will fall apart if another byte of the command can be the same value as the “S” or the termination character,
which is why it’s better to go something close to the first way, and check both start and end chars if they are always the same.