AVR0/1/XMEGA Virtual Ports - Confusion

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey Ho, hope someone knows.

 

I have been amping my game up with XMEGAS(really starting to like them now that I have gotten past a few issues), and now I am experimenting with a MEga4809 XPLAIN kit and I am trying to understand the VPORTS. 

 

THe Datasheets are COPY/PASTE explanations almost verbatim.  Google searching this site has provided SOME help, but the part that I do not understand is:

 

From the MEGA AVR-0 Family Datasheet:

The Virtual PORT registers map the most frequently used regular PORT registers into the bit-accessible I/O space.
Writing to the Virtual PORT registers has the same effect as writing to the regular registers, but allows for memoryspecific instructions, such as bit-manipulation instructions, which are not valid for the extended I/O memory space
where the regular PORT registers reside.
 

And from the XMEGA E series datasheet:

Virtual port registers allow the port registers to be mapped virtually in the bit-accessible I/O memory space. When this is
done, writing to the virtual port register will be the same as writing to the real port register. This enables the use of I/O
memory-specific instructions, such as bit-manipulation instructions, on a port register that normally resides in the
extended I/O memory space. There are four virtual ports, and so four ports can be mapped at the same time.
 

 

The descriptions are verbatim with the exception of the last sentence added to the XMEGA, which states that thee are 4 VPORTS so four ports can be mapped at the same time.

 

My question(s) are:

 

How does one map a VPORT to a physical port in AVR-0/1?

 

How does one map a VPORT to a physical port on the XMEGA?

 

From my reading, it seems that these VPORTS are geared more for speed/time critical access operations and I may not need them for my diddly purposes, but HEY, I wanna know!

 

Can anyone provide some clarity on the VPORTS.  And some better reading for explanationos(and example code)?

 

Cheerio!

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You don't need to do anything (except for the usual setting of IN/OUT direction) to enable VPORTs, their behavior is built in.

 

VPORTA.OUT = value you want on PORTA

VPORTB.OUT |= 1<<3; // set bit 3 on output port A

uint8_t foo = VPORTA.IN;

Letting the smoke out since 1978

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How does one map a VPORT to a physical port in AVR-0/1?

They're just "there."  The AVR-0/1 has none of its peripherals in the bitmapped IO region, so the GPIO stuff is accessible either via the PORT registers in the memory space, OR via the legacy-style VPORT registers in the low IO space.

 

The larger XMEGA chips ahave enough GPIO ports that they can't ALL have low-IO-space registers assigned to them, so I think they have additional configuration (VPCTRLA, VP0MAP, VP1MAP, etc) to control which PORTs are associated with a particular VPORT.

 

 

Edit: (I guess I shouldn't call them GPIO Ports, since that seems to mean something else (general purpose registers in the IO space.  XMega has 16 of those general purpose registers, and 4 VPORTS.  Mega0 has 4 of the general registers, and 6 VPORTs.)

 

Last Edited: Sat. Jan 18, 2020 - 01:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

digitalDan wrote:

You don't need to do anything (except for the usual setting of IN/OUT direction) to enable VPORTs, their behavior is built in.

 

VPORTA.OUT = value you want on PORTA

VPORTB.OUT |= 1<<3; // set bit 3 on output port A

uint8_t foo = VPORTA.IN;

 

Yes, I sort of arrived at that conclusion in reading the datasheets >>YUP I READ THEM!<<.  The wording though is rather 'interesting' though.

 

westfw wrote:
The larger XMEGA chips ahave enough GPIO ports that they can't ALL have low-IO-space registers assigned to them, so I think they have additional configuration (VPCTRLA, VP0MAP, VP1MAP, etc) to control which PORTs are associated with a particular VPORT.

And I guess I ned to look for the 'family specific' datasheet for a better explanation.  THanks!

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want to end up with sbi/cbi, then the VPORT is your choice as they are in io space. Which also means you need the old method of setting/clearing bits (1 ping only please -if you wanted sbi/cbi).

 

VPORTA.DIR |= 1;
VPORTA.OUT |= 1;
VPORTA.IN |= 1; 

46:    00 9a           sbi    0x00, 0    ; 0
48:    08 9a           sbi    0x01, 0    ; 1
4a:    10 9a           sbi    0x02, 0    ; 2

 

 

If you want a little nicer code to look at, and maybe is a little clearer then the new stuff will work (at the cost of being in mem space, so a sts to store and a little more flash space used)

 

PORTA.DIRSET = 1;
PORTA.OUTSET = 1;
PORTA.OUTTGL = 1;

4c:    81 e0           ldi    r24, 0x01    ; 1
4e:    80 93 01 04     sts    0x0401, r24    ; 0x800401
52:    80 93 05 04     sts    0x0405, r24    ; 0x800405
56:    80 93 07 04     sts    0x0407, r24    ; 0x800407

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

THanks Curt!

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgmdesign wrote:
And some better reading for explanationos(and example code)?
different reading

VPORT registers | megaTinyCore/DirectPortManipulation.md at master · MCUdude/megaTinyCore · GitHub

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I had not seen that MCUdude markdown.

 

NOTE Unlike classic AVRs, setting the bit in PORTx.OUT while the pin is configured as an INPUT will NOT enable the pullups — only the PORTx.PINnCTRL registers can do that. There is no VPORT register that allows for changing the pullup status.

 

That would have caused me hours of frustration.

 

What I have now is untested, but I need to figure out how to set the pullup. I still don't have an m4809 on my bench. Someday, I guess?

 

/* DigitalIO Library
  Copyright (C) 2019 Ronald Sutherland
 
  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
   Hacked from William Greiman to work in C with my board
   Functions are inspired by Wiring from Hernando Barragan
 */
#ifndef PinNum_h
#define PinNum_h

// avr-libc
#include <avr/io.h>
#include <util/atomic.h>
#include <stdbool.h>

// Direction
#define INPUT 0
#define OUTPUT 1

// Configuration
// use PORT_PULLUPEN_bm, PORT_INVEN_bm, and PORT_ISC_enum from iom4809.h

// Level
#define LOW 0
#define HIGH 1

/* The port and pin mask are needed.  iom4809.h has a PORT_struct that will be used to map the register based functions.

typedef struct PORT_struct
{
    register8_t DIR;  // Data Direction 
    register8_t DIRSET;  // Data Direction Set (output)
    register8_t DIRCLR;  // Data Direction Clear (input)
    register8_t DIRTGL;  // Data Direction Toggle 
    register8_t OUT;  // Output Value 
    register8_t OUTSET;  // Output Value Set 
    register8_t OUTCLR;  // Output Value Clear 
    register8_t OUTTGL;  // Output Value Toggle 
    register8_t IN;  // Input Value 
    register8_t INTFLAGS;  // Interrupt Flags 
    register8_t PORTCTRL;  // Port Control 
    register8_t reserved_1[5];
    register8_t PIN0CTRL;  // Pin 0 Control 
    register8_t PIN1CTRL;  // Pin 1 Control 
    register8_t PIN2CTRL;  // Pin 2 Control 
    register8_t PIN3CTRL;  // Pin 3 Control 
    register8_t PIN4CTRL;  // Pin 4 Control 
    register8_t PIN5CTRL;  // Pin 5 Control 
    register8_t PIN6CTRL;  // Pin 6 Control 
    register8_t PIN7CTRL;  // Pin 7 Control 
    register8_t reserved_2[8];
} PORT_t;
*/
struct Pin_Map { // https://yarchive.net/comp/linux/typedefs.html
    PORT_t *port; // pointer to above struct
    uint8_t mask; // Generic Port Pin mask
};

#if defined(__AVR_ATmega4809__)

#define NUM_DIGITAL_PINS 33

/* Each of the AVR Digital I/O ports is associated with registers that have functions. 
The megaAVR 0-series has more going on than the Mega series so look at
https://github.com/MicrochipTech/Getting_Started_with_GPIO
https://www.microchip.com/wwwproducts/en/ATMEGA4809

Wiring uses pin numbers to control their functions. */
const static struct Pin_Map pinMap[NUM_DIGITAL_PINS] = {
    [0] = { .port= &PORTD, .mask= PIN0_bm }, // AIN0
    [1] = { .port= &PORTD, .mask= PIN1_bm } // AIN1
};
/*
    [2] = { .port= &PORTD, .mask= PIN2_bm }, // AIN2
    [3] = { .port= &PORTD, .mask= PIN3_bm }, // AIN3
    [4] = { .port= &PORTD, .mask= PIN4_bm }, // AIN4
    [5] = { .port= &PORTD, .mask= PIN5_bm }, // AIN5
    [6] = { .port= &PORTD, .mask= PIN6_bm }, // AIN6
    [7] = { .port= &PORTD, .mask= PIN7_bm } // AIN7
*/

#endif

void badPinNumber(void)
  __attribute__((error("Bad pin number or it is not a constant")));

// dead code elimination should remove the badPinNumber function call if the pin is valid
// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
static inline __attribute__((always_inline))
void badPinCheck(uint8_t pin_num) {
  if (!__builtin_constant_p(pin_num) || pin_num >= NUM_DIGITAL_PINS) {
     badPinNumber();
  }
}

static inline __attribute__((always_inline))
volatile PORT_t* portReg(uint8_t pin_num) {
  badPinCheck(pin_num);
  return pinMap[pin_num].port;
}

static inline __attribute__((always_inline))
uint8_t pinMask(uint8_t pin_num) {
  badPinCheck(pin_num);
  return pinMap[pin_num].mask;
}

/* read value from pin number return 0 or 1 */
static inline __attribute__((always_inline))
bool digitalRead(uint8_t pin_num) 
{
    return (portReg(pin_num)->IN & pinMask(pin_num));
}

/* set pin value HIGH or LOW */
static inline __attribute__((always_inline))
void digitalWrite(uint8_t pin_num, bool value_for_bit) {
    if (value_for_bit) {
        portReg(pin_num)->OUTSET = pinMask(pin_num);
    }
    else {
        portReg(pin_num)->OUTCLR = pinMask(pin_num);
    }
}

/* toggle pin number 
    Toggling an INVERTED pin causes an edge, which can be detected by 
    all peripherals using the pin, and by interrupts or Events if enabled. */
static inline __attribute__((always_inline))
void digitalToggle(uint8_t pin_num) {
    portReg(pin_num)->OUTTGL = pinMask(pin_num);
}

/* set pin mode INPUT or OUTPUT. */
static inline __attribute__((always_inline))
void pinMode(uint8_t pin_num, bool output_mode) {
    if (output_mode) {
        portReg(pin_num)->DIRSET = pinMask(pin_num);
    }
    else {
        portReg(pin_num)->DIRCLR = pinMask(pin_num);
    }
}

/* pin control settings from iom4809.h
#define PORT_ISC0_bm  (1<<0)  // Input/Sense Configuration bit 0 mask. 
#define PORT_ISC1_bm  (1<<1)  // Input/Sense Configuration bit 1 mask.
#define PORT_ISC2_bm  (1<<2)  // Input/Sense Configuration bit 2 mask.
#define PORT_PULLUPEN_bm  0x08  // Pullup enable bit mask.
#define PORT_INVEN_bm  0x80  // Inverted I/O Enable bit mask.

// ISC 0..5
// Input/Sense Configuration select is in iom4809.h
typedef enum PORT_ISC_enum
{
    PORT_ISC_INTDISABLE_gc = (0x00<<0),  // Interrupt disabled but input buffer enabled 
    PORT_ISC_BOTHEDGES_gc = (0x01<<0),  // Sense Both Edges 
    PORT_ISC_RISING_gc = (0x02<<0),  // Sense Rising Edge
    PORT_ISC_FALLING_gc = (0x03<<0),  // Sense Falling Edge
    PORT_ISC_INPUT_DISABLE_gc = (0x04<<0),  // Digital Input Buffer disabled
    PORT_ISC_LEVEL_gc = (0x05<<0),  // Sense low Level
} PORT_ISC_t;

How to use: set pin 1 as pullup
pinControl(1, PORT_PULLUPEN_bm)

How to use: also set pin 1 as pullup with ISC set for both edges
pinControl(1, PORT_PULLUPEN_bm & PORT_ISC_BOTHEDGES_gc)
*/
static inline __attribute__((always_inline))
void pinControl(uint8_t pin_num, register8_t cntl_mode) {
    // is there a better way?
    if (pinMask(pin_num) == PIN0_bm) portReg(pin_num)->PIN0CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN1_bm) portReg(pin_num)->PIN1CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN2_bm) portReg(pin_num)->PIN2CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN3_bm) portReg(pin_num)->PIN3CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN4_bm) portReg(pin_num)->PIN4CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN5_bm) portReg(pin_num)->PIN5CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN6_bm) portReg(pin_num)->PIN6CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN7_bm) portReg(pin_num)->PIN7CTRL = cntl_mode;
}
#endif  // DigitalPin_h

 

Update: fix &, and add !! to digitalRead
Update^1: rm !!, add pinControl hack so Wiring code will be broken

Update^2: name cntlReg changed to cntlMap

Update^3: cntlMap should not return a pointer to PORT_t

Update^4: I can't make it work?

 

Last Edited: Tue. Jan 21, 2020 - 10:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ron_sutherland wrote:
I still don't have an m4809 on my bench. Someday, I guess?
In PDIP-40 to enable common use cases.

https://www.avrfreaks.net/forum/megaavr-0-series?page=3#comment-2667576

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

>but I need to figure out how to set the pullup

 

You just need to get at the PINCTRL registers.

 

I think that code posted has some problems in some of those functions, or I can't read properly-

portReg(pin_num).OUTSET & pinMask(pin_num);

I'm not sure what that would do, but probably the compiler would complain about it since it does nothing.

I would think an '=' would be what is wanted in some of these. Without a mega4809 it probably doesn't matter :)

 

 

https://www.microchip.com/DevelopmentTools/ProductDetails/DM320115

$15 and a solderless breadboard you have laying around will get you going. Next time ordering some parts (mouser, digikey, mchp, whoever) add it to the cart. Or maybe in your area they have them in vending machines.

 

 

Maybe C++ is not your thing, or maybe it could be, but you can end up with some nice looking code that is both easy to read and compact. 

 

using namespace PINS;   //bring in the pin names/modes (not required)
Port<PF0, OUTL> led;    //led pin output, low is on
//Port<PINS::PF0, PINS::OUTL> //or without 'using namespace'

/*
 26c:    80 e8           ldi    r24, 0x80    ;   //invert
 26e:    80 93 b0 04     sts    0x04B0, r24    ; //pinctrl
 272:    a8 98           cbi    0x15, 0    ; 21  //out=1=off (invert on, so 0=1)
 274:    a0 9a           sbi    0x14, 0    ; 20  //dir=1=output
 */

Port<PF1, INLPU> sw;  //sw pin input, low is on, pullup on
/*
 276:    88 e8           ldi    r24, 0x88    ;   //invert, pullup
 278:    80 93 b1 04     sts    0x04B1, r24    ; //pinctrl
 27c:    a1 98           cbi    0x14, 1    ; 20  //dir=0=input
*/

for(;;){
    led.on( sw.isOn() );
}

/*
 27e:    b1 9b           sbis    0x16, 1 ; //in[1] = sw
 280:    02 c0           rjmp    .+4     ; //sw = off, jmp
 282:    a8 9a           sbi    0x15, 0  ; //sw = on, so set led = on 
 284:    fc cf           rjmp    .-8     ; //start again
 286:    a8 98           cbi    0x15, 0  ; //sw = off, led = off
 288:    fa cf           rjmp    .-12    ; //start again
*/

 

Probably get tired of seeing my links and code, but this is what I use for Port, which can maybe be used for ideas-

https://github.com/cv007/Avr0PlusPlus/blob/master/Port.hpp

Once you get past c++ syntax, its pretty simple code. The vports get used when possible, and the invert bit is useful for several things- you can use to invert your logic so it only has to be done once and your on/off things can act normally, and it also becomes a nice way to toggle an input (I use it the autobaud function to produce my own break so the other end does not need to deal with the break).

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
some problems in some of those functions, or I can't read properly

 

You read just fine, those are problems (it is work in progress), not even compiled (but the first bugs found, much thanks).

 

I am on the 10-year plan for C++, and by then, who knows how much it will have changed, all I really know is that my C++ attempts go bonkers, and I prefer to blame the language rather than my crap skills.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
You just need to get at the PINCTRL registers.

 

Ok, but do I retain the old Wiring behavior or add a new function for PINCTRL? I need to spin loop on that for some time.

Last Edited: Tue. Jan 21, 2020 - 12:15 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Update: fix &, and add !! to digitalRead

 

The return value is a bool, and the compiler knows how to convert things to bool, so !! is probably wasted effort. I would take anytime I see !! as a sign that bool is not being used when it should, and if it already is then there is no need for it (it being the !!).

 

>Ok, but do I retain the old Wiring behavior or add a new function for PINCTRL? I need to spin loop on that for some time.

 

I assume Arduino as the functions look Arduino-ee. I'm not a big fan, but my think matters not. I think the first step is you will need more than a bool for 'output' as there are three states they use- in/out/in-pullup. Then its a matter of finding the pinctrl register needed by the use of the port/pin info you have. Each pin gets its own register starting 16 clicks above the base dir register. Hit the right bit and you get a pullup.

 

I hope 10 years from now we are not still trying to fit everything into original Arduino code, but not my problem so should probably keep silent.

 

 

>I am on the 10-year plan for C++

 

I have a coupon code for the 5-year plan- STARTNOW

(I'm only a few years into my 5-year plan, and will probably end up upgrading/downgrading to the 10-year plan)

Last Edited: Tue. Jan 21, 2020 - 01:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
Or maybe in your area they have them in vending machines.
laugh

Some parts of the world are blessed with electronics distributors.

curtvm wrote:
Probably get tired of seeing my links and code, ...
Please carry on as I'm lacking in C++ knowledge especially embedded C++.

curtvm wrote:
(I use it the autobaud function to produce my own break so the other end does not need to deal with the break).
Auto-baud is one of the features of unified memory AVR; likewise in recent PIC though am unaware of C++ for PIC.

If I correctly read the megaAVR 0-series FDS, there's still a break though with the option that the break is shorter in duration than a complete character other than for LIN.

In the FDS, search for auto-baud or WFB (Wait For Break)

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
maybe in your area they have them in vending machines.

 

I could bicycle down the road a few miles and ask for a chip, but then I would need a wire bonding machine and steady hands, which sadly alcohol/age has robbed me of.

 

curtvm wrote:
The return value is a bool

 

!! seemed like a good idea.

 

more updates on #8

 

 

Last Edited: Tue. Jan 21, 2020 - 02:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>If I correctly read the megaAVR 0-series FDS, there's still a break though with the option that the break is shorter in duration than a complete character other than for LIN.

 

The break for genauto is any H->L->H. The problem is the other end may not even know what a break is or how to produce one (your terminal app), and you can simulate one from the other side with the start bit and then a few choice bytes (UUU) to get the synch byte, but then you get into a whole other problem with the fact that you just used the start bit as a break and are now 1 bit shifted for everything that follows so need a third byte to get the second byte stop bit (assuming you wanted to verify with a second U). I wrote a whole story about it in my old Usart version-

https://github.com/cv007/ATmega4809CuriosityNano/blob/master/Usart.hpp

(bottom of file)

If I remember correctly, the problem with simulating the break with a set of choice bytes (UUU) is they have to go out with no time/space between bytes, and there is no way to know what the pc is doing under the hood even though most likely they go out back to back.

 

Then I got (possibly) smarter, and ended up with-

https://github.com/cv007/Avr0PlusPlus/blob/master/Usart.hpp

(line ~830)

I create the break in software, then wait for a U (synch) which gets the baud set, then wait for another U which will verify. There are timeouts in each step, and the other side just needs to send 2 U's within about 100ms of each other. So simply triggering the break via a pin invert (x2) on rx, means we can wait for the U as long as we want (after the wait-for-break is triggered, it then waits for a start bit, then uses a sync char to set the baud). If resulting baud is a valid value there will be no sync error and then can check for another U to verify. Seems to work as I tested it using a number of baud rates. There probably is a better way, though.

 

Long story just to say the pin invert came in handy to produce the break on rx.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

do I retain the old Wiring behavior or add a new function for PINCTRL?

I really hate that the Arduino team has retained the "digitalWrite(pin, HIGH) turns on pullups if the pinMode is INPUT" behavior.  That was an accident of the original AVR code, and it's made digitalWrite() really ugly (and slower) for all the more modern chips where pullups are enabled via a separate register.   Grr.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It  (code on #8) won't work, I will have to look at it some other day.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Sack of beer, there has to be something in <util/atomic.h> that breaks when __builtin_constant_p() is used; I don't think atomic is needed now. Again this code is not at all tested, but it finally compiled on AS7 (I almost never use that).

 

/*
 * m4809_pin.c
 * Copyright (C) 2019 Ronald Sutherland
 * Created: 12/22/2019 12:47:09 PM
 * Author : rsutherland
 */ 

#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/io.h>

/* DigitalIO Library
   Hacked from William Greiman to work in C with my board
   Functions are inspired by Wiring from Hernando Barragan
 */

// avr-libc
#include <stdbool.h>

// Direction
#define INPUT 0
#define OUTPUT 1

// Configuration
// use PORT_PULLUPEN_bm, PORT_INVEN_bm, and PORT_ISC_enum from iom4809.h

// Level
#define LOW 0
#define HIGH 1

/* The port and pin mask are needed.  iom4809.h has a PORT_struct that will be used to map the register based functions.

typedef struct PORT_struct
{
    register8_t DIR;  // Data Direction
    register8_t DIRSET;  // Data Direction Set (output)
    register8_t DIRCLR;  // Data Direction Clear (input)
    register8_t DIRTGL;  // Data Direction Toggle
    register8_t OUT;  // Output Value
    register8_t OUTSET;  // Output Value Set
    register8_t OUTCLR;  // Output Value Clear
    register8_t OUTTGL;  // Output Value Toggle
    register8_t IN;  // Input Value
    register8_t INTFLAGS;  // Interrupt Flags
    register8_t PORTCTRL;  // Port Control
    register8_t reserved_1[5];
    register8_t PIN0CTRL;  // Pin 0 Control
    register8_t PIN1CTRL;  // Pin 1 Control
    register8_t PIN2CTRL;  // Pin 2 Control
    register8_t PIN3CTRL;  // Pin 3 Control
    register8_t PIN4CTRL;  // Pin 4 Control
    register8_t PIN5CTRL;  // Pin 5 Control
    register8_t PIN6CTRL;  // Pin 6 Control
    register8_t PIN7CTRL;  // Pin 7 Control
    register8_t reserved_2[8];
} PORT_t;
*/
struct Pin_Map { // https://yarchive.net/comp/linux/typedefs.html
    PORT_t *port; // pointer to above struct
    uint8_t mask; // Generic Port Pin mask
};

#if defined(__AVR_ATmega4809__)

#define NUM_DIGITAL_PINS 33

/* Each of the AVR Digital I/O ports is associated with registers that have functions.
The megaAVR 0-series has more going on than the Mega series so look at
https://github.com/MicrochipTech/Getting_Started_with_GPIO
https://www.microchip.com/wwwproducts/en/ATMEGA4809

Wiring uses pin numbers to control their functions. */
const static struct Pin_Map pinMap[NUM_DIGITAL_PINS] = {
    [0] = { .port= &PORTD, .mask= PIN0_bm }, // AIN0
    [1] = { .port= &PORTD, .mask= PIN1_bm } // AIN1
};
/*
    [2] = { .port= &PORTD, .mask= PIN2_bm }, // AIN2
    [3] = { .port= &PORTD, .mask= PIN3_bm }, // AIN3
    [4] = { .port= &PORTD, .mask= PIN4_bm }, // AIN4
    [5] = { .port= &PORTD, .mask= PIN5_bm }, // AIN5
    [6] = { .port= &PORTD, .mask= PIN6_bm }, // AIN6
    [7] = { .port= &PORTD, .mask= PIN7_bm } // AIN7
*/
#else
#   error this is for a mega4809
#endif

void badPinNumber(void)
  __attribute__((error("Bad pin number or it is not a constant")));

// dead code elimination should remove the badPinNumber function call if the pin is valid
// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
static inline __attribute__((always_inline))
void badPinCheck(uint8_t pin_num) {
  if (!__builtin_constant_p(pin_num) || pin_num >= NUM_DIGITAL_PINS) {
     badPinNumber();
  }
}

static inline __attribute__((always_inline))
volatile PORT_t* portReg(uint8_t pin_num) {
  badPinCheck(pin_num);
  return pinMap[pin_num].port;
}

static inline __attribute__((always_inline))
uint8_t pinMask(uint8_t pin_num) {
  badPinCheck(pin_num);
  return pinMap[pin_num].mask;
}

/* read value from pin number return 0 or 1 */
static inline __attribute__((always_inline))
bool digitalRead(uint8_t pin_num)
{
    return (portReg(pin_num)->IN & pinMask(pin_num));
}

/* set pin value HIGH or LOW */
static inline __attribute__((always_inline))
void digitalWrite(uint8_t pin_num, bool value_for_bit) {
    if (value_for_bit) {
        portReg(pin_num)->OUTSET = pinMask(pin_num);
    }
    else {
        portReg(pin_num)->OUTCLR = pinMask(pin_num);
    }
}

/* toggle pin number
    Toggling an INVERTED pin causes an edge, which can be detected by
    all peripherals using the pin, and by interrupts or Events if enabled. */
static inline __attribute__((always_inline))
void digitalToggle(uint8_t pin_num) {
    portReg(pin_num)->OUTTGL = pinMask(pin_num);
}

/* set pin mode INPUT or OUTPUT. */
static inline __attribute__((always_inline))
void pinMode(uint8_t pin_num, bool output_mode) {
    if (output_mode) {
        portReg(pin_num)->DIRSET = pinMask(pin_num);
    }
    else {
        portReg(pin_num)->DIRCLR = pinMask(pin_num);
    }
}

/* pin control settings from iom4809.h
#define PORT_ISC0_bm  (1<<0)  // Input/Sense Configuration bit 0 mask.
#define PORT_ISC1_bm  (1<<1)  // Input/Sense Configuration bit 1 mask.
#define PORT_ISC2_bm  (1<<2)  // Input/Sense Configuration bit 2 mask.
#define PORT_PULLUPEN_bm  0x08  // Pullup enable bit mask.
#define PORT_INVEN_bm  0x80  // Inverted I/O Enable bit mask.

// ISC 0..5
// Input/Sense Configuration select is in iom4809.h
typedef enum PORT_ISC_enum
{
    PORT_ISC_INTDISABLE_gc = (0x00<<0),  // Interrupt disabled but input buffer enabled
    PORT_ISC_BOTHEDGES_gc = (0x01<<0),  // Sense Both Edges
    PORT_ISC_RISING_gc = (0x02<<0),  // Sense Rising Edge
    PORT_ISC_FALLING_gc = (0x03<<0),  // Sense Falling Edge
    PORT_ISC_INPUT_DISABLE_gc = (0x04<<0),  // Digital Input Buffer disabled
    PORT_ISC_LEVEL_gc = (0x05<<0),  // Sense low Level
} PORT_ISC_t;

How to use: set pin 1 as pullup
pinControl(1, PORT_PULLUPEN_bm)

How to use: also set pin 1 as pullup with ISC set for both edges
pinControl(1, PORT_PULLUPEN_bm & PORT_ISC_BOTHEDGES_gc)
*/
static inline __attribute__((always_inline))
void pinControl(uint8_t pin_num, register8_t cntl_mode) {
    // is there a better way?
    if (pinMask(pin_num) == PIN0_bm) portReg(pin_num)->PIN0CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN1_bm) portReg(pin_num)->PIN1CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN2_bm) portReg(pin_num)->PIN2CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN3_bm) portReg(pin_num)->PIN3CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN4_bm) portReg(pin_num)->PIN4CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN5_bm) portReg(pin_num)->PIN5CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN6_bm) portReg(pin_num)->PIN6CTRL = cntl_mode;
    if (pinMask(pin_num) == PIN7_bm) portReg(pin_num)->PIN7CTRL = cntl_mode;
}

int main(void)
{
    pinMode(0,OUTPUT);
    digitalWrite(0,HIGH);

    while (1)
    {
	    _delay_ms(500);
	    digitalToggle(0);
    }
}

 

Update: The compile problem I was having on Linux seems to involve CPPFLAGS in Makefile and the use of a side loaded tool-chain, the work around (for now) can be seen hear:

 

https://github.com/epccs/PiUpdi/...

Last Edited: Wed. Jan 22, 2020 - 10:12 PM