How to combine TMR0H + TMR0L into a 16 bit variable using XC8 - Page 1
Forum section: electronics / general

author: DTJ
author_rank: Super Contributor
timestamp: February 10, 2016, 09:05:26 am
content: Hoping for some help with a basic PIC XC8 question:I'm counting pulses using TMR0, converting the resulting count to ASCII & displaying it on an LCD.To get the value in TMR0 into the counter variable I do it the long way as shown below, it works ok.Q) I imagine I can do it much simpler. How to I get it to just concatenate the two TMR0 registers?Thanks.unsigned int counts = 0; //counts variablecounts = TMR0H*256+TMR0L; // load current TMR0 counter valueitoa(buffer,counts,10); // convert counts to ASCII string & place them in buffer array// now write buffer to LCD

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 10, 2016, 09:15:49 am
content: Code:counts = (TMR0H << 8) | TMR0L;Code:unsigned int *ptr = &count + 3;*ptr = TMR0L; ptr--;*ptr = TMR0H;// it depends on the endianess

author: helius
author_rank: Super Contributor
timestamp: February 10, 2016, 09:16:05 am
content: C does not have any concatenate operator. The standard library has string catenating functions, but they do not work on binary values.Either usecounts = (TMR0H<<8 ) ^TMR0L;orCode:union {unsigned v; struct {unsigned char h, l;} s;} counts;counts.s.h = TMR0H; counts.s.l = TMR0L;itoa(buffer, counts.v, 10);

author: Ian.M
author_rank: Super Contributor
timestamp: February 10, 2016, 09:43:25 am
content: If you are using Timer 0 in 16 bit mode, you must be using a PIC18 as all other families supported by XC8 have an 8 bit Timer 0.The PIC18 Timer 0 has a latch for the high byte, which is used both for read and write operations. TMR0H is the latch, not the actual timer. To write the timer you must first write TMR0H to load the latch then write TMR0L to transfer the latch to the timer high byte, while simultaneously writing the low byte. To read the timer you must first read TMR0L which also updates the latch from the high byte of the timer, then read TMR0H to get the previously latched value.Although the XC8 headers include a definition for a 16 bit TMR0, It should NOT be used as you are relying onundefined behaviour, namely the order of byte reads or writes to a 16 bit volatile unsigned short.Even your:Code:counts = TMR0H*256+TMR0L; // load current TMR0 counter valueis unsafe as the compiler is free to rearrange operations between sequence points and the RHS doesn't contain any.To guarantee a correct read, even if you upgrade the compiler or change optimisation level, you *must* use:Code:counts = TMR0L;counts|=TMR0H*256U; // load current TMR0 counter valueor a similar sequence to ensure you get the high byte that was latched most recently.

author: DTJ
author_rank: Super Contributor
timestamp: February 10, 2016, 11:40:05 am
content: Many thanks for the replies gentlemen. Based on Ian's comments ( & my lack of reading the data sheet), I was also using the incorrect method to write to the Timer.I've found the ReadTimer0() and WriteTimer0() functions also. Is it preferable to use these? I imagine they should take into account subtleties you have pointed out.I also noted the 'U' suffix on the 256 in:counts|=TMR0H*256U; // load current TMR0 counter valueIs this necessary or just good practice? I did some reading and it seems to indicate that the constant (256) is unsigned. I thought that would be implicit anyway.Also I imagine using the 8 left shifts would be faster than multiplying by 256 - is that correct?Thanks for your help guys, really appreciated.

author: Xenoamor
author_rank: Regular Contributor
timestamp: February 10, 2016, 11:43:23 am
content: Quote from: helius on February 10, 2016, 09:16:05 am+1

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 11:58:05 am
content: The fastest approach would be to use a 16-bit pointer and point to TMR0L - in cases where TMR0H and TMR0L are consecutive. This approach suffers from the same flaw as the struct approach in that they are endian-dependent.In general, it is safer to take a look at the datasheet and see precautions on operating 16-bit types on an 8-bit mcu -> atomocity isn't always assured if yo don't follow the right sequence of operations.

author: Ian.M
author_rank: Super Contributor
timestamp: February 10, 2016, 12:49:33 pm
content: @Helius, Xenoamor: You are wrong. TMR0L *MUST* be read first. See any PIC18 datasheet. Even if you fix it, the type-punning union approach is also generally regarded as bad practice, and is non-portable.@DannyF: Its an 8 bit CPU with only three index registers, each split into two parts. Pointers are *SLOW*.@DTJ,Its good practice to use unsigned constants when doing unsigned maths. Under ANSI C's usual arithmetic conversions (C89 #3.2.1.5) the unsigned type of TMR0H dominates so in this case it doesn't really matter. Efficiency relies on the compiler recognising the idiom as a byte shift so simply using MOV instructions, which it will also do for unsigned shifts that are multiples of 8 bits, but some non-ANSI compliant PIC compilers (including Microchip C18) don't do standard integer promotion for chars so the result of TMR0H<<8 will be 0.The functions are actually macros defined in pic18.h so can be used without a performance penalty. As the compiler writers know the current 16 bit byte read order, they will always be correct for the current compiler version and may be faster than the alternative explicit method. However they are non-portable.

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 04:07:46 pm
content: I fed the four approaches (1=using a struct, 2=using a pointer, 3=left shift, 4=multiplication) through XC8 and here is the output:Code:33:myTMR0_struct->W+=1;//increment the word1FB2 4AD6 INFSNZ 0xfd6, F, ACCESS1FB4 2AD7 INCF 0xfd7, F, ACCESS34:tmp = myTMR0_struct->W;//the struct approach1FB6 CFD6 MOVFF 0xfd6, 0x11FB8 F001 NOP1FBA CFD7 MOVFF 0xfd7, 0x21FBC F002 NOP35:tmp = myTMR0_ptr;//the pointer approach1FBE C003 MOVFF 0x3, 0xfd91FC0 FFD9 NOP1FC2 C004 MOVFF 0x4, 0xfda1FC4 FFDA NOP1FC6 CFDE MOVFF 0xfde, 0x11FC8 F001 NOP1FCA CFDD MOVFF 0xfdd, 0x21FCC F002 NOP36:tmp = myTMR0_shift;//the left-shifting approach1FCE 50D6 MOVF 0xfd6, W, ACCESS1FD0 CFD7 MOVFF 0xfd7, 0x51FD2 F005 NOP1FD4 6A06 CLRF 0x6, ACCESS1FD6 C005 MOVFF 0x5, 0x61FD8 F006 NOP1FDA 6A05 CLRF 0x5, ACCESS1FDC 1005 IORWF 0x5, W, ACCESS1FDE 6E01 MOVWF 0x1, ACCESS1FE0 5006 MOVF 0x6, W, ACCESS1FE2 6E02 MOVWF 0x2, ACCESS37:tmp = myTMR0_mlt;//the multiplication approach1FE4 50D6 MOVF 0xfd6, W, ACCESS1FE6 CFD7 MOVFF 0xfd7, 0x51FE8 F005 NOP1FEA 6A06 CLRF 0x6, ACCESS1FEC C005 MOVFF 0x5, 0x61FEE F006 NOP1FF0 6A05 CLRF 0x5, ACCESS1FF2 1005 IORWF 0x5, W, ACCESS1FF4 6E01 MOVWF 0x1, ACCESS1FF6 5006 MOVF 0x6, W, ACCESS1FF8 6E02 MOVWF 0x2, ACCESSCompiler in free mode.The compiler is pretty smart, isn't it? Especially being free,

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: February 10, 2016, 04:12:11 pm
content: pic18f458.hCode:// Register: TMR0extern volatile unsigned short TMR0 @ 0xFD6;main.cCode:unsigned short counts = 0;counts = TMR0;18f458.asCode:;main.c: 12: counts = TMR0;movff(c:4054),(c:_counts);volatilemovff(c:4054+1),(c:_counts+1);volatile

author: Ian.M
author_rank: Super Contributor
timestamp: February 10, 2016, 04:36:30 pm
content: That's what the ReadTimer0() macro generates, a direct 16 bit read of TMR0. If they ever reorder it, the macro will be updated to match.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 10, 2016, 04:55:00 pm
content: Quote from: Bruce Abbott on February 10, 2016, 04:12:11 pmyou should look how the TMR0 get assigned then...

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 04:55:22 pm
content: Quote from: Ian.M on February 10, 2016, 04:36:30 pmHave you tried to tell Micorchip engineers that those macros they wrote violated your directive?Quote

author: Ian.M
author_rank: Super Contributor
timestamp: February 10, 2016, 05:15:57 pm
content: Because the macros are provided by the compiler development team, they can make sure that the access order for each macro is correct for the current compiler version. e.g. the WRITETIMER0() macro is currently quite complex because of this:Code:#define WRITETIMER0(x) ((void)(TMR0H=((x)>>8),TMR0L=((x)&0xFF)))If you use TMR0 directly and a new compiler version breaks your code, that's your problem.A fatal mistake would be to try to alter the period of the timer in its ISR by:Code:TMR0+=CyclesToSkipthe same way you would in 8 bit mode using TMR0L.Its therefore best to avoid using the 16 bit TMR0 definition directly in your code, and if you need to 'bump' the timer like that, use:Code:{unsigned t;t=READTIMER0();t+=CyclesToSkip;WRITETIMER0(t);}n.b.Code:WRITETIMER0(READTIMER0()+CyclesToSkip);is also a disastrous mistake, because WRITETIMER0(x) evaluates x twice so would read the timer twice, with the possibility of TMR0L rollover between the two reads.

author: Brutte
author_rank: Frequent Contributor
timestamp: February 10, 2016, 05:38:47 pm
content: Code:main.c: 12: counts = TMR0;I'd also add:Code:assert(no IRQs accessing TMR0L are enabled);just before that.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: February 10, 2016, 06:11:30 pm
content: Quote from: Ian.M on February 10, 2016, 05:15:57 pmWonderful. You write programs in C because it's portable and less error-prone, but spend half your time dealing with compiler issues. Then when you try to move the program to another chip you discover what 'portability'reallymeans...

author: Ian.M
author_rank: Super Contributor
timestamp: February 10, 2016, 06:26:26 pm
content: *DON'T* remind me. Code written in CCS C using its idiosyncratic library functions is a bear to port to any other compiler. As soon as you make heavy use of non-ANSI libraries, you've locked yourself in to that compiler vendor. At least if you hit the registers directly, the differences in register bit access syntax can usually be accommodated by simple search and replace.Porting up and down within one PIC range (e.g. PIC18) isn't usually so troublesome as long as the new device has the peripherals you need and they support the operating modes you have actually used.

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 07:53:51 pm
content: QuoteYeah right.QuoteOr maybe not.

author: hamster_nz
author_rank: Super Contributor
timestamp: February 10, 2016, 10:06:45 pm
content: Quote from: DTJ on February 10, 2016, 09:05:26 amIsn't this code borked anyway?There is a race condition in that TMR0H may change between when it is read and TMR0L is read, causing the count to be off by 256.If TMR0H is read first, when the timer is 00:FF and then TMR0L is read when it is 01:00 then you end up with 00:00.or If TMR0L is read first, when the timer is 00:FF and then TMR0H is read when it is 01:00 then you end up with 01:FF.Read the high byte first, then the low byte, then the high byte again and check that it hasn't changed. If it has, then rinse and repeat.Code:unsigned short this_high, low, last_high;this_high = TMR0H;do {last_high = this_high;low = TMR0H;this_high = TMR0H;} while (last_high != this_high);count = (this_high << 8) | low;

author: mikerj
author_rank: Super Contributor
timestamp: February 10, 2016, 10:22:04 pm
content: Quote from: dannyf on February 10, 2016, 04:55:22 pmYou are trying to be a too clever, and failed. There is a 16 bit TMR0 definition which is a literal 16 bit pointer to the TMR0 registers, so write ordering in this case is effectively undefined and could change with compiler revisions (though unlikely). This is was Ian was referring to.There is also a pair of macros that explicitly define the order of the reads and writes.

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 10:55:14 pm
content: QuoteOnly if the high byte is unbuffered. It is not necessary in this case as the high byte is buffered.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: February 10, 2016, 11:07:11 pm
content: Quote from: hamster_nz on February 10, 2016, 10:06:45 pmNo. TMR0H is not the timer high byte, it is a buffer. When you read TMR0L it copies the timer high byte to TMR0H. That is why youmustread TMR0L first! (if you read TMR0H first it will have whatever it had on the previous write to TMR0Horread of TMR0 - whichever was the last operation). Similarly you should write to TMR0H first, then writing to TMR0L updates both the high and low bytes together.Note: not all PICs have this feature!

author: dannyf
author_rank: Super Contributor
timestamp: February 10, 2016, 11:26:02 pm
content: Also your code can be simplified:Code:do {msb = TMR0H;lsb = TMR0L;} while (msb == TMR0H);tmr0=(msb<<8) | lsb;

author: westfw
author_rank: Super Contributor
timestamp: February 12, 2016, 02:15:13 am
content: QuotePresumably the combination of the (Microchip-provided) compiler and the (Microchip-provided) access definition combine to provide "defined" behavior that you can complain about if it breaks. Perhaps not very potable to other compilers, though, which is one of the reasons to use a HLL in the first place.QuoteUm. WTF are those NOPs doing there, besides doubling the size of the code?avr-gcc is disappointingly not smart enough to realize that it doesn't need to zero the low byte in "a = a<<8 + PORTB", nor does it recognize that it could INPUT the byte directly to the low register of (16bit) a. Sigh. Although it otherwise produces much better code for the shift case.

author: Ian.M
author_rank: Super Contributor
timestamp: February 12, 2016, 04:22:07 am
content: Quote from: westfw on February 12, 2016, 02:15:13 amIf you use the Microchip provided macros you get implementation defined behaviour - namely correctly ordered access to timers in 16 bit latched mode - that you can complain about if they break it.If you use TMR0 (or the other 16 bit Timer word access SFR variables while the timer is in latched mode) directly, currently reads work but writes fail with the previous value of the latch being used. You may not even notice if you are reloading it in an ISR with a constant, but I hnow of a few users doing more complex things with the timers who have already wasted days or weeks debugging it. It doesn't help that the hardware debuggers use code running on the PIC so also interact with the latch, and the simulator has historically had fairly broken and untrustworthy timer handling.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: February 14, 2016, 08:53:07 am
content: Getting slightly off topic becauseDTJonly wanted toreadTimer0 in a much simpler way, but here is what WRITETIMER0(counts) becomes:-Code:TMR0H=counts>>8,TMR0L=counts&0xFF;3FA4 5006 MOVF 0x6, W, ACCESS3FA6 6ED7 MOVWF 0xfd7, ACCESS3FA8 C005 MOVFF 0x5, 0xfd6Not quite as compact as handcrafted assembler, but pretty close!

author: Ian.M
author_rank: Super Contributor
timestamp: February 14, 2016, 09:08:26 am
content: What XC8 compiler version, mode and optimisation level produced that?

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: February 15, 2016, 01:53:06 am
content: XC8 V1.35 in 'free' mode, via MPLAB IDE V8.92