Quantcast
Channel: Hauke’s Projects
Viewing all articles
Browse latest Browse all 41

Using PB6/7 of ATmega328P with Arduino IDE

$
0
0

The Goal

For a small project I used the ATmega328P MCU – and then the small project somewhat exploded and I needed more and more I/O-Pins. Suddenly all but the PB6 and PB7 pins were in use, and I needed exactly two more…

The Arduino IDE did not offer pin numbers for these pins, since they are used for the crystal oscillator on Arduino. My project however did not rely on ultra precise timing, so the internal oscillator was more than enough, leaving the two pins open for other use, but how to address them? I guess with Atmel Studio this would be rather easy, but I started in Arduino IDE and did not want to switch horses…  Browsing the net did bring up many hints (e.g. this and this), but no actual solution that worked in my case. Here is what I finally figured out – which works… kindof. And which is obsolete, because there is already a working…

Solution

Use MiniCore – does it all.

All I wrote below is outdated. DrAzzy gave me the hint to MiniCore in the Arduino forum – thanks again!

Obsolete: My own solution

just left it for completeness… and because it hurts to delete it 🙂

The following steps are necessary to achieve the goal:

  • Install board definitions for barebone ATmega328 et al.
  • Create dedicated pins_arduino.h definitions including the two pins
  • Extend the board definitions to use the modified pins_arduino.h

In detail:

Using ATmega328 and its Cousins with Ardiuno first place

Easy. Use the board definitions from carlosefr (Thanks for this!) – just follow the instructions on the linked github site.

Modifying pins_arduino.h

The actual pin assignments are stored in the file pins_arduino.h which can be found in the subfolders of C:\Program Files (x86)\Arduino\hardware\arduino\avr\variants or – this happened after an update of the libraries and boards for me – of %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\avr\1.6.18\variants (Windows 10 – no idea about *nix, but should be easy enough to locate). So I created my own board variant by making a copy of “standard”, named “standardPB67”. In there, I changed pins_arduino.h as follows (modified lines are highlighted):

/*
  pins_arduino.h - Pin definition functions for Arduino
  Part of Arduino - http://www.arduino.cc/

  Copyright (c) 2007 David A. Mellis, modified by Hauke 2017

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

#ifndef Pins_Arduino_h
#define Pins_Arduino_h

#include <avr/pgmspace.h>

#define NUM_DIGITAL_PINS            22
#define NUM_ANALOG_INPUTS           6
#define analogInputToDigitalPin(p)  ((p < 6) ? (p) + 16 : -1)

#if defined(__AVR_ATmega8__)
#define digitalPinHasPWM(p)         ((p) == 9 || (p) == 10 || (p) == 11)
#else
#define digitalPinHasPWM(p)         ((p) == 3 || (p) == 5 || (p) == 6 || (p) == 9 || (p) == 10 || (p) == 11)
#endif

#define PIN_SPI_SS    (10)
#define PIN_SPI_MOSI  (11)
#define PIN_SPI_MISO  (12)
#define PIN_SPI_SCK   (13)

static const uint8_t SS   = PIN_SPI_SS;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK  = PIN_SPI_SCK;

#define PIN_WIRE_SDA        (18)
#define PIN_WIRE_SCL        (19)

static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;

#define LED_BUILTIN 13

#define PIN_A0   (14)
#define PIN_A1   (15)
#define PIN_A2   (16)
#define PIN_A3   (17)
#define PIN_A4   (18)
#define PIN_A5   (19)
#define PIN_A6   (20)
#define PIN_A7   (21)

static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
static const uint8_t A6 = PIN_A6;
static const uint8_t A7 = PIN_A7;

#define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 23) ? (&PCICR) : ((uint8_t *)0))
#define digitalPinToPCICRbit(p) (((p) <= 7) ? 2 : (((p) <= 15) ? 0 : 1))
#define digitalPinToPCMSK(p)    (((p) <= 7) ? (&PCMSK2) : (((p) <= 15) ? (&PCMSK0) : (((p) <= 23) ? (&PCMSK1) : ((uint8_t *)0))))
#define digitalPinToPCMSKbit(p) (((p) <= 7) ? (p) : (((p) <= 15) ? ((p) - 8) : ((p) - 16))) 

#define digitalPinToInterrupt(p)  ((p) == 2 ? 0 : ((p) == 3 ? 1 : NOT_AN_INTERRUPT))

#ifdef ARDUINO_MAIN

// On the Arduino board, digital pins are also used
// for the analog output (software PWM).  Analog input
// pins are a separate set.

// ATMEL ATMEGA8 & 168 / ARDUINO
//
//                  +-\/-+
//            PC6  1|    |28  PC5 (AI 5/D 21)
//      (D 0) PD0  2|    |27  PC4 (AI 4/D 20)
//      (D 1) PD1  3|    |26  PC3 (AI 3/D 19)
//      (D 2) PD2  4|    |25  PC2 (AI 2/D 18)
// PWM+ (D 3) PD3  5|    |24  PC1 (AI 1/D 17)
//      (D 4) PD4  6|    |23  PC0 (AI 0/D 16)
//            VCC  7|    |22  GND
//            GND  8|    |21  AREF
//     (D 14) PB6  9|    |20  AVCC
//     (D 15) PB7 10|    |19  PB5 (D 13)
// PWM+ (D 5) PD5 11|    |18  PB4 (D 12)
// PWM+ (D 6) PD6 12|    |17  PB3 (D 11) PWM
//      (D 7) PD7 13|    |16  PB2 (D 10) PWM
//      (D 8) PB0 14|    |15  PB1 (D 9) PWM
//                  +----+
//
// (PWM+ indicates the additional PWM pins on the ATmega168.)

// ATMEL ATMEGA1280 / ARDUINO
//
// 0-7 PE0-PE7   works
// 8-13 PB0-PB5  works
// 14-21 PA0-PA7 works 
// 22-29 PH0-PH7 works
// 30-35 PG5-PG0 works
// 36-43 PC7-PC0 works
// 44-51 PJ7-PJ0 works
// 52-59 PL7-PL0 works
// 60-67 PD7-PD0 works
// A0-A7 PF0-PF7
// A8-A15 PK0-PK7


// these arrays map port names (e.g. port B) to the
// appropriate addresses for various functions (e.g. reading
// and writing)
const uint16_t PROGMEM port_to_mode_PGM[] = {
	NOT_A_PORT,
	NOT_A_PORT,
	(uint16_t) &DDRB,
	(uint16_t) &DDRC,
	(uint16_t) &DDRD,
};

const uint16_t PROGMEM port_to_output_PGM[] = {
	NOT_A_PORT,
	NOT_A_PORT,
	(uint16_t) &PORTB,
	(uint16_t) &PORTC,
	(uint16_t) &PORTD,
};

const uint16_t PROGMEM port_to_input_PGM[] = {
	NOT_A_PORT,
	NOT_A_PORT,
	(uint16_t) &PINB,
	(uint16_t) &PINC,
	(uint16_t) &PIND,
};

const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
	PD, /* 0 */
	PD,
	PD,
	PD,
	PD,
	PD,
	PD,
	PD,
	PB, /* 8 */
	PB,
	PB,
	PB,
	PB,
	PB,
	PB,
	PB,
	PC, /* 16 */
	PC,
	PC,
	PC,
	PC,
	PC,
};

const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {
	_BV(0), /* 0, port D */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
	_BV(6),
	_BV(7),
	_BV(0), /* 8, port B */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
	_BV(6), 
	_BV(7),
	_BV(0), /* 16, port C */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
};

const uint8_t PROGMEM digital_pin_to_timer_PGM[] = {
	NOT_ON_TIMER, /* 0 - port D */
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	// on the ATmega168, digital pin 3 has hardware pwm
#if defined(__AVR_ATmega8__)
	NOT_ON_TIMER,
#else
	TIMER2B,
#endif
	NOT_ON_TIMER,
	// on the ATmega168, digital pins 5 and 6 have hardware pwm
#if defined(__AVR_ATmega8__)
	NOT_ON_TIMER,
	NOT_ON_TIMER,
#else
	TIMER0B,
	TIMER0A,
#endif
	NOT_ON_TIMER,
	NOT_ON_TIMER, /* 8 - port B */
	TIMER1A,
	TIMER1B,
#if defined(__AVR_ATmega8__)
	TIMER2,
#else
	TIMER2A,
#endif
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER, /* 16 - port C */
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER,
	NOT_ON_TIMER,
};

#endif

// These serial port names are intended to allow libraries and architecture-neutral
// sketches to automatically default to the correct port name for a particular type
// of use.  For example, a GPS module would normally connect to SERIAL_PORT_HARDWARE_OPEN,
// the first hardware serial port whose RX/TX pins are not dedicated to another use.
//
// SERIAL_PORT_MONITOR        Port which normally prints to the Arduino Serial Monitor
//
// SERIAL_PORT_USBVIRTUAL     Port which is USB virtual serial
//
// SERIAL_PORT_LINUXBRIDGE    Port which connects to a Linux system via Bridge library
//
// SERIAL_PORT_HARDWARE       Hardware serial port, physical RX & TX pins.
//
// SERIAL_PORT_HARDWARE_OPEN  Hardware serial ports which are open for use.  Their RX & TX
//                            pins are NOT connected to anything by default.
#define SERIAL_PORT_MONITOR   Serial 
#define SERIAL_PORT_HARDWARE Serial

#endif

Modifying boards.txt

Board definitions can be found in %LOCALAPPDATA%\Arduino15\packages\atmega\hardware\avr\1.3.0 (Windows 10 – but should again be easy enough to locate on other OS’s). I simply added a section to it by copying the original ATmega328-section and changing the prefix to atmega328PB67 (again, important lines highlighted):

#
# ATmega328/328p + PB6/7
#

atmega328PB67.name=ATmega328/328p+PB6/7

atmega328PB67.bootloader.tool=arduino:avrdude
atmega328PB67.bootloader.unlock_bits=0x3f
atmega328PB67.bootloader.lock_bits=0x0f

atmega328PB67.upload.tool=arduino:avrdude
atmega328PB67.upload.maximum_size=16384
atmega328PB67.upload.maximum_data_size=1024
atmega328PB67.upload.speed=19200
atmega328PB67.build.variant=arduino:standardPB67
atmega328PB67.build.core=arduino:arduino
atmega328PB67.build.board=atmega168

atmega328PB67.menu.clock.internal1=1 MHz (internal)
atmega328PB67.menu.clock.internal1.bootloader.low_fuses=0x62
atmega328PB67.menu.clock.internal1.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.internal1.bootloader.extended_fuses=0x01
atmega328PB67.menu.clock.internal1.build.f_cpu=1000000L

atmega328PB67.menu.clock.internal8=8 MHz (internal)
atmega328PB67.menu.clock.internal8.bootloader.low_fuses=0xe2
atmega328PB67.menu.clock.internal8.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.internal8.bootloader.extended_fuses=0x01
atmega328PB67.menu.clock.internal8.build.f_cpu=8000000L

atmega328PB67.menu.clock.external8=8 MHz (external)
atmega328PB67.menu.clock.external8.bootloader.low_fuses=0xff
atmega328PB67.menu.clock.external8.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.external8.bootloader.extended_fuses=0x01
atmega328PB67.menu.clock.external8.build.f_cpu=8000000L

atmega328PB67.menu.clock.external12=12 MHz (external)
atmega328PB67.menu.clock.external12.bootloader.low_fuses=0xff
atmega328PB67.menu.clock.external12.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.external12.bootloader.extended_fuses=0x01
atmega328PB67.menu.clock.external12.build.f_cpu=12000000L

atmega328PB67.menu.clock.external16=16 MHz (external)
atmega328PB67.menu.clock.external16.bootloader.low_fuses=0xff
atmega328PB67.menu.clock.external16.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.external16.bootloader.extended_fuses=0x01
atmega328PB67.menu.clock.external16.build.f_cpu=16000000L

atmega328PB67.menu.clock.external20=20 MHz (external)
atmega328PB67.menu.clock.external20.bootloader.low_fuses=0xff
atmega328PB67.menu.clock.external20.bootloader.high_fuses=0xdd
atmega328PB67.menu.clock.external20.bootloader.extended_fuses=0x00
atmega328PB67.menu.clock.external20.build.f_cpu=20000000L


# Signature: ATmega328
# Specs: http://www.atmel.com/devices/ATMEGA328.aspx
atmega328PB67.menu.cpu.atmega328=ATmega328
atmega328PB67.menu.cpu.atmega328.build.board=atmega328
atmega328PB67.menu.cpu.atmega328.build.mcu=atmega328

# Signature: ATmega328p
# Specs: http://www.atmel.com/devices/ATMEGA328P.aspx
atmega328PB67.menu.cpu.atmega328p=ATmega328p
atmega328PB67.menu.cpu.atmega328p.build.board=atmega328p
atmega328PB67.menu.cpu.atmega328p.build.mcu=atmega328p

Line 15 refers to the name of the subfolder I created in the previous step.

Using in Arduino IDE

After the modifications, when you restart the Arduino IDE, the new board variant shows up:

ATmega328 PB6 & PB7 board
The new board is available

Works… kindof.

The following works:

#define DigitalPinA 14    // PB6
#define DigitalPinB 15    // PB7
#define DigitalPinC 16    // same pin as A0
#define AnalogPin A1

[...]

void setup() {
  // put your setup code here, to run once:

  pinMode (DigitalPinA, INPUT);
  pinMode (DigitalPinB, INPUT);
  pinMode (DigitalPinC, INPUT);

}

void loop() {
  // put your main code here, to run repeatedly:

  SomeVarA = digitalRead(DigitalPinA);
  SomeVarB = digitalRead(DigitalPinB);
  SomeVarC = digitalRead(DigitalPinC);
  SomeVarAnalog = analaogRead(AnalogPin);

  [...]

}

While the following does not work:

#define DigitalPinA 14    // PB6
#define DigitalPinB 15    // PB7
#define DigitalPinC A0    // should be the same pin as 16
#define AnalogPin A1

[...]

void setup() {
  // put your setup code here, to run once:

  pinMode (DigitalPinA, INPUT);
  pinMode (DigitalPinB, INPUT);
  pinMode (DigitalPinC, INPUT);

}

void loop() {
  // put your main code here, to run repeatedly:

  SomeVarA = digitalRead(DigitalPinA);
  SomeVarB = digitalRead(DigitalPinB);
  SomeVarC = digitalRead(DigitalPinC);
  SomeVarAnalog = analaogRead(AnalogPin);

  [...]

}

When I try to access a pin digitally by its analog reference (A0 in the example above), it does not work. Initially I changed pins_arduino.h with regard to the analog pin references (which originally started at 14):

#define PIN_A0   (16)
#define PIN_A1   (17)
#define PIN_A2   (18)
#define PIN_A3   (19)
#define PIN_A4   (20)
#define PIN_A5   (21)
#define PIN_A6   (22)
#define PIN_A7   (23)

This seemed to be logical to me, but this did not work either: The analog references were not working for analogRead! I can’t work out what I do wrong – so if anyone has an idea, please leave a comment. For the time being just remeber to use the numeric references when using digital I/O, and the Ax references for analog inputs.

Update Stability

Modifying boards.txt as given above may not be stable with regard to updates – make sure you have a copy somewhere. At some point I’ll need to find out how to implement my additions properly, following the standards.

Final Remarks

I only needed ATmega328, but following the above scheme, ATmega8 and ATmega168 should work the same way.

I’ll reach out to the community – perhaps someone with more insights can clear up my confusion with regard to the analog pin reference. And perhaps carlosefr will add the pins to his board definitions.


Viewing all articles
Browse latest Browse all 41

Trending Articles