Combining ADC Result Over 2 Registers Into 1 Variable - Page 1
Forum section: electronics / general

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 06:26:24 am
content: I'm writing some functions to use the ADC module on a PIC16F917. It's an 8 bit device, and the ADC is 10 bits. So the result is split across two registers, 8 bits in one and two in the other. I want my read function to return one 10 bit value (I'm thinking of using a short integer but the compiler keeps complaining about that). My first idea was to create an addressable bitfield and tie it to the return variable using a union, however I would have to manually address each of the bits in the registers. As it is I can only directly go to the registers as a whole.Is there a clean way for me to combine these two into a single 16 bit value (short integer)? Right now I'm going to try putting each register into a variable of the same size as my result, shifting the high register over the correct amount, and XORing them together.Also, are there any tips or tricks anyone has for using the ADC module? Thanks.

author: scrat
author_rank: Frequent Contributor
timestamp: May 10, 2011, 06:51:46 am
content: Which compiler/library are you using? The Microchip C16 ones?

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 07:11:34 am
content: No, I'm using the Hi-Tech C compiler.

author: Psi
author_rank: Super Contributor
timestamp: May 10, 2011, 07:13:53 am
content: Quote from: Lance on May 10, 2011, 06:26:24 amCheck if your environment already has a way to access the adc data as 16bits. I dunno about PICs, but with avrdude you can use ADCL for low bits, ADCH for high or ADCW for all 16 bits together. There may be something like that already available to you.

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 07:28:21 am
content: I ran a search through the manual for the term ADC but didn't find anything other than a brief assembly code example. That would take all the fun out of this anyway.I'm gonna try my idea of XORing the two registers after shifting one of them over. It makes sense in theory.If I say had this in my upper and lower registers:Code:0000001111111111My plan is to stick them into a short unsigned int like so:Code:00000000000000110000000011111111Shift the upper result register over:Code:00000011000000000000000011111111And then XOR them:Code:0000001111111111Seems solid to me.

author: Psi
author_rank: Super Contributor
timestamp: May 10, 2011, 07:43:38 am
content: ah, so you're trying to do it in asm?

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 07:49:31 am
content: No, I'm doing it in C. The only mention of the string "ADC" in the manual PDF was around some assembly examples.

author: Psi
author_rank: Super Contributor
timestamp: May 10, 2011, 08:04:05 am
content: (where myvariable is 16bit)myvariable = ADCL;myvariable = myvariable | (ADCH << 8 ); // or, if you're paranoid... myvariable = (myvariable & 255) | ( (ADCH << 8 ) & 768 );

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 08:23:05 am
content: Quote from: Psi on May 10, 2011, 08:04:05 amIs your value set to be left or right justified, and how many bits is the ADC? It looks like a right justified value to me. Would I be correct in saying that an OR takes fewer operations than an XOR? Using an OR in my situation would be fine.This is what I came up with:Code:unsigned short int ADCREGU=0;//Holds upper ADRESH valueunsigned short int ADCREGL=0;//Holes lower ADRESL valueunsigned short int ADCRES=0; //Final ResultADCREGU=ADRESH;ADCREGL=ADRESL;ADCREGU=ADCREGU << 8;ADCRES=ADCREGU^ADCREGL;return(ADCRES);I don't understand what the purpose of this is:Code:myvariable = (myvariable & 255) | ( (ADCH << 8 ) & 768 );

author: scrat
author_rank: Frequent Contributor
timestamp: May 10, 2011, 08:52:21 am
content: Try searching for "ADC" through the included header files (especially if there's one .h file with "adc" in its name), you'll find #define constants, structures and variables defined there.I bet there's one referring to the entire 16-bit ADC data, made up by the high and low byte.

author: Psi
author_rank: Super Contributor
timestamp: May 10, 2011, 09:54:54 am
content: Quote from: Lance on May 10, 2011, 08:23:05 amright, which is normal afaikQuote from: Lance on May 10, 2011, 08:23:05 am10bit, same as yoursQuote from: Lance on May 10, 2011, 08:23:05 amI dunno, ya would need to see how many asm instructions both ways translate into.That's just the way i do it.I doubt it will matter, ya shouldn't be that tight on instructions that you need to worry about it.If you wanted to investigate you could check if there's a single XOR asm instruction for the pic cpu core your using.My guess is that OR is going to be quicker.Quote from: Lance on May 10, 2011, 08:23:05 amThat looks like it should work fine but- You might want to check the PIC datasheet as i'm not sure if this applies to PICs but... With AVRs you should always read the Low ADC byte first because it buffers the result. So reading the low byte locks the high byte from changing until you read it. If you read the High byte first there is a chance the low byte may get updated with new data before you can read it. In which case you end up with the high byte from sample 1 and the low byte from sample 2. (but it may not apply to PICs)- Again, i'm going on my AVR experience but.. you don't really need the ADCREGL variable, you can use ADRESL directly and save the memory.- You don't strictly need to zero those 3 variables, since they all get assigned to, but it's a good habit to get into. If you were tight on space you could safely remove the initialization to zero for those 3 lines.Quote from: Lance on May 10, 2011, 08:23:05 amSome old cpus can do unusual or undefined things when you do bitwise shift operations. If you are paranoid about checking that the "seemingly" new bits that appear in a shift operation are zeros you can use a mask.That is what those two AND operations do.& 255 masks off all data but the bits your after 0000 0000 1111 1111 = 255& 768 does the same 0000 0011 0000 0000 = 768This isn't necessary if you know the shift behavior of the cpu your working on, but some people are paranoid

author: ziq8tsi
author_rank: Regular Contributor
timestamp: May 10, 2011, 01:44:26 pm
content: Quote from: Lance on May 10, 2011, 08:23:05 amRight justified is the better choice in this case, because then the low order bits are already in the correct place. With a left justified result, both halves would need to be shifted.QuoteIOR and XOR are both available as native instructions, and all arithmetic instructions are the same speed on a PIC. In fact, I doubt you could find any architecture where there is a speed difference.You could also use addition, but thatcanbe slightly slower than OR on 8bit platforms due to the additional complication of handling a (impossible in this case) carry between the bytes.I think I would argue that inclusive OR is the more common idiom, when dealing with fields that do not overlap.QuoteThe &768 would only be necessary if the six unused bits in ADRESH might contain junk values. But the datasheet guarantees that the unimplemented bits read as zeros, so that mask cannot have any effect.The &255 could only have an effect if ADRESL was declared as a signed char, and had therefore been sign-extended when promoting to 16bit. But ADRESL is actually declared as unsigned char, so that mask cannot have any effect. And the correct solution in any case would have been an explicit cast to (unsigned char), since masking back afterwards is not guaranteed to work on all arithmetic systems.

author: Psi
author_rank: Super Contributor
timestamp: May 10, 2011, 02:43:59 pm
content: Quote from: ziq8tsi on May 10, 2011, 01:44:26 pminteresting

author: Lance
author_rank: Frequent Contributor
timestamp: May 10, 2011, 07:29:08 pm
content: Quote from: Psi on May 10, 2011, 09:54:54 am1. As far as I know the result only changes when you actually tell the module to run. The way I wrote my functions if the ADC is in the middle of a computation it will not allow the read to happen.2. I could, I'm not super tight on memory. However I do want to try and make the code as clean as possible.3. I always initialize variable where needed. It guarantees silly things won't happen from what I'm told.Quote from: Psi on May 10, 2011, 09:54:54 amI see. I've heard of masks, but I don't actually know what they are, nor have I ever used one before. To wikipedia!Quote from: ziq8tsi on May 10, 2011, 01:44:26 pmGood to know, thanks!

author: DrGeoff
author_rank: Frequent Contributor
timestamp: June 02, 2011, 11:54:27 pm
content: Code:unsigned short n;unsigned char adl;unsigned char adh;n = (unsigned short )(adl) | (((unsigned short )(adh) << 8) & 0xff00);// if it's a signed value, with the sign in the 10th bit you will need to move the// sign bit up to bit 15 and remove it from bit 9.n = (n | ((n << 6) & 0x8000)) & 0xfdff;

author: johnmx
author_rank: Frequent Contributor
timestamp: June 03, 2011, 10:12:40 am
content: The simplest, cleanest and fastest way:Code:union IntOr2Char{unsigned int ir;unsigned char cr;};union IntOr2Char ADCResult;ADCResult.cr = ADRESH;ADCResult.cr = ADRESL;ADC result can be accessed like this: ADC_Mean += ADCResult.ir;

author: RayJones
author_rank: Frequent Contributor
timestamp: June 03, 2011, 10:24:11 pm
content: Quote from: johnmx on June 03, 2011, 10:12:40 amFinally commonsense prevails.You must remember at the end, a 16 bit value is exactly as johnmx has shown, 2 sequential bytes in memory.When you have a 8 bit bus, you are simply wasting time wanking about with 16 bit shorts bit shifting and oring to simply read the data from the ADC.Do as John says and use a union and the ADC read opration will take place very efficiently as per the sample code.Yes it is micro specific, but so is the architecture of the PIC's ADC.

author: RayJones
author_rank: Frequent Contributor
timestamp: June 03, 2011, 10:29:39 pm
content: a variation I am fond of also uses a unionCode:union ADCshort {unsigned short word;struct bytes {unsigned char lsb;unsigned char msb;}}union ADCshort ADCval;short testo;ADCval.bytes.msb = ADRESH; // read and store ADC's msbADCval.bytes.lsb = ADRESL; // read and store ADC's lsbtesto = ADCval.word; // utilise 16bit resultThe proof is looking at the assembler listing that the compiler generates.You will see that the structure de-referencing boils down to simple reads from one address to another address.All the tom foolery of calculating the addresses is pre-determined by the compiler.(It would be good if fixed font size appeared in the forum's editor when punching in "code" blocks - hence the edits. :-/)

author: Mechatrommer
author_rank: Super Contributor
timestamp: June 04, 2011, 03:47:49 am
content: i concur. if you know how data is represented in memory location, there should be not much trouble. union is a great tool for bit/byte arrangement.

author: ziq8tsi
author_rank: Regular Contributor
timestamp: June 04, 2011, 05:21:22 am
content: Quote from: RayJones on June 03, 2011, 10:24:11 pmBut you do need to know what order they are stored. There is no compelling reason to prefer one endianness or another on the pic14 architecture, so it is really up to the compiler to choose.QuoteWhen you have an 8bit ALU, there are no instructions that can be used to shift a 16bit value by more than one bit at a time. So you can pretty much guarantee that even a poorly optimizing compiler will have to eliminate the shift. Not to mention that it is a common idiom.Quote from: RayJones on June 03, 2011, 10:29:39 pmOkay. First we need to insert your two missing semicolons and complete the declaration of the member "bytes" so that the code will compile.Hi-tech C lite produces twelve instructions for your code (plus six for the extra assignment) compared to fifteen for the shift and or.

author: RayJones
author_rank: Frequent Contributor
timestamp: June 04, 2011, 09:30:58 am
content: Quote from: ziq8tsi on June 04, 2011, 05:21:22 amOkay. First we need to insert your two missing semicolons and complete the declaration of the member "bytes" so that the code will compile.Hi-tech C lite produces twelve instructions for your code (plus six for the extra assignment) compared to fifteen for the shift and or.Oh Jesus, nail me to the wall, this was written straight into the forum's text editor.Do you code 100% correct every time without the compiler reminding you of your errors?I spend most of my life coding in C++ these days so probably got the syntax of the struct wrong, I'm sorry you had to work it out a bitThe extra assignment was purely used as an example of what to do beyond the ADC reading operations and is irrelevant, the actual loading of the ADC is the part that matters, so 12 over 15 cycles is still better, what's your point?The endianess of a compiler will be known, just as you need to read the PIC's data sheet to know how to read the ADC.We are NOT talking about portable code here, it is very much platform dependent.

author: Mechatrommer
author_rank: Super Contributor
timestamp: June 04, 2011, 05:28:54 pm
content: you should gave him the "pseudo-code" Ray.

author: Lance
author_rank: Frequent Contributor
timestamp: June 04, 2011, 06:26:30 pm
content: I actually got this working a while ago...I'm trying to figure out the LCD driver right now.