Add GPS/PARSE to 8xSERVO-MOTOR CODE in Oshonsoft

Hi,
The simulator GPS is working and filling the BUFF 0 to 44.

I connected a GPS to PCB9 and there are NMEA sentences at the RX PIN.

Just got to add some CODE to get an output.
C
 
Change the while, wend to Main: and goto Main. But the generated code will be identical.

Or just put it in the while wend loop.

Mike.
 
It needs the ms, tick stuff adding back in but if you add a delay then it'll slow down, you need to learn how to do something after a set time without delays.

I'll add code that will be executed after X time without delays.

Mike.
 
It needs the ms, tick stuff adding back in but if you add a delay then it'll slow down, you need to learn how to do something after a set time without delays.

I'll add code that will be executed after X time without delays.

Mike.
Hi M,
The CODE I'm thinking about will be something like: Lot's of calculations, and comparisons, and yes, it looks like I'm going to have to learn how to do it.

First: my mate has written CODE for the SPI (between PICs) and I ought to see if i can get this going.
Then the exiting bit of JOYSTICK to SERVO movement CODE.
Cheers, C
 
Hi,
I've tried adding some controll CODE into the WAIT1 loop, but it makes the SERVOS jitter.
I'm not so good at CODING so I can't change my methods too much, i'm afraid.

I assume the control CODE will be pages long, but in the CODE attached the SEROUT slows the SERVO movement, which won't be suitable
Any ideas please?
C
 

Attachments

  • 18F4431 32MHz XTL PCB9 REMOTE_SLAVE GPS_SERVO 170423 1100.bas
    5.2 KB · Views: 257
Last edited:
Just a comment. In the serial interrupt code, you only check PIR1.RCIF once, but it is possible there are two bytes to be read. The UART has a 2 byte FIFO. As well, there could be another character arriving before the interrupt routine finishes. Exiting the routine before flushing out all the characters "may or may not" trigger another interrupt, as the flag remains set (never cleared). The RCIF flag is cleared ONLY when RCREG is empty (read until empty).
Your interrupt routine should always check if PIR1.RCIF is zero before exiting, and if not, loop back to the start of the UART routine to read the second character before exiting the interrupt routine.
Odds of this happening is small, but one should always verify the FIFO is empty before exiting anyway.

Also, disabling global interrupts in the main code, even for that short time, will also disable the UART interrupt. There is a remote chance you will miss a UART character. If the UART flags the interrupt when INTCON.GIE is turned back on, fine, but one should be sure of this. I don't know if the interrupt is edge triggered or level triggered. (I think they are level triggered, which would be ok in your case)
 
Last edited:
Hi S,
Regarding the 2xFIFO, I recall this from other programs
----------------------------------------------------------------
If PIE1.RCIE = 1 Then
'overrun error
If RCSTA.OERR = 1 Then
RCSTA.CREN = 0 'disable UART
RXIRQchar = RCREG '1 'clear UART RX registers
RXIRQchar = RCREG '2
RCSTA.CREN = 1 'reenable UART
Gosub IRQinitBuf 're-init buffer, discard bad sentence
Goto RXIRQdone 'done, wait for next character
Endif 'OERR
----------------------------------------------------------------

I presume this would address your comment, is this correct?

I can't quite figure out the timing of all of this, especially with INTERRUPTS, but the UART output does seem strange.

If you recall, 'M' kindly wrote this CODE for me, so as usual, I'll see if he has a comment.

Thanks, C.
 
There's no need to loop inside the isr. If another byte comes in you'll immediately re-enter the isr on exit.

As far as disabling interrupts, as long as it's for less than one char time you won't miss anything when it gets re-enabled.
 
The ISR only checking for 1 byte from the UART will be taken care of (as pointed out by tumbleweed) by a second interrupt. Two bytes can only be available if interrupts were disabled longer than 1mS which shouldn't happen.

I suspect the soft serial routine built into Oshonsoft disables interrupts for timing purposes and this will cause all kinds of errors.

I'll try and find time later to post the code with serial out (pseudo hardware) included without any delays or disabling ISRs.

Mike.
 
I think this has all the code and suggestions implemented,
Code:
'18F4431 32MHz XTL REMOTE_SLAVE 164 160223 2330

Define CONFIG1L = 0x00
Define CONFIG1H = 0x06    '8mHz XTL x4 =32mHz
Define CONFIG2L = 0x0c
Define CONFIG2H = 0x20
Define CONFIG3L = 0x04
Define CONFIG3H = 0x80
Define CONFIG4L = 0x80    'Set for HVP
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

Define CLOCK_FREQUENCY = 32
Define SINGLE_DECIMAL_PLACES = 2
Define STRING_MAX_LENGTH = 20

'Define SIMULATION_WAITMS_VALUE = 1    'Comment in for SIM out for PIC

Dim wordTemp As Word
Dim servoCount As Byte
Dim i As Word
Dim servoPos(8) As Word
Dim servoDir(8) As Byte
Dim frame As Word
Dim buff(80) As Byte
Dim strCount As Byte
Dim rec As Byte
Dim isDone As Bit
Dim cnt As Byte
dim fifo(16) as byte
dim fifoStart as byte
dim fifoEnd as byte
Dim ccpDone As Bit
Dim previous as Bit
Dim TXbyte as byte
Dim bitCount as byte
Dim ms as word
Dim readMsFlag as bit
Dim tick as byte
Dim tickLED as word
Dim str as string

OSCCON = %01110000        '& h70
'Start up leds
rled = 1
WaitMs 1000
rled = 0
WaitMs 1000
rled = 1
WaitMs 1000
rled = 0
WaitMs 1000
gosub initIO
gosub initServos
gosub initSerial
gosub initTMR1
gosun initTMR2
tick=0
ms=0
INTCON=0xc0                        'only PEIE and GIE set
WaitMs 2                            'let serial flush and get stable
str="$test stringW"
for i=0 to len(str)-1
    call putFifo(str(i))
next i
While 1
    If (ms.LB - tick) > 20 Then
        tick=ms.LB
        'this code gets executed 50 times per second (every 20mS)
       For i = 0 To 7
           dir=servoDir(i)                'doesn't matter if ISR happens as servoPos isn't changed by ISR
           pos=servoPos(i)
           If dir = 1 Then
              pos = pos + 40            'add 1/50th to the servo position
          Else
              pos = pos - 40             'subtract it
          Endif
          If pos < 2000 Then             'have we gone past the end?
              pos = 2000                  'yes so make it the end stop
              dir = 1                      'and turn it around
          Endif
          If pos > 4000 Then              'same for other end
              pos = 4000
              dir = 0
          Endif
          INTCON.GIE = 0                    'no interrupts
          servoPos(i)=pos
          INTCON.GIE = 1                    're enable interrupts
          servoDir(i)=dir                'servoDir not used by ISR
        Next i
    Endif
    'example code to flash an LED at 1Hz
    if (getMS-tickLED)>500 then            'note cannot be more than 65535 (65 seconds!!!!)
        'this code executed every 500mS which is 1Hz
        tickLED=ms
        rLED=1-rLED
    endif
Wend
End                        

function getMS() as word
    getMsFlag=0
    getMS=ms
    if getMsFlag then
        getMS=ms
    endif
end function                                         

proc putFifo(dat as byte)
    while ((fifoEnd-fifoStart) AND 0x0f)=0x0f
    wend    
    fifo(fifoEnd)=dat                                    'add data to queue
    fifoEnd=(fifoEnd+1) AND 0x0f                    'and increment pointer
End Proc    

function getFifo() as byte                            'note, doesn't test if fifo is empty
    getFifo=fifo(fifoStart)                            'so always call getFifoLen first
    fifoStart=(fifoStart+1) AND 0x0f
End Function                                                                                     

function getFifoLen() as byte
    getFifoLen=(fifoEnd-fifoStart) AND 0x0f
End Function

initSerial:
    'setup USART for 9600 baud receive
    RCSTA = %10010000
    TXSTA.BRGH = 1
    SPBRG = 207
    PIR1.RCIF = 0
    BAUDCON.BRG16 = 1
    PIE1.RCIE = 1
    PIE1.RCIE = 1
    strCount = 0
    isDone = 0
    ccpDone = 1
return

initServos:
    LATC.0 = 0                        'ensure data is low
    LATC.2 = 0                        'and clock
    For i = 0 To 7
        LATC.2 = 1                    'send positive clock edge
        servoPos(i) = i * 250 + 2000    '1ms(2000) to 1.875(1 7/8ths - 3750)ms in 1/8th mS steps
        LATC.2 = 0                    'send negative edge
        servoDir(i) = i And 1    '<<<<<<<added
    Next i
    servoCount = 8                    'cause it to reset
    CCP1CON = %1000                'will go high on interrupt - will start a 0.5mS pulse
    frame = 1000                    'start everything in 4000 cycles
return

initTMR1:
    T1CON = %00100000            'prescaler = 4
    T1CON.0 = 1                        'start timer
return

initTMR2:
    'setup 1mS interrupt = 8,000,000/16 = 500,000/10 = 50,000 set PR2=49 = 50,000/50 = 1000 = 1mS
    T2CON = %01001110                        '& h4e
    PR2 = 49                                        'This is timer 2 period register. I.E. reset after 50 clocks (0 to 49 = 50 counts)
    PIE1.TMR2IE = 1                             'timer 2 interrupts enable
return

initIO:
    TRISA = %11000000            '7=OSC, 6=OSC,
    TRISC = %11111000            '6=1-slave4431_cs, 2=74HC164 CLK, 0=74HC164 DATA 1=Serial out
    TRISC = %11111010            'CCP0 (RC2) & RC0 output
return

On High Interrupt            'go via location 0x0008
Save System
    if PIR2.CCP2IF Then
        serialCount=serialCount+1
        wordTemp.LB=CCPR2L                    'ensure next interrupt is
        wordTemp.HB=CCPR2H                    'in 208*4=832 instruction cycles
        wordTemp=wordTemp+208                '208 is 2,000,000/9600 = 104uS or the time for 1 bit at 9600 baud
        CCPR2H=wordTemp.HB                    'it's really important that the high byte gets written first
        CCPR2L=wordTemp.LB                    'write it back
        LATC.1=PORTC.1                            'ensure latch is same as current CCP output
        if ccpDone then                        'are we idle
            bitCount=0                            'yes so reset things
            length=getFifoLen()                'is anything in the fifo?
            if length>0 then                    'yes so start a new transmision
                TXbyte=getFifo()                'get byte to transmit
                CCP2CON=%1001                    'pin high and low on next interrupt = start bit
                ccpDone=false                    'tell everything we're no londer idle
                previous=0                        'set previous equal to start bit
            Endif
        else
            'we're sending a byte
            if bitCount<9 then                'have we done a complete byte
                if TXbyte.0=previous then    'no so send next bit,
                    'just need the interrupt as next bit is same as previous bit
                    CCP2CON=%1010
                else
                    'we need to flip the bit
                    if previous Then            'did we previously send a one?
                        'we need a zero next
                        previous=0                'we're sending a zero next
                        CCP2CON=%1001            'will go to zero next match
                    else
                        'we need a one next
                        previous=1                'we're sending a one next
                        CCP2CON=%1000            'will go to zero next match
                    endif
                endif
                TXbyte=shiftRight(TXbyte,1)'shift next bit to zero position
            else
                'doing the stop bits
                if bitCount<10 then
                    if previous then
                        CCP2CON=%1010        'just need interrupt
                    else
                        previous=1
                        CCP2CON=%1000        'go high next interrupt
                    endif
                else
                    ccpDone=true            'stop bits will still be generated - this happens at bitCount=10
                 Endif                            'and output will stay high until next byte
            Endif
        Endif
        bitCount=bitCount+1                'this will get reset if idle
        PIR2.CCP2IF=0
    Endif
    If PIR1.TMR2IF Then                    'Is it a timer 2 interrupt
        getMsFlag=1                            'make any read invalid
        Cnt = Cnt + 1                        'Increment count
        ms=ms+1                                'increment milliSecond
        If Cnt >= 5 Then                    'Has 5mS passed without a byte being received
            isDone=0
            strCount=0
            cnt=0
        Endif
        PIR1.TMR2IF = 0
    Endif
    If PIR1.CCP1IF Then                'has CCP1 triggered?
        wordTemp.HB = CCPR1H        'get value of CCPR1 into wordTemp
        wordTemp.LB = CCPR1L
        If CCP1CON = 0x08 Then    'have we started the 1000 cycle pulse
            CCP1CON = 0x09                'yes so end the pulse after 0.5mS
            wordTemp = wordTemp + 1000    'adding 1000 will make pulse 0.5mS long
        Else
            LATC.0 = 0                        'clear the data pin
            CCP1CON = 0x08                'No so output the timed gap
            If servoCount < 8 Then
                'still doing the servos so add remainder of time
                wordTemp = wordTemp + servoPos(servoCount)
                wordTemp = wordTemp - 1000    'knock of the 1000 (0.5mS) already elapsed
                frame = frame - servoPos(servoCount)
                servoCount = servoCount + 1
            Else
                'done all the servos so just the frame gap to do
                wordTemp = wordTemp + frame
                frame = 39000                '40,000(20mS) minus time of positive pulse(0.5mS)
                servoCount = 0            'start all over again
                LATC.0 = 1                    'will become first pulse
            Endif
        Endif
        CCPR1H = wordTemp.HB    'put value back into CCPR1
        CCPR1L = wordTemp.LB
        PIR1.CCP1IF = 0                'clear interrupt flag
    Endif
    If PIR1.RCIF Then
        cnt=0                                    'stop any timeout happening
        rec = RCREG                        'get the received character
        If RCSTA.OERR Or RCSTA.FERR Then    'neither of these should ever occur.
            RCSTA.CREN = 0            'this is kinda wishful thinking
            while PIR1.RCIF            'if any bytes in the buffer
                rec = RCREG                'throw them away
            wend
            RCSTA.CREN = 1            'as any data received is corrupt
            strCount = 0                'however, reset everything
            isDone = 0                    'and hope for the best
        Else                                    'no errors so use the data
            'the following is waiting for a "$" sign
            If strCount=0 Then    'are we already receiving
                'waiting for $        'no so wait
                If rec = "$" Then    'for $ to appear
                    buff(strCount)=rec'start receiving
                    strCount = 1        'must have been zero previously
                Endif
            Endif
            'the following gets executed if we've allready got a "$" sign
            'but not got a "W" yet.
            If isDone=0 And strCount<>0 Then
                if strCount<80 then                'only store it if there's room in the buffer
                    buff(strCount)=rec            'no so carry on storing
                    strCount = strCount + 1    'this may cause strCount to get to 80 = buffer full
                Endif
                If rec = "W" Then                    'have we got the "endOfString" character
                    isDone = 1                            'yes, so set done true
                Endif
            Endif
            If isDone=0 AND strCount>79 Then    
                'buffer has overflown
                strCount = 0
            Endif
        Endif
    Endif    'end RS232 if
Resume

I changed ms to be a word but only use the low byte for comparison with tick as suggested by tumbleweed.
I did this so delays longer than 255 (¼ second) could be implemented - the LED blinks every 500mS.

Let me know if there are any errors.

Mike.
Note, the filename contains date and time information now.
 

Attachments

  • code 180423 0900.txt
    9.2 KB · Views: 219
BTW, I have deliberately stayed away from prioritize interrupts as I've no idea how they work in Osh or what save System does.

Mike.
 
Pommie, the Oshonsoft software UART does not seem to touch interrupts in any way, nor use any timers. It uses simple timing loops, calculated based on the declared clock frequency.
So if the interrupts are level triggered (so if RCIF still high triggers a second interrupt), then there should not be any reason for the servo to stutter with the addition of a software UART routine doing it's internal code as Camerart mentions.
Of course, your interrupt driven "software" UART I have not looked at...

"Save System" saves the internal working variables to internal temporary storage, like moving R0L (internal working register) to R0L_TEMP. Usually there are half a dozen or so of those, depending on the code. On "resume" when exiting the interrupt, those variables are restored. This does not affect user variables, those are possible to modify in the interrupt routine itself.
 
BTW, I have deliberately stayed away from prioritize interrupts as I've no idea how they work in Osh or what save System does.

Mike.
Pommie, from the Oshonsoft manual about high and low interrupts:

"Interrupt routines (high and low priority) should be placed as all other subroutines after the END statement. They should begin with ON LOW INTERRUPT or ON HIGH INTERRUPT statement and end with RESUME statement. If arithmetic operations, arrays or any other complex statements are used in interrupt routine, then SAVE SYSTEM statement should be placed right after ON LOW/HIGH INTERRUPT statement to save the content of registers used by system. However, it is always a good programming practice to keep interrupt routines as small as possible. ENABLE LOW, ENABLE HIGH, DISABLE LOW and DISABLE HIGH statements can be used in main program to control GIEH and GIEL bits in INTCON register. RESUME statement will set the appropriate GIEH or GIEL bit and enable new interrupts."
 
Unfortunately, I don't know what "stutter" looks like. The soft serial seems to be blocking and therefore the servo updates were not being carried out - see the while 1 look above and envisage a routine that waits until the string is sent added to the bottom of the loop. Plus there were various WaitMS' in there for good measure.

I also think the ISR messes up the timing loops.

The ISR driven serial uses the second ccp module to provide the timing for sending 9600 baud serial. Hence pseudo hardware. It's at the start of the ISR if you're interested.

Mike.
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…