PDA

View Full Version : Interrupts interfering with PRINT



lsteele
- 20th September 2006, 22:37
Hello,

I have a 16f876a running at 20MHz. It's connected to 2 H-bridges (of Bob Blick's design as can be seen at http://www.bobblick.com/techref/projects/hbridge/hbridge.html) which I'm using to control two motors via PWM. Initially I was doing the pwm in software manually on each cycle round my program by varying the on/off time ratio at the end of each loop round the program. This worked, but because I was doing quite a few other things each loop I was getting a very low frequency of pwm - around 10-20hz I believe. (I don't have an oscillioscope so can't be sure of the exact frequency, but that's my best guestimate).

The logical solution I figured was to do the pwm using an interrupt routine driven by timer0. This works - I'm getting an (audibly and visibly) much smoother control of the motor speed. Trouble is the LCD display to which I was outputting program data each loop is now showing partially or completely scrambled data. I assume that the PRINT commands I'm using are being rudely interrupted by my timer interrupt and that this is screwing up the timing of the data transmission to the LCD display.

How can I avoid this? I've tried reducing the frequency of the pwm (by adjusting the values placed in PS2:PS0) but this doesn't solve the problem, not to mention that it defeats the point of the whole exercise. My ISR is fairly short (assembly code below), so I presume I'm not taking too long.

Incidentally - if you're wondering why I don't just use hardware pwm, the problem is that each h-bridge requires an input for each direction of motor operation - that's 4 outputs from the PIC in total. As I recall (can't find the specs now), an 876a only has 2 or 3 hardware pwm channels.

If it's the case that serivicing interrupts messes up the timing for picbasic commands doesn't that rather limit their utility? Or am I just doing something really wrong.

Any help/ideas/pointers is really appreciated because I don't think I can progress any further without some assistance.

Thanks,
Luke

Here's the asm code for the ISR. I am doing a RESUME at the end. pwmm1spd is a byte variable with the desired motor speed in it. intCount is a byte variable that just increments each time round the ISR. each time it hits zero is one cycle of pwm, so my pwm has a 'resolution' of 256 degress of on/offness. Hope that makes sense:



movfw pwmm1spd
subwf intCount, w
btfsc STATUS, C
goto PWMOFF
PWMON:
movlw 1
subwf direction, w
btfsc STATUS, Z
goto FORWARD
bcf M1B
bsf M1F
bcf M2F
bsf M2B
goto DONEPWM
FORWARD:
bcf M2B
bsf M2F
bcf M1F
bsf M1B
goto DONEPWM
PWMOFF:
; Turn everything off
bcf M1F ; Since this is the down part of the cycle
bcf M1B ; Easier to turn everything off than test
bcf M2F ; which motors to turn off...
bcf M2B
DONEPWM:
decfsz intCount, 1 ; Decrement intCount
goto intDoneStuff ; if NOT ZERO, then jump to intDoneStuff
movlw 255 ; otherwise (if 0)... 255 into w
movwf intCount ; reset counter
intDoneStuff:
bcf T0IF ; Clear the TMR0 overflow flag

Darrel Taylor
- 21st September 2006, 00:19
<bgsound src="http://www.darreltaylor.com/files/cricket.wav" LOOP=5>Writes to the LCD are Synchronous. So they won't be affected by Interrupts, as long as the interrupts follow the rules.

Typically, when they do interfere, it's because the "Context" hasn't been saved/restored properly.

If the code shown above is the entire interrupt handler, then it's definitely missing the Restore.

SteveB
- 21st September 2006, 00:50
To pick up where Darrel Left off: Giving us more of your code will help keep us from playing twenty questions and guessing at the problem.

Also look at the vB Code page (http://www.picbasic.co.uk/forum/misc.php?do=bbcode#code) to see how you can use the
... to make the code you post look like this:


X VAR BYTE
Y VAR BYTE

X = Y

It will make it a lot more readable (especially for a lot of code). :)

Steve B

mister_e
- 21st September 2006, 01:34
I assume that the PRINT commands I'm using are being rudely interrupted by my timer interrupt and that this is screwing up the timing of the data transmission to the LCD display.

We are ready to help, but you're probably using PROTON compiler. SO you're on the wrong forum to have the full compiler understanding here.

www.picbasic.org/forum

lsteele
- 21st September 2006, 12:20
Actually I am using Proton. I posted here automatically because I'm used to reading this forum. Would I be better off posting it elsewhere (at www.picbasic.org/forum)?

Nontheless, here's a stripped down version of the full thing. (Which I compiled and tested to make sure it exhibits the same behaviour as the full version - it does!) Hopefully it's not too unintelligible. At the moment it's taking a value from a pot and placing it in pwmm1spd which the ISR uses to determine the duty cycle. When PORTB.7 is high it goes into a loop and spits some data out to the LCD.

Thanks again. Any input is really appreciated.



Device 16F876A
Config HS_OSC, WDT_OFF, PWRTE_OFF
Xtal = 20
ON_INTERRUPT ipwm

' Assign some Interrupt associated aliases
SYMBOL T0IE = INTCON.5 ' TMR0 Overflow Interrupt Enable
SYMBOL T0IF = INTCON.2 ' TMR0 Overflow Interrupt Flag
SYMBOL GIE = INTCON.7 ' Global Interrupt Enable

SYMBOL PS0 = OPTION_REG.0 ' Prescaler ratio bit-0
SYMBOL PS1 = OPTION_REG.1 ' Prescaler ratio bit-1
SYMBOL PS2 = OPTION_REG.2 ' Prescaler ratio bit-2
SYMBOL PSA = OPTION_REG.3 ' Prescaler Assignment (1=assigned to WDT 0=assigned to oscillator)
SYMBOL T0CS = OPTION_REG.5 ' Timer0 Clock Source Select (0=Internal clock 1=External PORTA.4)

Declare ADIN_RES 10 ' Set number of bits in result
Declare ADIN_TAD FRC ' Set clock source (3=rc)
Declare ADIN_STIME 10 ' Set sampling time in uS (was 50)

Declare LCD_TYPE ALPHA
Declare LCD_DTPIN PORTC.0
Declare LCD_ENPIN PORTC.5
Declare LCD_RSPIN PORTC.4
Declare LCD_INTERFACE 4
Declare LCD_LINES 2

symbol GLED portb.5
symbol PWMCYLEN 60
symbol M1F portb.4
symbol M1B portb.6
symbol M2F portb.2
symbol M2B portc.7

dim i as dword
dim j as dword
dim h as dword
dim k as dword
dim asenscalib as word
dim asensor as dword
dim potval as word
dim angle as dword
dim oldangle as dword
dim arate as dword
dim speed as dword
dim oldspeed as dword
dim spdout as byte
dim torque as dword
dim ptime as word
dim aCon as word
dim arCon as dword

dim pwmm1spd as byte
dim pwmm2spd as byte
dim pwmcycle as word
dim incr as dword
dim cpause as byte

' PWM interrupt handler variables
dim intCount as byte
dim direction as byte

GOTO overInterrupt

EDATA word 500, word 701, word 696, word 10, word 10

' Interrupt routine starts here
ipwm:
asm

movfw pwmm1spd
subwf intCount, w
btfsc STATUS, C
goto PWMOFF
PWMON:
movlw 1
subwf direction, w
btfsc STATUS, Z
goto FORWARD
bcf M1B
bsf M1F
bcf M2F
bsf M2B
goto DONEPWM
FORWARD:
bcf M2B
bsf M2F
bcf M1F
bsf M1B
goto DONEPWM
PWMOFF:
; Turn everything off
bcf M1F ; Since this is the down part of the cycle
bcf M1B ; Easier to turn everything off than test
bcf M2F ; which motors to turn off...
bcf M2B
DONEPWM:
decfsz intCount, 1 ; Decrement intCount
goto intDoneStuff ; if NOT ZERO, then jump to intDoneStuff
movlw 255 ; otherwise (if 0)... 255 into w
movwf intCount ; reset counter
intDoneStuff:
bcf T0IF ; Clear the TMR0 overflow flag

endasm
CONTEXT RESTORE ' Restore the registers and exit the interrupt

overInterrupt:

TRISB = %10000011
TRISC = $00
TRISA = $FF
ADCON1 = %10000001

M1F = 0 ' Turn all motors off
M1B = 0
M2F = 0
M2B = 0
portb.2 = 0
delayms 25 ' Wait for display (is this necessary?)

print $FE, 1, " Hello" ' Clear display and show Hello
delayms 333
print $FE, 1 ' Clear display

pwmm1spd = 40
pwmm2spd = 40
intCount = 255

' Initiate the interrupt
GIE = 0 ' Turn off global interrupts
PSA = 1 ' Assign the prescaler to external oscillator
PS0 = 0 ' Set the prescaler
PS1 = 0 ' to increment TMR0
PS2 = 0 ' every 256th instruction cycle

T0CS = 0 ' Assign TMR0 clock to internal source
TMR0 = 0 ' Clear TMR0 initially
T0IE = 1 ' Enable TMR0 overflow interrupt
GIE = 1 ' Enable global interrupts

asenscalib = eread 0
ADIN 0, aCon
ADIN 2, arCon
aCon = aCon / 12
arCon = arCon * 35
oldspeed = 0
oldangle = 0
speed = 0
pwmcycle = 0
PORTB.5 = 0

incr = 1
i = 0
cpause = 0
delayms 10

loop:
while PORTB.7 = 1 ' Then we're in calibrate mode

''''''''''' test code
ADIN 0, potval
pwmm1spd = potval >> 2
'''''''''''''''''''''''''''''''''''''''

' M1F = 0 ' Turn all motors off
' M1B = 0
' M2F = 0
' M2B = 0
speed = 0 ' Reset values in case we've already been running...
arate = 0
oldangle = 0
ADIN 1, asensor
ADIN 0, aCon
ADIN 2, arCon
aCon = aCon / 12
arCon = arCon * 35
if arCon < 0 then
print at 1, 1, "aK:", #aCon, " arK:-", #arCon, "(/10k)"
else
print at 1, 1, "aK:", #aCon, " arK:", #arCon, "(/10k)"
endif
print 2, 1, "clb : ", #asensor, " (", #asenscalib, ")"
if PORTB.1 = 1 then
asenscalib = asensor
ewrite 0, [asenscalib]
endif
wend
' END OF CALIBRATION

' BALANCING ALGORITHM

direction = 1
ADIN 0, potval
pwmm1spd = potval >> 2

goto loop

end

mister_e
- 21st September 2006, 15:45
In theory it's suppose to work, but i know i miss something in this one and with PROTON's HardWare/software Interrupt's stuff. I stay away of this compiler anyways since 'round 1 year.

So i guess, the best way will be to use their forum to have the real answer for that.

SteveB
- 21st September 2006, 18:14
I'm not up to speed on PROTON, but did see that the PRINT statements you use are not the same. The last one is missing the "AT" following the PRINT. I imagine this could cause some unexpected results.



if arCon < 0 then
print at 1, 1, "aK:", #aCon, " arK:-", #arCon, "(/10k)"
else
print at 1, 1, "aK:", #aCon, " arK:", #arCon, "(/10k)"
endif
print 2, 1, "clb : ", #asensor, " (", #asenscalib, ")"


Steve

lsteele
- 21st September 2006, 20:44
Hi,

Well spotted! Unfortunately putting it in doesn't seem to resolve the problem.