adding H and L results together for ADC (AVR) - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 10:07:05 am
content: I'm writing a little function so that I can easily get ADC results from the H and L register and straight into a single result. Is the following correct:Code:uint16_t adc_result (void){uint16_t resultresult = (ADCH << 8) + ADCL;return result;}

author: firewalker
author_rank: Super Contributor
timestamp: May 04, 2014, 10:17:26 am
content: Just use ADC.Code:while( ADCSRA & (1<<ADSC) ); // Wait until ADC conversion is complete.result = ADC;Alexander.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 10:19:10 am
content: how do you mean just use ADC ? as far as i know there are no ADC libraries.......My question is abut combining the two registers

author: firewalker
author_rank: Super Contributor
timestamp: May 04, 2014, 10:27:46 am
content: Somewhere in the header files for every mcu there will be a statement like (iom328p.h)Code:#define ADC _SFR_MEM16(0x78)#define ADCW _SFR_MEM16(0x78)When you call ADC (16-bit), it will automatically serve you the combined content of the ADCH and ADCL registers.Alexander.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 10:34:54 am
content: ok, i sort of see it but lets assume I'm really dumb. I see no code there that adds two 8 bit registers together to form one 16 bit variable, just some #defines

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 10:35:12 am
content: either way is my code correct ?

author: Psi
author_rank: Super Contributor
timestamp: May 04, 2014, 10:42:22 am
content: Quote from: Simon on May 04, 2014, 10:35:12 amJust use ADC. Its faster and simpler.Your code will join the two registers into one variable yes, but it looks to be doing it in the wrong order.It looks like ADCH would be read first, then ADCL. (It should be done the other way around)Since there are two ADC registers and the AVR cpu can only do one 8bit operation at a time its possible that the ADCL/ADCH registers could be updated from an interrupt (or free running mode) right in the middle of you reading them in main().To stop you getting the high bits from one sample and low bits from another (or vise versa) the AVR will hardware lock the register when it detects you reading ADCL and then unlock it when you read ADCH.So the correct procedure so to read ADCL first, that way your subsequent ADCH read is guaranteed to be from the same sample.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 10:44:35 am
content: ok but apart from that my code is correct ?

author: Jeroen3
author_rank: Super Contributor
timestamp: May 04, 2014, 10:45:06 am
content: That code to combine the two 8 bits is behind the _SFR_MEM16() macro.Eventually it turn out to:Code:#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr))Which uses typecasting to leave the combining trick to the compiler.Maybe, you will need to read the * at the ADCL/H registers regarding the read sequence.

author: Psi
author_rank: Super Contributor
timestamp: May 04, 2014, 10:47:05 am
content: Quote from: Simon on May 04, 2014, 10:44:35 amyep

author: firewalker
author_rank: Super Contributor
timestamp: May 04, 2014, 10:48:28 am
content: You should OR the shifted ADCH with ADCL.Code:result = (ADCH << 8) |ADCL;Alexander.

author: zapta
author_rank: Super Contributor
timestamp: May 04, 2014, 03:19:06 pm
content: Quote from: Simon on May 04, 2014, 10:07:05 amSimon, there are a few issues here1. This is a C thing. You want to do the shift on a 16 bit value. If you shift a 8 bit value by 8 you will get zero (bit fall off the left side). Hence ((uint16)adch) + adcl.2. On avr, the h/l values of a 16 bit register must be read in a certain order. Otherwise it's not an atomic sampling of the value. Read the AVR datasheet and make sure this is what happens, even after compiler optimizations. (using ADC instead will take care of that).3. Avr specific again. Reading the h/l value of a 16 bit register uses a global temporary register (again, AVR datasheet). If any ISR does i/o, even on different registers, it may corrupt that temp register. In this case you want to disable interrupts while doing the read. I don't think ADC does that so it's a problem even if you use ADC.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 03:35:45 pm
content: Quote from: zapta on May 04, 2014, 03:19:06 pm1) yes I was wondering about that I thought that my result variable being 16 bit would handle it but obviously not.ok so how do i use ADC, i have never heard of it do i just use ADC as the variable that contains the current analog value ?

author: firewalker
author_rank: Super Contributor
timestamp: May 04, 2014, 03:57:31 pm
content: Yes use ADC or ADCW (it is the same thing) directly. ADCW has the extra W to remind you that the result is a Word (16 bit long).Alexander.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 03:58:59 pm
content: so:Code:uint16_t result;result = ADCwill transfer the full result to the 16 bit variable "result" ?

author: Jeroen3
author_rank: Super Contributor
timestamp: May 04, 2014, 03:59:23 pm
content: Code:uint16_t result = ADC;orCode:uint16_t result = ADCL;result |= (ADCH<<8);Quote

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 04:02:57 pm
content: but doesn'tCode:result |= (ADCH<<8);mean that the ADCH can get lost unless it is treated as 16 bit ?

author: madires
author_rank: Super Contributor
timestamp: May 04, 2014, 04:11:17 pm
content: You don't have to add them manually. There's a 16bit access method:ADCSRA |= (1 << ADSC); /* start conversion */while (ADCSRA & (1 << ADSC)); /* wait until conversion is done */my_16bit_variable = ADCW;The "W" means "word" ;-)

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 04:27:06 pm
content: so the answer to my question (bearing in mind i'm dyslexic so trying to tell me too much at once just leaves me blank) is that I can make my 16 bit variable equal to ADCW (or ADC ?) to get my result ?my_variable = ADCW;

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 04:32:18 pm
content: or as in my function as I'm trying to write my own functions to cover everything (my version of arduino)Code:uint16_t adc_result (void){uint16_t result;result = ADCWreturn result;}

author: madires
author_rank: Super Contributor
timestamp: May 04, 2014, 04:43:09 pm
content: Quote from: Simon on May 04, 2014, 04:27:06 pmYep! If you want to get just the lower or upper 8 bit value you use ADCL or ADCH. And if you want to get both combined you'll use ADCW.

author: zapta
author_rank: Super Contributor
timestamp: May 04, 2014, 04:44:14 pm
content: Looking at iom328p.h, ADC and ADCW are the same (see below). As for your code, using ADC or ADCW is not safe if you have ISR that are also accessing some 16 bit registers (even non related). Read the AVR datasheet about the temp i/o buffer. Look at line 34 here are reading a 16 bit register is protected against interruptshttps://github.com/zapta/power-monitors/blob/master/pmon_uno/arduino/hardware_clock.hCode:#ifndef __ASSEMBLER__#define ADC _SFR_MEM16(0x78)#endif#define ADCW _SFR_MEM16(0x78)#define ADCL _SFR_MEM8(0x78)#define ADCL0 0#define ADCL1 1#define ADCL2 2#define ADCL3 3#define ADCL4 4#define ADCL5 5#define ADCL6 6#define ADCL7 7#define ADCH _SFR_MEM8(0x79)#define ADCH0 0#define ADCH1 1#define ADCH2 2#define ADCH3 3#define ADCH4 4#define ADCH5 5#define ADCH6 6#define ADCH7 7

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 04:59:25 pm
content: well that makes the writing of a specific function more useful them because I can disable and then re enable the interrupts.

author: firewalker
author_rank: Super Contributor
timestamp: May 04, 2014, 05:02:25 pm
content: Quote from: Simon on May 04, 2014, 04:32:18 pmI woud addwhile( ADCSRA & (1<<ADSC) ); // Wait until ADC conversion is complete..Code:uint16_t adc_result (void){uint16_t result;while( ADCSRA & (1<<ADSC) ); // Wait until ADC conversion is complete.result = ADCWreturn result;}Personally I use the function below for my projects. Single conversion mode.Code:uint16_t read_ADC(uint8_t ADC_channel){int16_t adc_val;ADMUX = (ADMUX & 0xF0) | (ADC_channel & 0x0F); //Select channel to read.ADCSRA |= (1<<ADSC); // Start ADC conversion.while( ADCSRA & (1<<ADSC) ); // Wait until ADC conversion is complete.adc_val = ADCW;return adc_val;}Alexander.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 05:11:10 pm
content: yes that is a good idea. i have written an ADC setup function too, i just didn't see the point of running the setup every time I want a result so have written a setup function and a ADC result read function. I'm writing functions for most things that require register interaction as it will be easier for me to go through my list of functions and choose the right one than to reread the datasheet every time and it saves me rewriting code all of the time.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 05:17:26 pm
content: another thing i need reminding of is do i need to set the pin as input or output ? I'm disabling the pin's digital register in my channel select function as it saves power apparently

author: free_electron
author_rank: Super Contributor
timestamp: May 04, 2014, 06:04:44 pm
content: multiple ways to skin a cat :method 1:define a 16 bit variable.then create a pointer to that variablewrite adcl to pointer, increment pointer write adh to pointerread 16 bit number.method 2 : create a structure of two bytes. obtain pointer to structure , read a 16 bit number from pointer address.method 3: (my preferred one , but the language / compiler must have such capability)#pragma location = 0x100byte lsb;#pragma location = 0x101byte msb;#pragma location = 0x100int16 sixteenbit;lsb and msb are two bytes , sixteenbit is .. well .. what it says.lsb = 0x55msb = 0xaaprint sixteenbit gives 0xaa55it also works in reverse. throw some 16 bit value in 'sixteenbit' and you can snatch msb and lsb directlythe advantage is that it takes ZERO cpu instructions. it is memory operationsdisadvantage : you need to know a bit about memroy layout of the cpu

author: madires
author_rank: Super Contributor
timestamp: May 04, 2014, 06:30:18 pm
content: Quote from: Simon on May 04, 2014, 05:17:26 pmRight! Simply set the ADC pin to HiZ mode (set the pin in the DDR and PORT registers to zero, or if PUD is set only set DDR to zero).

author: tjaeger
author_rank: Regular Contributor
timestamp: May 04, 2014, 06:54:38 pm
content: Quote from: Simon on May 04, 2014, 04:02:57 pmNot quite. What actually happens is that ADCH will get promoted to an int (which should be a 16-bit signed integer on an 8-bit processor) before the shift. So as long as ADCH < 128 this is actually fine (although I still wouldn't recommend it). If ADCH >= 128, this triggers undefined behavior, which you should never ever let happen (even if you would probably get away with it in this instance). You could do this instead:Code:result |= (uint16_t)ADCH << 8;

author: tjaeger
author_rank: Regular Contributor
timestamp: May 04, 2014, 07:05:09 pm
content: Quote from: free_electron on May 04, 2014, 06:04:44 pmThere's no need to write such platform/compiler-specific and error-prone code in the 21st century. Modern compilers have no trouble translating a little bit of bit-shifting into efficient object code.

author: Simon
author_rank: Unknown
timestamp: May 04, 2014, 07:13:54 pm
content: I'll stick with what I have as I'm yet to understand pointers etc.

author: zapta
author_rank: Super Contributor
timestamp: May 04, 2014, 08:39:52 pm
content: Free electron, on avr the high and low bytes of a register need to be read in a certain order for atomicity. C does not guarantee the that order (it's about reading order, not memory address order)

author: andersm
author_rank: Super Contributor
timestamp: May 04, 2014, 09:09:02 pm
content: Quote from: zapta on May 04, 2014, 08:39:52 pmavr-gcc will read the registers in the correct order.