Just thought I would post a quick update:
I am making some progress on having the Atom Pro properly intercept the PWM signals from the hitec receiver using ASM interrupt handlers and the HSERVO timer tick from the .17exp release. When I use it to then go on to output HSERVO commands, it does get a bit jumpy. I know that I need to add some checks for when the 32 bit timer counter rolls over, which is one problem. In the end I may offload the servo handling to an SSC-32 which would help it out a lot. Or potentially I could put a second processor board on the rover which handles these IOS and maybe communicates back to the PRO by something like I2C. The IRQ2 is connected to one channel of one of my motors and it is getting called, but now I need to do all of the work for checking the other channel and then also do it for the second encoder. Lots of work left, but I thought I would post the current code in case anyone is interested. Someday I will actually work on making the robot go outside…
The assembly code took me longer than it should have. Some of the hardware manual was unclear to me at times. Things like if I do something like: btst.b, #1,r1l, do you do a beq xxx when the actual bit is set in the register or when it is clear… Also I first tried doing something like: “bset.b #0,@FDATAVALID:16” thinking that it would work to set a byte variable to non-zero and have basic pick it up. It did not work, so I ended up doing something like: mov.b #0xff,r1l mov.b r1l,@FDAT…
Sure wish the IDE debugger allowed me to step through the assembly code and see the registers!
[code]’ Constant definitions
TRUE con 1
FALSE con 0
;Variable declaration
iOverflowTime var sword
PULSE_SLOP con 4 ; try to define some slop in pulse size so dont just flop back and forth
lEncoder1Cnt var slong
lLastPulse var long
aiPulse var sword (6)
alPulseTimingTICKS var long(6)
iPulseT var sword
fPulseTimingsBusy var byte
fPulseDataValid var byte
fProcessingPulses var byte
fPulseChanged var byte
i var byte
;============================== Motor or Servos ================================================
enablehservo
MOTORSERVO1 con p6
MOTORSERVO2 con p7
fMotorActive var bit
iMotor1Speed var sword
iMotor1SpeedPrev var sword
iMotor2Speed var sword
iMotor2SpeedPrev var sword
;=================================== LCD ======================================================
;USEHSERIAL con 1
;#if USEHSERIAL
;enablehserial
;#else
; define which LCD I am using…
SEETRON con 1
LCDPIN con p15
;#endif
#ifdef SEETRON
;LCD_BAUD con n9600
;LCD_BAUD con 206
LCD_BAUD con n2400
#else
LCD_BAUD con i2400
#endif
; variables for LCD… Not sure how to pass an array to a gosub so.
LCD_String var byte(17)
;=============================- Interrupt definitions =======================================
;Interrupt init
ONASMINTERRUPT WKPINT,handle_WKP
ONASMINTERRUPT IRQ2INT, HANDLE_IRQ2
;============================== Start of CODE ================================================
; Initialization code
gosub LCD_Init
LCD_String = “PWM Intercept”, 0
gosub LCD_Print[1, 0]
;----------------------------- Init System components ---------------------------------------
PMR5.bit0 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit1 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit2 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit3 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit4 = 1 ;enables pin as WKP interrupt instead of normal I/O
PMR5.bit5 = 1 ;enables pin as WKP interrupt instead of normal I/O
IEGR2.bit0 = 1 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit1 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit2 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit3 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit4 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
IEGR2.bit5 = 0 ;0 = Pin will interrupt on a falling edge, 1 to interrupt on a rising edge.
PMR1.bit6 = 1 ; enable pin to IRQ2 interrupt instead of normal I/O
IEGR1.bit2 = 1 ; Interrupt IRQ2 on risigin edge
#if 0
; TimerA regs
iOverflowTime = 0
TMA = 0x6 ; clk/32 (Wish there was /16 but try to cut down interrupts…)
#endif
fPulseTimingsBusy = FALSE
fProcessingPulses = FALSE
fPulseDataValid = FALSE
fPulseChanged = FALSE
aiPulse = 0,0,0,0,0,0
alPulseTimingTicks = 0,0,0,0,0,0
iMotor1Speed = 0
iMotor2Speed = 0
fMotorActive = FALSE
ENABLE WKPINT
ENABLE IRQ2INT
;================================ Main Loop =================================================
main_loop
; I wish I had some semaphores!
if fPulseDataValid and !fPulseTimingsBusy then
fProcessingPulses = true
if !fPulseTimingsBusy then
' handle checks and sets not atomic need to handle race conditions...
' probably won't be problem as we have a window of time after CH6 completes before
' ch1 starts again.
' loop through all 6 and convert into pulse values.
fPulseChanged = 0
for i = 0 to 5
iPulseT = alPulseTimingTicks(i) / 16; convert ticks into ms
if ((iPulseT + PULSE_SLOP) < aiPulse(i)) or ((iPulseT - PULSE_SLOP) > aiPulse(i)) then
aiPulse(i) = iPulseT
fPulseChanged = 1
endif
next
fPulseDataValid = FALSE
'
' Now See if we should update our motor speeds or servo locations. Next round will be to take more than 1 sample for
' each pulse and average and if any of the values differ by some threshold, restart the samples as either something is changing or
' there was a glitch in capturing the signals.
if fPulseChanged then
if !fMotorActive and (aiPulse(4) > 1500) then
fMotorActive= TRUE
endif
' See if we are active
if fMotorActive then
if aiPulse(4) < 1500 then
' Switch on transmitter set to not run motor...
hservo [MOTORSERVO1\0, MOTORSERVO2\0] ; first make sure to stop the motors...
HServoWait[MOTORSERVO1, MOTORSERVO2]
;
hservo [MOTORSERVO1\-16000, MOTORSERVO2\-16000]
fMotorActive = FALSE
gosub DisplayPulses
else
' see if the speed or direction changed
' The ranges intercepted appear to go from about 1100 to about 1900 center at 1500.
' hservo has a range of -12000 to 12000 center at 0 so each value intercepted equals 30 units.
iMotor1SpeedPrev = iMotor1Speed
iMotor2SpeedPrev = iMotor2Speed
iMotor1Speed = (((aiPulse(0)-1500)*30) max 12000) min -12000
iMotor2Speed = (((aiPulse(1)-1500)*30) max 12000) min -12000
if (iMotor1Speed <> iMotor1SpeedPrev) or (iMotor2Speed <> iMotor2SpeedPrev) then
hservo [MOTORSERVO1\iMotor1Speed, MOTORSERVO2\iMotor2Speed]
endif
endif
endif
endif
' let the interrupts know that they can process again.
endif
fProcessingPulses = false
endif
; pause 500
;pulsout 10, pulse_ch1*2
;servo P10, (pulse_ch1-1500)*2, 2
goto main_loop
DisplayPulses
DISABLE WKPINT
gosub LCD_SetPos[0, 1]
for i = 0 to 5
if i = 3 then
gosub LCD_SetPos[0, 2]
endif
gosub LCDConvertNumToString[aiPulse(i)/100]
gosub LCD_Print[0,0]
LCD_STRING = " ",0
gosub LCD_PRINT[0,0]
next
gosub LCDConvertNumToString[LENCODER1CNT]
gosub LCD_PRINT[0,0]
ENABLE WKPINT
return
'============================================================================================
’
’ Handle interrupts for each of the channels
’ It appears like the channels happen sequentially. So we will only watch the rising edge of Ch0 and then record
’ the times for falling edges for all 6 channels. After channel ch6 completes we will trigger main program to do any other
’ processing that may be needed.
BEGINASMSUB
handle_WKP
;save workspace registers
push.l er0
push.l er1
push.l er2
;Get current timer tick
mov.w @_TIMERWTICK:16,e0
mov.w @TCNT,r0
btst #7,@TIERW:8
beq _DONTINC:8
inc.w #1,e0
_DONTINC:
; we want to enable interrupts as quickly as possible. So lets get the interrupt information, clear it as quickly as possible and then
; reenable interrupts.
mov.b @IWPR:8,r1l
; clear the interrupt flag as quickly as possible and enable interrupts.
mov.b r1l, r1h
and.b #0xc0,r1h
mov.b r1h,@IWPR:8
andc #0x7f,ccr ; clear the I bit
;Figure out which WKP happened
; Now see if it is WKP0
; Use Shift Right to find which interupt happened.
shlr.b r1l
bcc _NOTWKP0:8
mov.w #0, e1
bra _WKPKNOWN:8
_NOTWKP0:
; we only want to process and of the other WKPs if we processed the rising edge of WKP0
mov.b @FPULSETIMINGSBUSY:16, r1h
;or.b r1h,r1h ; mov sets zero state
beq _WKPEXIT:16
shlr.b r1l
bcc _NOTWKP1:8
mov.w #1*4,e1 ;might as well have e1 with the offset from the array instead of just the index
bra _WKPKNOWN:8
_NOTWKP1:
shlr.b r1l
bcc _NOTWKP2:8
mov.w #2*4,e1
bra _WKPKNOWN:8
_NOTWKP2:
shlr.b r1l
bcc _NOTWKP3:8
mov.w #3*4,e1
bra _WKPKNOWN:8
_NOTWKP3:
shlr.b r1l
bcc _NOTWKP4:8
mov.w #4*4,e1
bra _WKPKNOWN:8
_NOTWKP4:
; we will assume it must be 5 if we got to here!
mov.w #5*4,e1
xor.b r1l, r1l
mov.b r1l,@FPULSETIMINGSBUSY:16 ; Clear the PULSE PROCESSING Busy
mov.b #0xff, r1l
mov.b r1l,@FPULSEDATAVALID:16 ; Tell the main function it now has valid data
_WKPKNOWN
; OK, we now have register E01 with which WKP happend and E00 with
mov.b @FPROCESSINGPULSES, r1l ; If the main code is processing the input then don't do anything
;or.b r1l,r1l ; I think mov will set the zero bit
bne _WKPEXIT
; processing of WKP0 is special as we need to check if we are doing rising edge or not.
cmp.w #0,e1
bne _PH2_NOTWKP0:8
btst.b #0,@IEGR2:8
beq _PH2_WKP0_FALL_EDGE:8
bclr.b #0,@IEGR2:8 ; set to interrupt on falling edge
mov.b #0xff,r1l
mov.b r1l,@FPULSETIMINGSBUSY:16 ; SET PULSE PROCESSING Busy
mov.l er0, @LLASTPULSE
bra _WKPEXIT
_PH2_WKP0_FALL_EDGE:
bset.b #0,@IEGR2:8 ; Set to interrupt on Rising Edge again
; fall through and process like other interrupts
_PH2_NOTWKP0:
; calculate the aiPulseTimings for this array value.
mov.w e1,r1
xor.w e1,e1
add.w #ALPULSETIMINGTICKS, r1
mov.l @LLASTPULSE,er2 ; get the last pulse time
mov.l er0, @LLASTPULSE ; save away this pulse time
sub.l er2, er0 ; Calculate the time difference
mov.l er0,@er1 ; Save away the time difference in the array
; Fall through
;restore workspace registers
_WKPEXIT
pop.l er2
pop.l er1
pop.l er0
rte
ENDASMSUB
#if USING_BASIC_FOR_INTERRUPTS
handle_WKP0
if fProcessingPulses = FALSE then
If (IEGR2 & 0x01) then ; process rising edge
; need to reset timer
TMA = 0xc ; reset timer
TMA = 0x6 ; clk/32
iOverflowTime = 0
fPulseTimingsBusy = 1
IEGR2.bit0 = 0 ;interrupt on a falling edge.
else
enable
aiPulseTimings(0) = iOverflowTime + TCA
IEGR2.bit0 = 1 ;interrupt on a falling edge.
endif
endif
resume
handle_WKP1
if fPulseTimingsBusy then
enable
; if (irr1.bit6) then ; pending interrupt?
; irr1.bit6 = 0
; iOverflowTime = iOverflowTime + 256
; endif
aiPulseTimings(1) = iOverflowTime + TCA
endif
resume
handle_WKP2
if fPulseTimingsBusy then
enable
aiPulseTimings(2) = iOverflowTime + TCA
endif
resume
handle_WKP3
if fPulseTimingsBusy then
enable
aiPulseTimings(3) = iOverflowTime + TCA
endif
resume
handle_WKP4
if fPulseTimingsBusy then
enable
aiPulseTimings(4) = iOverflowTime + TCA
endif
resume
handle_WKP5
if fPulseTimingsBusy then
enable
aiPulseTimings(5) = iOverflowTime + TCA
fPulseDataValid = TRUE
fPulseTimingsBusy = FALSE
endif
resume
;-----------------------------------------------------------------------------------
; Handle TimerA interrupt
; Try Assembly level handler.
handle_timera
iOverflowTime = iOverflowTime + 256
resume
BEGINASMSUB
HANDLE_TIMERA_ASM
push.w r1 ; first save away ER1 as we will mess with it.
bclr #6,@IRR1:8 ; clear the cooresponding bit in the interrupt pending mask
andc #0x7f,ccr ; allow other interrupts to happen
mov.w @IOVERFLOWTIME:16,r1 ; Add 256 to our counter
;add.w #0x100:16,r1
inc.b r1h
mov.w r1, @IOVERFLOWTIME:16
pop.w r1
rte
ENDASMSUB
#endif
;---------------------------------------------------------------------------------------
; Handle IRQ2 interrupt - We will have the encoder Channel A on this interrupt.
; - First pass we will only handle the rising level
; - later we may try to handle the falling as well
BEGINASMSUB
HANDLE_IRQ2
push.l er1 ; first save away ER1 as we will mess with it.
bclr #2,@IRR1:8 ; clear the IRQ2 bit in the interrupt pending mask
andc #0x7f,ccr ; allow other interrupts to happen
mov.l @LENCODER1CNT:32,er1 ; increment our count of encoders we have seen
inc.l #1,er1
mov.l er1, @LENCODER1CNT:32
pop.l er1
rte
ENDASMSUB
;============================================= LCD Helper functions ==================================================
;
; Serial LCD functions added by Kurt
;
LCD_Init
#ifdef USEHSERIAL
sethserial h9600,h8databits,hnoparity,h1stopbits
#endif
; make sure SerLCD has a chance to finish initializing after power-up
; Reset to 9600 and powerup... - Uncomment if having problems with LCD.
;pause 100
;serout LCDPIN, I9600, [0x12]
Pause(1000)
; Now lets try setting it to 2400 baud. - uncomment if having problems
;Serout LCDPIN, I9600, [0x7c] ; 124, <control k>
;pause 2
;Serout LCDPIN, I9600, [11] ; 124, <control k>
#ifndef SEETRON
; Set the backlight to low
;#ifdef USEHSERIAL
; HSerout [0x7C, 0x80] ; 124, 128 - Off
;#else
Serout LCDPIN, LCD_BAUD, [0x7C, 0x80] ; 124, 128 - Off
#endif
;#endif
; Output the clear screen command
gosub LCD_WriteCommand[1] ; // Clear the screen
Pause(5)
return
;
; Main LCD output function
;
line var sbyte
fPad var byte
LCD_Print[line, fPad]
if line = 1 then
gosub LCD_WriteCommand[0x80]
elseif line = 2
gosub LCD_WriteCommand[0xC0]
endif
Pause(10)
for line = 0 to 14 ; try now to write to last position as it appears to autoscroll...
if fPad <> 2 and LCD_String(line) > 0 then
#ifdef USEHSERIAL
hserout [LCD_String(line)]
#else
serout LCDPIN, LCD_BAUD, [LCD_String(line)]
#endif
else
if fPad = 0 then
goto LCDP_Enable
endif
fPad = 2
#ifdef USEHSERIAL
hserout " "]
#else
serout LCDPIN, LCD_BAUD, " "]
#endif
endif
Pause(1)
next
LCDP_Enable
return
;
;DISPLAY DATA RAM ADDRESSES
;CHARACTER 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
; + – -- – -- – -- – -- – -- – -- – -- – --
;LINE 1 | 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
;LINE 2 | C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
;
ich var byte
iline var byte
LCD_SetPos[ich, iline]
if iline = 1 then
gosub LCD_WriteCommand[0x80+ich]
else
gosub LCD_WriteCommand[0xC0+ich]
endif
pause 10
return
; clears the display and resets the current location to the upper left */
LCD_ClearAndReturn
gosub LCD_WriteCommand[1] ; // clear display
Pause(100)
return
fRight var byte
ichShift var byte
LCD_ShiftDisplay[fRight, ichShift]
if fRight <> 0 then
fRight = 0x1c
else
fRight = 0x18
endif
while (ichShift > 0)
gosub LCD_WriteCommand[fRight]
ichShift = ichShift -1
wend
return
; Send a command to the LCD, first send a command prefix character and then then command
cmd var byte
LCD_WriteCommand[cmd]
#ifdef USEHSERIAL
hserout [0xfe]
pause 10
hserout [cmd]
#else
serout LCDPIN, LCD_BAUD, [0xfe]
pause 10
serout LCDPIN, LCD_BAUD, [cmd]
#endif
return
iCnT1 var byte
iCnt2 var byte
tb var byte
iOutLast var sword
iOut var sword
LCDConvertNumToString[iOut]
if iOut < 0 then
LCD_String(0) = “-”
iOut = -iOut
iCnt1 = 1
iCnt2 = 1
else
iCnt1 = 0
iCnt2 = 0
endif
do
iOutLast = iOut
LCD_String(iCnt1) = "0" + iOut // 10
iOut = iOut / 10
iCnt1 = iCnt1 + 1
while (iOutLast >= 10)
LCD_String(iCnt1) = 0
iCnt1 = iCnt1 - 1
while (iCnt1 > iCnt2)
;swap LCD_String(iCnt1), LCD_String(iCnt2)
tb = LCD_String(iCnt1)
LCD_String(iCnt1) = LCD_String(iCnt2)
LCD_String(iCnt2) = tb
iCnt1 = iCnt1 - 1
iCnt2 = iCnt2 + 1
wend
return
[/code]