Issue calculating timer value for PIC18F27J13

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
I am having a strange issue with calculating the timer value of a PIC18F27J13. This chip has the capability of setting the timer with one 16 bit write. Which I have enabled.

T3CONbits.RD16 = 1; // Enables register read/write of Timer3 in one 16-bit operation

This is not working correctly. The timer is actaully interrupting as if I set it to 0XFFFF.


unsigned delayTime;

Sample 1
delayTime = 287;
TMR3 = (0xFFFF - delayTime);

Sample 2
delayTime = (0xFFFF - 287;
TMR3 = delayTime ;

If you do the math the value in TMR3 should be FEE0 but when I look at it with the debugger I am getting FFE0. It seems like the high byte is not being set.

But this works as expected.
TMR3 = (0xFFFF - 287);


After looking at TMR3 here it works fine as expected. I get 0XFEE0 in the TMR3 register.

In all cases, delayTime contained the expected value.


I am probably going to back to the old fashioned 2 8 bit operations but I am curious as to why this is not working. Has anyone had experience withe the RD16 feature? Why would the first two bits of code not work but the third does?
 

AlbertHall

Joined Jun 4, 2014
12,346
Look at the dis-assembly for the compiler output and see what it is actually doing. You need to write to TMR3H with the high byte, then write the low byte to TM3L. When you write the low byte, both bytes are actually written to the timer and the prescaler is cleared. It seems that the compiler is not smart enough to know that. Try spitting the write into the two parts.
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
You need to write to TMR3H with the high byte, then write the low byte to TM3L. When you write the low byte, both bytes are actually written to the timer and the prescaler is cleared. It seems that the compiler is not smart enough to know that. Try spitting the write into the two parts.
Already explained that not with this chip and 16 bit operation feature. and it does not explain why this works TMR3 = (0xFFFF - 287);



BTW went to two separate 8 bit operations as you described and it works. But it should also work in 16bit mode.


Update. I think my dyslexia got the best of me again. In 16bit mode you need to write to TMR3H anot TMR3L

A write to the high byte of Timer3/5 must also take place
through the TMRxH Buffer register. The Timer3/5 high
byte is updated with the contents of TMRxH when a write
occurs to TMRxL. This allows users to write all 16 bits to
both the high and low bytes of Timer3/5 at once.


You read from TMR3L

A read from TMRxL will load the contents of the high byte of Timer3/5
into the Timerx High Byte Buffer register. This provides
users with the ability to accurately read all 16 bits of Timer3/
5 without having to determine whether a read of the
high byte, followed by a read of the low byte, has become
invalid due to a rollover between reads.


I am going to try writing to TMR3H and report back.
 

AlbertHall

Joined Jun 4, 2014
12,346
BTW went to two separate 8 bit operations as you described and it works. But it should also work in 16bit mode.
The data bus inside the chip is 8 bits wide so there can be no actual 16 bit read or write. The way '16 bit' mode works is by an 8 bit write to the high byte buffer, followed by an 8 bit write to the low byte. That write to the low byte loads both bytes in one operation to the timer registers.

When you write 'TMR3 = 0xFFE0' what actually happens depends on how the compiler converts that into machine code which can only use 8 bit operands. Look at the dis-assembler code that the compiler produces and see what the compiler actually does with that operation for different values.
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
The data bus inside the chip is 8 bits wide so there can be no actual 16 bit read or write. The way '16 bit' mode works is by an 8 bit write to the high byte buffer, followed by an 8 bit write to the low byte. That write to the low byte loads both bytes in one operation to the timer registers.

When you write 'TMR3 = 0xFFE0' what actually happens depends on how the compiler converts that into machine code which can only use 8 bit operands. Look at the dis-assembler code that the compiler produces and see what the compiler actually does with that operation for different values.
What is odd is that it WAS working. Unfortunately I don't have version control to go back and see what I did before and prove it was working.
 

AlbertHall

Joined Jun 4, 2014
12,346
The compiler may choose different optimisations depending on what values it is trying to write.
Looking at the dis-assembly will reveal what it is doing.
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
Code:
// Register: TMR3
#define TMR3 TMR3
extern volatile unsigned short  TMR3  @ 0xF7A;
#ifndef _LIB_BUILD
asm("TMR3 equ 0F7Ah");
#endif



// Register: TMR3L
#define TMR3L TMR3L
extern volatile unsigned char  TMR3L  @ 0xF7A;
#ifndef _LIB_BUILD
asm("TMR3L equ 0F7Ah");
#endif
// bitfield definitions
typedef union {
  struct {
  unsigned TMR3L  :8;
  };
} TMR3Lbits_t;
extern volatile TMR3Lbits_t TMR3Lbits @ 0xF7A;
// bitfield macros
#define _TMR3L_TMR3L_POSN  0x0
#define _TMR3L_TMR3L_POSITION  0x0
#define _TMR3L_TMR3L_SIZE  0x8
#define _TMR3L_TMR3L_LENGTH  0x8
#define _TMR3L_TMR3L_MASK  0xFF





/ Register: TMR3H
#define TMR3H TMR3H
extern volatile unsigned char  TMR3H  @ 0xF7B;
#ifndef _LIB_BUILD
asm("TMR3H equ 0F7Bh");
#endif
// bitfield definitions
typedef union {
  struct {
  unsigned TMR3H  :8;
  };
} TMR3Hbits_t;
extern volatile TMR3Hbits_t TMR3Hbits @ 0xF7B;
// bitfield macros
#define _TMR3H_TMR3H_POSN  0x0
#define _TMR3H_TMR3H_POSITION  0x0
#define _TMR3H_TMR3H_SIZE  0x8
#define _TMR3H_TMR3H_LENGTH  0x8
#define _TMR3H_TMR3H_MASK  0xFF
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
Look at the dis-assembly for the compiler output and see what it is actually doing. You need to write to TMR3H with the high byte, then write the low byte to TM3L. When you write the low byte, both bytes are actually written to the timer and the prescaler is cleared. It seems that the compiler is not smart enough to know that. Try spitting the write into the two parts.
Here is setting the TMR3H in 16 bit mode. delayTime equates to 0xFEEo but TMR3 equals 0xFEED.


delayTime = 287;
delayTime = (0XFFFF - delayTime); // Set 16 bit mode
TMR3H = delayTime;

0x58: MOVLW 0x1
0x5A: MOVLB 0x0
0x5C: MOVWF 0xEB, BANKED
0x5E: MOVLW 0x1F
0x60: MOVWF delayTime, BANKED
! delayTime = (0XFFFF - delayTime); // Set 16 bit mode
0x62: MOVFF delayTime, __pcstackCOMRAM
0x64: NOP
0x66: MOVFF 0xEB, 0x2
0x68: NOP
0x6A: COMF __pcstackCOMRAM, F, ACCESS
0x6C: COMF 0x2, F, ACCESS
0x6E: INFSNZ __pcstackCOMRAM, F, ACCESS
0x70: INCF 0x2, F, ACCESS
0x72: MOVLW 0xFF
0x74: ADDWF __pcstackCOMRAM, W, ACCESS
0x76: MOVWF delayTime, BANKED
0x78: MOVLW 0xFF
0x7A: ADDWFC 0x2, W, ACCESS
0x7C: MOVWF 0xEB, BANKED
! TMR3H = delayTime;
0x7E: MOVFF delayTime, TMR3H
0x80: NOP


This is setting TMR3H directly. Which also gives FEED incorrectly.


TMR3H = 0xFFFF - 287; // Set 16 bit


! TMR3H = 0xFFFF - 287; // Set 16 bit
0x58: MOVLW 0xE0
0x5A: MOVWF TMR3H, ACCESS
!


This works as expected.


TMR3 = 0xFFFF - 287; // Set 16 bit

! TMR3 = 0xFFFF - 287; // Set 16 bit
0x58: MOVLW 0xFE
0x5A: MOVWF TMR3H, ACCESS
0x5C: MOVLW 0xE0
0x5E: MOVWF TMR3, ACCESS

And I think I see what is going on there. There are 2 8 bit moves there. Looks like the complier is giving you some help.

Here is the equalvilent with delayTime which does not work.

delayTime = 287;
TMR3 = delayTime - 287; // Set 16 bit

! TMR3 = delayTime - 287; // Set 16 bit
0x62: MOVLW 0xE1
0x64: ADDWF delayTime, W, BANKED
0x66: MOVWF TMR3, ACCESS
0x68: MOVLW 0xFE
0x6A: ADDWFC 0xEB, W, BANKED
0x6C: MOVWF TMR3H, ACCESS
!


So how is this 16 bit mode supposed to work? Or maybe it can't be done in C?
 

AlbertHall

Joined Jun 4, 2014
12,346
So how is this 16 bit mode supposed to work? Or maybe it can't be done in C?
OK, I'll have another go. There isn't, and can't be, a 16 bit write operation with an 8 bit data bus.
The '16 bit' mode pretends to it do it by a trick. When you write to the timer low byte it also copies the high byte buffer to the actual timer high byte thus the whole 16 bits is written at the same time to the timer registers.

The code achieves this by first writing the high byte to the buffer register and then in a second operation writing the low byte to the timer. Looking at the code it looks like two separate writes to the timer registers but that is not what actually happens.

If you do not follow the prescribed procedure - write high byte, then write low byte - then you cannot expect to get the correct results.
 

AlbertHall

Joined Jun 4, 2014
12,346
The advantage is that there is no chance of something changing between two separate writes - or more especially reads.
There is quite a long procedure for reading 16 bit timer registers when RD16 is not available.

I can't think of a circumstance where it would be advantageous to not enable RD16 other than backward compatibility with code written for chips without RD16 capability.
 

BobTPH

Joined Jun 5, 2013
8,954
Yep, it looks like you cannot count on the compiler to write the two bytes in the correct order. And, if you think about it, how could it? It does not know about that special handling in the hardware.

So, you must write TMR3H followed by TMR3L to make it work.

Bob
 
Top