ATtiny3226 12 bit ADC Max Reading only 4080

I am testing ADC0 on ATtiny3226.

I am referencing the Microchip tech note TB3256:

How to Use the 12-Bit Differential ADC with PGA in Single Mode

I want to optimize the speed of reading a 12 bit single ended, single sample for some crude DSP I want to do on a low frequency audio signal.

I am using VDD as the reference voltage.

When I ground the ADC, if I use a sample duration of above about 6, I get a stable reading of 0. All good.

When I connect the ADC pin directly to VDD with a jumper patch cable, I never quite get 4095, the full 12 bit value.

My 5v supply comes from one of those phone charger blocks and is additionally decoupled with a 470uF Electrolytic and 100nF ceramic capacitor.

The ATtiny3226 has a 100nF decoupling capacitor mounted directly on its DIL carrier board.

I get close - in the range 3980 - 4080.

I believe that the voltage is measured by getting a capacitor to charge up so I would understand if I’m not giving this time to happen.

I’ve tried increasing the sample duration (value of ADC0_CTRLE) all the way to 255 but this seems to have no affect.

I’ve increased the prescaler divider from 2 to 4 but this made no difference.

I don’t see why collecting a set of samples and converting them into a result will make any difference - this will just slow down the final result.

I want the ADC to be able to read 4095 so I can use this to measure when an input is ‘too loud’ and hence, out of range.

Can you think of what I am doing wrong?

Do you have any ideas how to enable the device to measure up to VDD and yield 4095?

I used Arduino IDE to ‘burn the bootloader’ with settings as per comments - specifically F_CPU at 20MHz (and this was tuned with the megaTinyCore tuner).

OK, the code’s a bit clunky but it’s a stripped down version of a much bigger project. Here is the demo code:

// cSpell:includeRegExp CStyleComment
// ATtiny3226_Help

// Arduino Bootloader:
// megaTinyCore
// 20MHz tuned - with megaTinyTuner
// RESET on PB4
// Millis() on TCB1 as I want to use TCA0 for servo (ESC actually) motor control

// Set up of ADC using Microchip Tech Note TB3256 "How to Use the 12-Bit Differential ADC with PGA in Single Mode"

// include external code section
#include <Arduino.h>
#include <math.h>										// from the Microchip Tech Note

// pre compile definitions
#define BAUD 115200										// selected Serial BAUD rate

// pin map
//
// 				ATtiny3226 / ARDUINO Pins
//                          _____
//               VDD      1|*    |20      GND
//               PA4  0   2|     |19  16  PA3 
//               PA5  1   3|     |18  15  PA2 
//               PA6  2   4|     |17  14  PA1 
//               PA7  3   5|     |16  17  PA0 (~RESET/UPDI)
//               PB5  4   6|     |15  13  PC3
//               PB4  5   7|     |14  12  PC2
//               PB3  6   8|     |13  11  PC1 
//               PB2  7   9|     |12  10  PC0 
//               PB1  8  10|_____|11  9   PB0

#define ADC_bp 1	// PA1 as AIN1 for ADC0
#define LED_bp 3	// PA3 

#define TXD_bp 2	// PB2 as TXD for USART0
#define RXD_bp 3	// PB3 as RXD for USART0

#define TIMEBASE_VALUE ((uint8_t) ceil(F_CPU*0.000001))  // from the Microchip Tech Note


// put global variables here:
unsigned long last_print = millis();					// for non blocking delay timer

volatile uint16_t sample_variable; 						// data type from the Microchip Tech Note

// put drivers here:

// put function declarations here:
void system_init();

void setup() {
	// put your setup code here, to run once:
	system_init();
	Serial.begin(BAUD);
	Serial.print(F("\r\n\r\nStart\r\nF_CPU "));
	Serial.println(F_CPU);
}

void loop() {
	// put your main code here, to run repeatedly:
	if ((millis() - last_print > 500)){					// only print a sample every 500ms
		Serial.println(sample_variable);
		last_print = millis();
	}
}

// put function definitions here:
void mcu_init(void){
	/* On AVR devices all peripherals are enable from power on reset, this
	 * disables all peripherals to save power. Driver shall enable
	 * peripheral if used */

	/* Set all PORTA pins to low power mode and input disabled */
	for (uint8_t i = 0; i < 8; i++){ // PA0 - PA7 on ATtiny3226
		*((uint8_t *)&PORTA + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}

	/* Set all PORTB pins to low power mode and input disabled */
	for (uint8_t i = 0; i < 6; i++){ // PB0 - PB5 on ATtiny3226
			*((uint8_t *)&PORTB + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}

	/* Set all PORTC pins to low power mode and input disabled */
	for (uint8_t i = 0; i < 4; i++){ // PC0 - PC3 on ATtiny2326
			*((uint8_t *)&PORTC + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}
}

void disablePORTA(uint8_t pin){
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);						// for pin
	*port_pin_ctrl = (*port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_INPUT_DISABLE_gc;	// disable input
}

void disablePullUpPORTA(uint8_t pin){
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin); // for pin
	*port_pin_ctrl &= ~(1 << PORT_PULLUPEN_bp);				   // remove pull up
}

void pin_init(void){ /* PIN initialization */
	/* See <https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_DirectPortManipulation.md> */
		
	PORTA_DIRSET = (1<<LED_bp);	// Output pin for debug to see ADC running
	disablePullUpPORTA(LED_bp);	// disable pull up on output pin
	disablePullUpPORTA(ADC_bp);	// disable pull up on ADC input pin
	disablePORTA(ADC_bp); 		// disable digital port on ADC input pin
}

void CPUINT_init(void){ // Interrupt initialisation 	
	sei();				// Enable interrupts 
}

void ADC0_init(void){ // ACD initialization 
	ADC0_PGACTRL = ADC_GAIN_1X_gc | ADC_PGABIASSEL_1X_gc | ADC_ADCPGASAMPDUR_6CLK_gc | ( 0 << ADC_PGAEN_bp); // no gain used 
	ADC0_INTCTRL = (1 << ADC_SAMPRDY_bp);										// generate an interrupt on sample ready
	ADC0_CTRLA = ( 1<< ADC_RUNSTDBY_bp) | (1 << ADC_LOWLAT_bp) | (1 << ADC_ENABLE_bp);
	ADC0_CTRLB = ADC_PRESC_DIV2_gc;												// select the fastest 
	ADC0_CTRLC = ADC_REFSEL_VDD_gc  | (TIMEBASE_VALUE << ADC_TIMEBASE_gp);		// set VDD as reference voltage adn set the time base value (from Microchip[ Tech note)
	ADC0_CTRLE = 255; 															// Sample Duration: (10 + 0.5) / 20 MHz    10 is ~min for a stable reading of 0v
	ADC0_CTRLF = (1 << ADC_FREERUN_bp);										// set free running mode
	ADC0_MUXPOS = ADC_MUXNEG_AIN1_gc;											// use AIN1 - PA1 as input
	ADC0_COMMAND = ADC_MODE_SINGLE_12BIT_gc | ADC_START_IMMEDIATE_gc;  			// 12 bit samples and start the first one now
}

void system_init(){							// from Microchip Studio 7 code generator
	mcu_init();
	pin_init();
	ADC0_init();
	CPUINT_init();
}

// put ISR here:
ISR(ADC0_SAMPRDY_vect){
	PORTA_OUTTGL = (1 << LED_bp);		// some debug flashing on PA1
	sample_variable = ADC0_SAMPLE;		// read the sample into a variable

	// ADC0_INTFLAGS = ADC_SAMPRDY_bm; // can clear interrupt by reading the sample so this not needed
}

And the platformio.ini

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:ATtiny3226]
platform = atmelmegaavr
board = ATtiny3226
framework = arduino

build_unflags = -DMILLIS_USE_TIMERA0    ; stop megaTinyCore using TCA0
build_flags = -DMILLIS_USE_TIMERB0      ; have it use TIMERB0 


upload_speed = 115200
upload_port = /dev/ttyUSB0

monitor_port = /dev/ttyUSB1
monitor_speed = 115200

board_build.f_cpu = 20000000L

upload_flags =
    --tool
    uart
    --device
    ATtiny3226
    --uart
    $UPLOAD_PORT
    --clk
    $UPLOAD_SPEED
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE

I changed my ADC0_init() definition to use 8 bit and left adjusted sample and I measured 0v as 0 and VDD as 255. I was also able to reduce the sample duration to 3. Maybe I’ll stick with 8 bit but I’d still be keen to know what may be wrong with my 12 bit sampling regime.

Here’s my modified 8 bit sampling code snippet…

void ADC0_init(void){ // ACD initialization 
	ADC0_PGACTRL = ADC_GAIN_1X_gc | ADC_PGABIASSEL_1X_gc | ADC_ADCPGASAMPDUR_6CLK_gc | ( 0 << ADC_PGAEN_bp); // no gain used 
	ADC0_INTCTRL = (1 << ADC_SAMPRDY_bp);										// generate an interrupt on sample ready
	ADC0_CTRLA = ( 1<< ADC_RUNSTDBY_bp) | (1 << ADC_LOWLAT_bp) | (1 << ADC_ENABLE_bp);
	ADC0_CTRLB = ADC_PRESC_DIV2_gc;												// select the fastest 
	ADC0_CTRLC = ADC_REFSEL_VDD_gc  | (TIMEBASE_VALUE << ADC_TIMEBASE_gp);		// set VDD as reference voltage adn set the time base value (from Microchip[ Tech note)
	ADC0_CTRLE = 3; 															// Sample Duration: (10 + 0.5) / 20 MHz    10 is ~min for a stable reading of 0v
	ADC0_CTRLF = (1 << ADC_FREERUN_bp) | (1 << ADC_LEFTADJ_bp);					// set free running mode and left shift 8 bit result
	ADC0_MUXPOS = ADC_MUXNEG_AIN1_gc;											// use AIN1 - PA1 as input
	ADC0_COMMAND = ADC_MODE_SINGLE_8BIT_gc | ADC_START_IMMEDIATE_gc;  			// 8 bit samples and start the first one now
}