CDC and timer interrupt
Moderators: David Barker, Jerry Messina
CDC and timer interrupt
Has anyone gotten a timer interrupt to work with USB CDC module.
The reason I ask is the CDC works fine by itself and a timer interrupt also works fine by itself, but when they are combined neither work.
The program is pretty basic. All it does is echo back what you send it until, a certain string is received then it will turn on the timer interrupt.
This is when the program breaks
So does anyone have any suggestion as to do to make them work together.
Thanks,
RKP
The reason I ask is the CDC works fine by itself and a timer interrupt also works fine by itself, but when they are combined neither work.
The program is pretty basic. All it does is echo back what you send it until, a certain string is received then it will turn on the timer interrupt.
This is when the program breaks
So does anyone have any suggestion as to do to make them work together.
Thanks,
RKP
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
Jerry, I am using the 14K50 but it stops working on the 18F2455 as well.
Yes, the CDC module defaults to the USB_SERVICE= true.
Here is my set up procedure
and here is my interrupt procedure
It just generates a square wave. Both work independantly but not together. I even tried to disable and enable the ISR like the library says but no luck.
In my program the CDC will function fine until I call for the activation of the timer (ActivateTimers). They just won't play well together.
RKP
Yes, the CDC module defaults to the USB_SERVICE= true.
Here is my set up procedure
Code: Select all
// Sub Procedures
Sub ActivateTimers()
TMR0 = TimerValue ' Setup Timer 0
T0CON = %10000001 ' Enable TMR0 to 16 bits, int. Clock
' Prescalar 4
Timer0IF = 0
Timer0IE = 1
Timer0On = 1
Enable(OnTimers) ' Enables Interrupt
End Sub
Sub De_activateTimers()
Disable(OnTimers) ' Disables Interrupt
End Sub
Code: Select all
// *****************INTERRUPT************************************
//
//
Interrupt OnTimers(1) ' Interrupt is low USB Int is high by default
High(PORTC.3)
' DisableISR()
If Timer0IF = 1 Then ' Service Timer 0
TMR0 = TimerValue ' Re-load Timer 0
Toggle(PORTC.0)
Timer0IF = 0 ' Clear the Timer0 interrupt bit
EndIf
' EnableISR()
End Interrupt
In my program the CDC will function fine until I call for the activation of the timer (ActivateTimers). They just won't play well together.
RKP
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
Well almost there. I added this
Now the USB CDC functions even when the timer is activated.
But the timer interuupt only seem to be call when data is sent over the USB port. It toggles the led each time the send key is pressed.
The timer interrupt does not seem to be called. By the way commenting out RCON does not seem to matter.
Code: Select all
INTCON2 = %00000000 '
IPR1 = %00000000 ' all others low priority
IPR2 = %00000100 ' USB is high priority
RCON.7 = 1 ' Enables priority levels of interrupts
But the timer interuupt only seem to be call when data is sent over the USB port. It toggles the led each time the send key is pressed.
The timer interrupt does not seem to be called. By the way commenting out RCON does not seem to matter.
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
You don't need to mess with those registers like that. You're effecting a lot of other settings, which you may not want to do if you use a different chip.
Enable() and Disable() will take care of the global interrupt enables for you as well as the IPEN bit in RCON, so all you need to do is clear INTCON2.2
Something like this should work...
Because of the interrupt structure on 18F's, this will only work when the high-priority interrupt is enabled (INTCON.7). Disabling high-priority interrupts also disables the low-priority ones as well. You don't have to do anything here... the high-priority intr will get enabled by the Initialize() sub in USBHID.bas/USBCDC.bas, which is called automatically for you.
Enable() and Disable() will take care of the global interrupt enables for you as well as the IPEN bit in RCON, so all you need to do is clear INTCON2.2
Something like this should work...
Code: Select all
dim Timer0IP as INTCON2.2
Sub ActivateTimers()
TMR0 = TimerValue ' Setup Timer 0
T0CON = %10000001 ' Enable TMR0 to 16 bits, int. Clock
' Prescalar 4
Timer0IP = 0 ' assign low priority intr vector
Timer0IF = 0
Timer0IE = 1
Timer0On = 1
Enable(OnTimers) ' Enables Interrupt
End Sub
Interrupt OnTimers(1) ' Interrupt is low USB Int is high by default
High(PORTC.3)
If Timer0IF = 1 Then ' Service Timer 0
TMR0 = TimerValue ' Re-load Timer 0
Toggle(PORTC.0)
Timer0IF = 0 ' Clear the Timer0 interrupt bit
EndIf
End Interrupt
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
Also, I just noticed you're using the timer in 16-bit mode.
If you have something like the following
That won't load the 16-bit timer correctly. The two 8-bit TMR0L and TMR0H registers must be written in the order TMR0H and then TMR0L.
I use a set of macros...
If you have something like the following
Code: Select all
dim TMR0 as TMR0L.AsWord
TMR0 = TimerValue ' Re-load Timer 0
I use a set of macros...
Code: Select all
// write 16-bit timer specified by 'tmr' with the value 'wval'
public macro write_tmr16(tmr, wval)
if (tmr = 0) then
TMR0H = (wval >> 8)
TMR0L = byte(wval)
elseif (tmr = 1) then
TMR1H = (wval >> 8)
TMR1L = byte(wval)
elseif (tmr = 3) then
TMR3H = (wval >> 8)
TMR3L = byte(wval)
endif
end macro
// reads 16-bit timer specified by 'tmr', stores it in 'wresult'
public macro read_tmr16(tmr, wresult)
if (tmr = 0) then
wresult.byte0 = TMR0L
wresult.byte1 = TMR0H
elseif (tmr = 1) then
wresult.byte0 = TMR1L
wresult.byte1 = TMR1H
elseif (tmr = 3) then
wresult.byte0 = TMR3L
wresult.byte1 = TMR3H
endif
end macro
// example
// write_tmr16(0, TimerValue)
Thanks Jerry,
Both interrupts are now working as they should. Part of my problem was, I was using the CDC3 demo as a starting point and timer INT did not work in with it.
I switched to the CDC1 and it works just fine, thanks again.
My Timer0 seems to load the 16 bit word without any problems, not sure why but I am not going to change it.
Now for another issue. This may be for David if you looking at this or I may need to start another thread topic.
The compiler apparently limits the 14K50 to 256 bytes of RAM even though data sheet says it has more. Is there a way to use more of the available RAM.
I know there is only 256 bytes of DP RAM but is this the determining factor or something wrong in the include or .bas files?
Keith
Both interrupts are now working as they should. Part of my problem was, I was using the CDC3 demo as a starting point and timer INT did not work in with it.
I switched to the CDC1 and it works just fine, thanks again.
My Timer0 seems to load the 16 bit word without any problems, not sure why but I am not going to change it.
Now for another issue. This may be for David if you looking at this or I may need to start another thread topic.
The compiler apparently limits the 14K50 to 256 bytes of RAM even though data sheet says it has more. Is there a way to use more of the available RAM.
I know there is only 256 bytes of DP RAM but is this the determining factor or something wrong in the include or .bas files?
Keith
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
Actually it's not, but depending on what value you're loading into the timer it may look like it's working and you may not notice.My Timer0 seems to load the 16 bit word without any problems, not sure why but I am not going to change it
In 16-bit mode, the value written into TMRH goes into a holding register until TMRL is written, at which point the holding register is then transferred into the TMRH register. That way, the timer is updated all 16-bits at once. So, you have to do the write as TMRH then TMRL. SF doesn't guarantee the order of byte access for a multi-byte thing like a word, and in this case if you look at the assembly language result of a 16-bit write it ends up being write low-byte then write high-byte. Most times you don't care, but it won't work in this case. The macros I showed split the write into two so it can do it in the proper order. The same goes for reading a 16-bit timer, by the way.
The 256 byte limit has to do with the changes done to USBsystem.bas and the USB_EXTENDED_RAM_K50 flag. I think in your code you haveNow for another issue. This may be for David if you looking at this or I may need to start another thread topic. The compiler apparently limits the 14K50 to 256 bytes of RAM even though data sheet says it has more. Is there a way to use more of the available RAM.
Code: Select all
#if USB_EXTENDED_RAM_K50 // Added for PIC18F13K50/14K50 family functionality
#variable _maxram = $0100 // Only 256 bytes of DP Ram <<*** THIS SETS AVAILABLE RAM, NOT DP RAM
#undefine EP_SIZE
#undefine USB_RAM_EP
#define EP_SIZE = 4 // endpoint size (byte)
#define USB_RAM_EP = $200 // USB RAM start location **** starts at 200 hex on these parts
#else
#variable _maxram = $0200
#endif
Code: Select all
#if USB_EXTENDED_RAM_K50 // Added for PIC18F13K50/14K50 family functionality
// max user RAM
#if _device in (18F14K50)
#variable _maxram = $0200 // 512 bytes
#else
#variable _maxram = $0100 // 256 bytes
#endif
#undefine EP_SIZE
#undefine USB_RAM_EP
#define EP_SIZE = 4 // endpoint size (byte)
#define USB_RAM_EP = $200 // USB RAM start location **** starts at 200 hex on these parts
#else
#variable _maxram = $0200
#endif
HOWEVER, that'll conflict with one of the changes you've made in USBCDC
Code: Select all
#if USB_EXTENDED_RAM_K50 // Added for PIC18F13K50/14K50 family functionality
public const // **********
BufferRAM = $100, // **********<<< THIS WILL OVERLAY THE RAM @ $100
TXBufferRAM = BufferRAM, // **********
RXBufferRAM = BufferRAM + 32 // **********
public dim // **********
Buffer(104) as byte absolute BufferRAM, // **********<<< THIS WILL OVERLAY THE RAM @ $100
TXBuffer(32) as byte absolute TXBufferRAM, // **********
RXBuffer(72) as byte absolute RXBufferRAM // **********
#endif // **********
As I've said, this whole USB_EXTENDED_RAM stuff has gotten very confusing with all the disjointed modifications to the stack floating around. I'm almost done with the consolidated version I've been working on that adds support for all the 18F USB pics. Hopefully it'll help with some of these issues, although you still have to do a bit of manual management.
The main HID and CDC code is done and working, including interrupt and polled mode both with and without extended ram. I've run it on an 18F2553, 18F2450, 18F46J50, and the 14K50, which covers most of the different memory layouts (except for the 47J53... I'll check that one later). All that remains is the USB HID bootloader, which I've ported over to SF from the original C code. The bootloader is rather large (4K, same as the Microchip version), and I was hoping I could cut it down some but that doesn't look like it's going to happen since the HID code itself uses ~3K. Checking that out right now, and hope to have something in the next few days.
As with Roshan, I too am interested in your efforts. Thank you for looking into it JerryJerry Messina wrote:I'm almost done with the consolidated version I've been working on that adds support for all the 18F USB pics. Hopefully it'll help with some of these issues, although you still have to do a bit of manual management.
digital-diy.com - Hobby microcontroller projects and tutorials. Assembly, PICBasic and C examples.
Australian distributor for the Swordfish Compiler
Australian distributor for the Swordfish Compiler
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
Thanks.
I should probably explain a little more about using the timers in 16-bit mode, and why it would appear to work if you're using something like
That code will write TMR0L then TMR0H, at least in the current version of SF.
To properly load a 16-bit timer, the sequence must be write TMRH (which goes into the holding register), then write TMRL (which transfers the holding register into TMRH). If you use the other sequence, when it writes TMRL:TMRH you will be transferring whatever's currently in the holding register into TMRH, which isn't what you want. So, the first time you write the timer it will be loaded incorrectly.
If you are reloading the timer with a constant value, the second time you write the TMRL:TMRH pair, it'll use the current contents of the holding register (which was set during the first write). Since the timer reload value isn't changing, the timer appears to have been loaded correctly while it really wasn't. The TMRH value from the second write is still in the holding register, and hasn't been transferred to TMRH.
If you reload the timer with the contents of a changing word variable (or a different value), then you'll see that this doesn't work. It's a very subtle 'bug' that's sure to have you pulling your hair out.
The same thing can happen during a 16-bit read... you must read TMRL (which latches TMRH into the holding register) and then read TMRH (you actually get the holding register contents, not TMRH).
Note: the order of writing/reading multi-byte values used to be different a few versions ago, so old code may have "gotten it right" purely by luck. If you recompile it using the newer versions, the timers may stop working.
I should probably explain a little more about using the timers in 16-bit mode, and why it would appear to work if you're using something like
Code: Select all
dim TMR0 as TMR0L.AsWord
TMR0 = TimerValue ' Re-load Timer 0
To properly load a 16-bit timer, the sequence must be write TMRH (which goes into the holding register), then write TMRL (which transfers the holding register into TMRH). If you use the other sequence, when it writes TMRL:TMRH you will be transferring whatever's currently in the holding register into TMRH, which isn't what you want. So, the first time you write the timer it will be loaded incorrectly.
If you are reloading the timer with a constant value, the second time you write the TMRL:TMRH pair, it'll use the current contents of the holding register (which was set during the first write). Since the timer reload value isn't changing, the timer appears to have been loaded correctly while it really wasn't. The TMRH value from the second write is still in the holding register, and hasn't been transferred to TMRH.
If you reload the timer with the contents of a changing word variable (or a different value), then you'll see that this doesn't work. It's a very subtle 'bug' that's sure to have you pulling your hair out.
The same thing can happen during a 16-bit read... you must read TMRL (which latches TMRH into the holding register) and then read TMRH (you actually get the holding register contents, not TMRH).
Note: the order of writing/reading multi-byte values used to be different a few versions ago, so old code may have "gotten it right" purely by luck. If you recompile it using the newer versions, the timers may stop working.
-
- Swordfish Developer
- Posts: 1473
- Joined: Fri Jan 30, 2009 6:27 pm
- Location: US
RangerBob,
I wouldn't call what you've done hacked at all! It's very well done, and easy to follow.
All I've done (other than a few bug fixes) is to try and consolidate all the work everyone else did, and add support for all the missing chips at the same time.
I've added a new module or two to hold all the device-specific stuff in one place to make it easier to change, but it's not radically different than what was there before. Some things have changed a bit (such as ExtendedRam), but most of the other changes were mainly just to make it easier for me to follow.
One of the other goals was to be able to write the main application one way, and be able to switch between devices/interrupt/polled/extended ram use with no real source code changes, and that seems to work ok, at least so far.
I wouldn't call what you've done hacked at all! It's very well done, and easy to follow.
All I've done (other than a few bug fixes) is to try and consolidate all the work everyone else did, and add support for all the missing chips at the same time.
I've added a new module or two to hold all the device-specific stuff in one place to make it easier to change, but it's not radically different than what was there before. Some things have changed a bit (such as ExtendedRam), but most of the other changes were mainly just to make it easier for me to follow.
One of the other goals was to be able to write the main application one way, and be able to switch between devices/interrupt/polled/extended ram use with no real source code changes, and that seems to work ok, at least so far.
Jerry, I am sure you have seen this. But Joe over at PDS forum did a bunch of work with the bootloaders - http://www.protonbasic.co.uk/showthread ... rt-Thread)