ATSAMD21 pin mapping / battery measurement / variant.cpp

I have a custom PCB, powered by a LiPo battery.

It includes a voltage divider that cuts the battery voltage in half and delivers it to physical pin 8, which I think I can refer to as ADC_BATTERY.
(Am I wrong there? Is that the whole problem? ADC_BATTERY prints as "33". )

That pin, physical pin 8, reads 1.6 V (on my multimeter) from my mostly-drained 3.2V LiPo. All as expected.
But this sketch gives me a sensor value of 1023:

void setup() {
  Serial1.begin(9600);
  analogReference(AR_DEFAULT); // 3.3V I think 
}

void loop() {
  sensorValue = analogRead(ADC_BATTERY);
  // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 4.3V):
  float voltage = sensorValue * (4.3 / 1023.0);
  Serial1.println(sensorValue);
  Serial1.print(voltage);  Serial1.println("V");
}

I would expect a much smaller sensor reading since the battery is pretty drained (on purpose).

I've tried changing analogreference() to various things - no joy.
Any advice welcome, and thank you all very much!

I didn't specify: This is a 48-pin ATSAMD21G18 chip, diagram attached.

Physical pin 8 is labeled PB09, but

  analogRead(PB09);

says 'PB09 was not declared". So I'm using analogRead(ADC_BATTERY), which compiles, but I'm not really sure which physical pin ADC_BATTERY maps to.

I am unclear in general on how to refer to physical pin X when I'm writing software.
Most of the pinout diagrams for this ATSAMD21G18 chip are for breakout boards, not for the bare chip itself, adding uncertainty about what the physical pin numbers are for: the chip, or the breakout board?

I found variant.cpp in my Arduino source code, which looks like this:

const PinDescription g_APinDescription[]=
{ // ----------------------------------- Arduino compatible pins -----------------------------------
  // Digital low D0..D7
  { PORTA, 11, PIO_SERCOM, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel19, PWM1_CH1, TCC1_CH1, EXTERNAL_INT_11 }, // p16 Serial1 RX
  { PORTA, 10, PIO_SERCOM, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel18, PWM1_CH0, TCC1_CH0, EXTERNAL_INT_10 }, // p15 Serial1 TX
  { PORTA, 14, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM3_CH0, TC3_CH0, EXTERNAL_INT_14 }, // p13
  { PORTA,  9, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel17, PWM0_CH1, TCC0_CH1, EXTERNAL_INT_9 }, // p14
  etc.

But when I try to read this, I don't know if this physical pin number is the row's order in the array, or the second column.
The first line, for example, starting with PORTA,11,PIO_SERCOM:
is that physical pin 1, because it's element #1 in the array?
Or is it physical pin 11, because the second column has an 11 in it?

And what name do I use when I'm writing code in the Arduino IDE:
PORTA?
PORTA_11?
PIO_SERCOM?

Hi Imorris99,

To measure the battery voltage on an analog input, it's necessary use a hardware resistive voltage divider, to divide down the battery's voltage to within acceptable limits for your microcontroller's ADC, in this case 3.3V. The voltage of the battery itself is then reconstructed by creating a simple mathematical model of you voltage divider in software.

At the beginning of your sketch add (with your resistor values):

const float REFERENCE_VOLTS = 3.3f;                         // Analog reference voltage
const float R1 = 6800.0f;                                   // Resistor values of voltage divider
const float R2 = 1000.0f;
const float RESISTOR_FACTOR = 4095.0f * (R2 / (R1 + R2));   // Calculate the resistor factor using 12-bit resolution (2^12-1=4095)

In the setup() portion of your code, set the ADC resolution to 12-bits:

analogReadResolution(12);

Also, remove the analogReference() line.

Then in the loop() portion of your code, read the analog pin and reconstruct the battery voltage:

int batteryPinVoltage = analogRead(0);                                      // Read the analog A0 pin
float volts = (batteryPinVoltage / RESISTOR_FACTOR) * REFERENCE_VOLTS;      // Calculate the battery voltage

OK, thanks.
Remember, my multimeter measures 1.6 V on physical pin 8, which this PCB uses for battery level detection.

I used float, and set 12-bit resolution as MartinI suggests.

Now when I use analogRead(0), I get
Voltage 2.00, Value 1239. ("Value" is the result from analogRead(). Full sketch below.)

When I do that, and use analogRead(ADC_BATTERY) (as my board designer suggests), I get
Voltage 6.60, Value 4095

Clearly, neither one is the correct battery reading.

But does analogRead(0) really refer to MY battery reading PIN, pin number 8? I don't think so.
This comes back to my original question:

How do I refer to physical pin 8 on my ATSAMD21G18?

Here's the whole sketch:

const float REFERENCE_VOLTS = 3.3f;                         // Analog reference voltage
const float R1 = 1000.0f;                                   // Resistor values of voltage divider
const float R2 = 1000.0f;
const float RESISTOR_FACTOR = 4095.0f * (R2 / (R1 + R2));   // Calculate the resistor factor using 12-bit resolution (2^12-1=4095)

void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial1.begin(9600);
  analogReadResolution(12);
  delay(100);
}
// the loop routine runs over and over again forever:
void loop() {
  int batteryPinVoltage = analogRead(0);                                      // Read the analog A0 pin
  float volts = (batteryPinVoltage / RESISTOR_FACTOR) * REFERENCE_VOLTS;      // Calculate the battery voltage
  // print out the value you read:
  Serial1.print("Voltage  ");   Serial1.print(volts);
  Serial1.print(",  Value "); Serial1.println(batteryPinVoltage);
}

So, again, how do I refer to physical pin 8 on my ATSAMD21G18?

So, again, how do I refer to physical pin 8 on my ATSAMD21G18?

I'm assuming your "variant.cpp" code snippet is from the Arduino Zero.

On the SAMD21 physical pin 8 is port pin PB09. Port pin PB09 is configured as Arduino analog pin A2 in the "variant.cpp" file.

Therefore you need to insert 2 or A2 as an argument for the analogRead() function:

int batteryPinVoltage = analogRead(2);

I agree: physical pin 8 is A2.

Here are the rules for mapping a chip's physical pin number to its Arduino name. This is for an ATSAMD (aka ARM Cortex) chip. I hope this benefits some others. It took me a long time to understand this three-level mapping. (Rules are probably valid for normal ATMEGA and Arduino boards if you skip steps 1 and 2. )

  1. Install a SAMD Core to your ARduino IDE. This will include a variant.cpp file, and a variant.h file.

  2. In the Arduino IDE, click Tools, Board, and select the Board Type that matches your hardware. Are you using a custom PCB or a bare-bones microprocessor? Then I'm not totally sure about which board type to pick. Talk to your hardware designer.

  3. Look at the "Port function Multiplexing" section in the datasheet for your chip (chapter 6, here). The 1st column defines the PHYSICAL pin number on the microprocessor chip. ("First" - well, pick the one of the three leftmost columns that matches your microprocessor type: there's a column for SAMD21E, one for SAMD21G, and one for SAMD21J.) The fourth column in the datasheet gives the Port and the Pin for that physical pin, e.g. PB09 for physical pin 8 on the SAMD21G. See attached image.

  4. Look in variant.cpp, the PinDescription array: the first two columns in each line of the array mean the Port and Pin from the datasheet: e.g. PORTB, pin 09 means the datasheet's I/O pin PB09.

  5. When you're writing Arduino code, the Pin number you need is the line number in this array ,indexed from zero.
    Here's a snippet indicating that SAMD21G physical pin 8 == PB09 == PORTB, 09 == Arduino pin number 16:

// From variant.cpp:
// Rows 14..19 - Analog pins  
 { PORTA,  2, PIO_ANALOG, PIN_ATTR_ANALOG, ADC_Channel0, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_2 }, // ADC/AIN[0]
 { PORTB,  8, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel2, PWM4_CH0, TC4_CH0, EXTERNAL_INT_8 }, // ADC/AIN[2]
 { PORTB,  9, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel3, PWM4_CH1, TC4_CH1, EXTERNAL_INT_9 }, // ADC/AIN[3]
  1. As a convenience, or just to add another layer of confusion, the variant.h file that came with your SAMD core defines some names like A2 you can use instead of the pin number. Most examples do use these names instead of the numbers.
#define PIN_A2               (16ul)   // "ul" means the unsigned long datatype.  Just a number
static const uint8_t [b]A2[/b]  = PIN_A2 ;

So I can say analogRead(A2), and I'm reading physical pin 8.

To map between the physical, port and Arduino pins, I often find the board's schematic diagram is really useful.

Here's a link to the original Arduino Zero schematic: Schematic error on Zero Digital-4 and Digital-2 - Arduino Zero - Arduino Forum.