How to refer to a pin? (AVR-0/AVR-1)

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

If using Microchip/Atmel toolsets, what is the correct way to refer to a pin (I'm using ATMega4809)?

What is  more readable? It's just a matter of personal taste?

 

Options:

 

    // Set pins 0 and 3 of PORTA as outputs
    PORTA.DIRSET = (1 << 0) | (1 << 3);     // option 1
    PORTA.DIRSET = _BV(PIN0) | _BV(PIN3);   // option 2
    PORTA.DIRSET = PIN0_bm | PIN3_bm;       // option 3
    ...                                     // none of the above?

 

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

El Tangas wrote:
It's just a matter of personal taste?
It is indeed. Personally I think I'd still be tempted by the (1 << 3) style though you missed one:

PORTA.DIRSET = 0x01 | 0x08;    

Some of us still "see" binary in hex digits so I would equally read this as bits 0 and 3.

 

Of course the OR there just means this is:

PORTA.DIRSET = 0x09;   

I think most programmers see 2 bits (0 and 3) set here?

 

Or how about (mad idea!!) decimal:

PORTA.DIRSET = 123;    

clearly different bits - but which ones (personally I haven't a clue!!).

 

GCC and later C standards of course support:

PORTA.DIRSET = 0b00001001;  

and to be honest that probably maps closer (in one's minds eye) to the actual schematic - I can almost see the wires connected to A0 and A3 here !! :-)

 

PS When Xmega came in an and Atmel introduced _bp and (possibly more useful) _bm I was almost persuaded but PIN3_bm still looks like "too much noise".

 

EDIT: I keep editing this as I have new thoughts. You see:

PORTA.DIRSET = _BV(PIN0) | _BV(PIN3);  

is kind of pleasant in an odd kind of a way but surely the "PIN" in there is superfluous noise? I think I could be tempted by:

PORTA.DIRSET = _BV(0) | _BV(3);  

clear and succinct.

 

HOWEVER here's another idea. What about:

#define RED_LED _BV(3)
#define GREEN_LED _BV(0)

PORTA.DIRSET = RED_LED | GREEN_LED;
...
PORTA.OUTSET = RED_LED;

That tells the reader MUCH more about what this line is achieving!

Last Edited: Thu. Oct 18, 2018 - 12:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You're right, that last option seems the best, self documenting the code.

Last Edited: Thu. Oct 18, 2018 - 12:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Still leaves the question of how you refer to it in the #defines. Is it going to be (1 << n) or _BV(n) or "n"_bm or 0xmm with bit n set or what exactly? But as it's now isolated to one line in one .h file it's not like the code is "peppered" with this construct any more.

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

El Tangas wrote:

You're right, that last option seems the best, self documenting the code.

Still there is a fourth one where in

PORTA.DIRSET = (1 << 0) | (1 << 3);

instead numbers you use bit position defines. You can define your own like bpSPI_CS for example. .inc files have them.

 

When a field width is larger than one bit, then I like using bit position with a bit mask like this:

REGISTER = (bmA << bpA | bmB << bpB);

I am not sure the parentheses are needed.

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

El Tangas wrote:

If using Microchip/Atmel toolsets, what is the correct way to refer to a pin (I'm using ATMega4809)?

// Set pins 0 and 3 of PORTA as outputs
    PORTA.DIRSET = (1 << 0) | (1 << 3);

...

(*(volatile uint8_t *)(0x0401)) = 0x05;

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

theusch wrote:
(*(volatile uint8_t *)(0x0401)) = 0x05;

 

Lol, c'mon, those poor interns at Micromel went to all that trouble to write the include files, the least we can do is use them...

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

theusch wrote:
(*(volatile uint8_t *)(0x0401)) = 0x05;

I think you mean 9.

But the underlaying problem is the use of magic numbers in the code.

I haven't used any of the newer parts yet (and probably won't), so I haven't used the structure notation for register access.

 

The _BV( ) macro seems to be deprecated by most, as it is hardly any shorter / more readable than the shift notation.

And you will have to look up what _BV( ) does the first time, while << is standard C notation, and it therefore has my preference.

 

Back to the magic numbers.

I gather all the magic numbers for I/O pins in the file main.h, which gets included in all other source files.

My goal was to make stuff movable by editing the definitions in main.h.

Therefore I make my own macro names for all DDR, PORT, PIN and BIT names.

The section for "Mumarnet", which uses a UART + RS485 driver looks like this:

/*===========================================================================
 Mumarnet over RS-485.
---------------------------------------------------------------------------*/
/* PORT and BIT for the rs485 Transmitter enable signal
PD4 is the default for the MumarBase pcb. */
#define NETWORK_RS485_DDR		DDRD
#define NETWORK_RS485_PORT		PORTD
#define NETWORK_RS485_PIN		PIND
#define NETWORK_RS485_ENABLE_BIT	(1<<4)

// PORT and BIT names for the UART. Usually PD0 and PD1.
#define NETWORK_UART_DDR		DDRD
#define NETWORK_UART_PORT		PORTD
#define NETWORK_UART_PIN		PIND
#define NETWORK_UART_RXD_BIT		(1<<0)
#define NETWORK_UART_TXD_BIT		(1<<1)

And then on onter places you will find in the source code constructs like:

#ifndef __linux__
//============================================================ Not for Linux.
void TMumar::Init( void) {					DEBUG(1);
// Sets the Hardware to a state where packets can be properly received.
// Because this function is so fast it is called before sending each packet.
	DEBUG_OUTPUT_ENABLE;
	NETWORK_UART_PORT |= NETWORK_UART_RXD_BIT;	// RxD Pullup enable.
	NETWORK_UART_DDR &= ~NETWORK_UART_RXD_BIT;	// RxD is now an input.

	NETWORK_UART_PORT |= NETWORK_UART_TXD_BIT;// TxD pin Idle State = high.
	NETWORK_UART_DDR  |= NETWORK_UART_TXD_BIT;// TxD is always an output.

	// Set the RS485 chip into receiving mode ( Pin is output and logic 0).
	NETWORK_RS485_PORT &= ~NETWORK_RS485_ENABLE_BIT;
	NETWORK_RS485_DDR  |= NETWORK_RS485_ENABLE_BIT;

	UBRRL = UART_BAUD_SELECT;	// Set Uart Baud Rate Register.
}
...

 

This has a few advantages for me.

1). As noted before, no more magic numbers in the rest of the source code.

2). All I/O pin definitions gathered together makes is easy to reuse the source code files between projects.

3). I can do a text search (Over all project source files) to get an instant overview of where a particular I/O pin is manipulated.

 

I have been thinking about ways to make (most of) the source code (of self written libraries) compatible between AVR and ARM Cortex M3 (Blue pill), but I haven't been able to find an acceptable way yet.

The STM32F103C8T6 has bit set and reset registers, a nice feature, but with very little overlap with AVR I/O ports.

 

A beautifull way to add abstraction would be to work with templates, which gets abstracted away by the preprocessor, but they are a bitch to write and debug, and I've decided they are too much effor to implement for my projects.

When templates have been written properly you can write code like:

Led_Blue = true;

Led_Blue = false;

Led_Blue = toggle;

and each line will compile into the optimum asm representation.

 

Yet another way is to use macro's or functions. Someting like:

#define BIT_SET( A, B) {A |= B}

With such macro's (or functions) you could make code portable between different uC architectures.

Macro's like these used to be standard for GCC in AVR before the compiler was able to work directly with port registers.

On my system I can find the remnants of these macro's in:

/usr/lib/avr/include/compat/deprecated.h

An excerpt of that file:

#define inb(port) (port)

/**
   \ingroup deprecated_items
   \def outb(port, val)
   \deprecated

   Write \c val to IO port \c port.
*/
#define outb(port, val) (port) = (val)

/**
   \ingroup deprecated_items
   \def sbi(port, bit)
   \deprecated

   Set \c bit in IO port \c port.
*/
#define sbi(port, bit) (port) |= (1 << (bit))

/**
   \ingroup deprecated_items
   \def cbi(port, bit)
   \deprecated

   Clear \c bit in IO port \c port.
*/
#define cbi(port, bit) (port) &= ~(1 << (bit))

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

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

That "deprecated" is because previously they were implemented as asm() before the compiler/linker were smart enough to recognize low addresses where IN/OUT/SBI/CBI could be used. In the latest deprecated.h they are just implemented as "plain C" in the sure and certain knowledge that the compiler will use the best sequence achievable anyway.

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

In this recent thread:

https://www.avrfreaks.net/forum/easiest-blinky-ever-making-mega4809-nano-blink-using-codevision

Brian is verry happy with Codevision and his new pile of ATmega4809-Nanos.

 

There he uses a TGLBIT( ) macro, which contents are probably similar to something I posted in #8

clawson looked a bit further in the code ans suggested

PORTF.OUTTGL = (1 << 5);

This probably means that that (new) AVR has hardware registers for toggling I/O bits, and also for setting or clearing multible bits.

I might be wrong here because I have not looked in the datasheet, but I am a bit familiar with STM32 which has similar support for bit manipulation in I/O registers.

 

In the "arduino" world (which is a strange mix of C and C++ which they insist on calling "wiring") they use functions to abstract manipulation of I/O pins.

These functions do a lot of error checking in the background. For example when a pin is using PWM and you set that output pin high, then that function first turns the PWM for that pin of.

This might be a nice feature for beginners, but it makes those functions horribly slow.

If you use those functions for software SPI you will be lucky to get a few kilobit per second out of it.

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

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

Well even old style tiny/mega as far back as 2005 (mega48 onwards) have the ability to toggle in hardware (write 1s to PIN) but the Xmega and derivatives then formalisedthis with xxxTGL registers.

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

Paulvdh wrote:
This probably means that that (new) AVR has hardware registers for toggling I/O bits, and also for setting or clearing multible bits.

 

That's right, there are set/clear/toggle sub-registers for the direction and output registers. This allows setting, clearing or toggling multiple pins of the same port as an atomic operation, while leaving the other pins in the previous state.

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

Paulvdh wrote:
In this recent thread:

???  Is the code used in that other thread pertinent to the discussion here?  Isn't this one just about the style, for a single construct?  I don't see where different implementations of toggling an output pin is directly relevant.   And the actual code in that other thread is nearly window dressing; the thrust was the highly integrated dev board; skeleton-building assistance with CodeVisionAVR Wizard; method of stuffing the resulting build output into the target.  Or did I miss something in both threads?

 

Does it matter for the quickest-put-tegether app whether the actual toggle code is optimal?  I expressed the same in the current Mega32 sine thread where the OP was taken to task for having an SRAM table versus a flash table.  Yet the thread went on to talk about the fastest implementation, and flash is slower than SRAM.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Sat. Oct 20, 2018 - 12:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Paulvdh wrote:
There he uses a TGLBIT( ) macro, which contents are probably similar to something I posted in #8

So, let's keep digging a bit more. "Probably", eh?

 

Pertinent to this thread, I noticed that in CV's iobits.h that helper macros are used internally...

#define __BM(b) (1 << (b))
#define __BV(b) __BM(b)

But let's check the TGLBIT() implementation for an Xmega-style device:

// toggle port bit b, atomic
#define TGLBIT(port,b) {__CONCAT(port,TGL) = __BM(b);}

 

I don't have a suitable CV version at home to check actual '4809 implementation.

 

I see no reference to toggling in #8.

 

Probably the old guy's eyes are reading too much into this.  No mention of toggle in #8 but a mention of "horribly slow".  Then an examination of the '4809 test program.   Was there a diff erent point in bringing it up?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Sat. Oct 20, 2018 - 01:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

But let's check the TGLBIT() implementation for an Xmega-style device:

// toggle port bit b, atomic
#define TGLBIT(port,b) {__CONCAT(port,TGL) = __BM(b);}

 

I don't have a suitable CV version at home to check actual '4809 implementation.

 

 

Given how the registers are named in "iom4809.h", that macro would work the same for a Mega4809 as for a xmega.

 

typedef struct PORT_struct
{
    register8_t DIR;  /* Data Direction */
    register8_t DIRSET;  /* Data Direction Set */
    register8_t DIRCLR;  /* Data Direction Clear */
    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 */
    ...

 

But in terms of readability, I'm not so sure something like

    SETBIT(PORTA.OUT,5);

is much better than

    PORTA.OUTSET = (1 << 5);

Maybe, if you are used to CV macros.

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

El Tangas wrote:
But in terms of readability, I'm not so sure something like SETBIT(PORTA.OUT,5); is much better than PORTA.OUTSET = (1 << 5); Maybe, if you are used to CV macros.

It doesn't really have to do with CV macros.  Re the readability:  If one chooses to go the e.g. SETBIT() macro route, then the same syntax applies to Tiny and Mega and Xmega and Xtiny and Mega0 and perhaps others, right?

 

BTW, you gurus will need to help with the CV example.  Shouldn't it be TGLBIT (PORTA,5) and not TGLBIT (PORTA.OUT,5) ?  [the CV Help implies using the .OUT, right?]

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

No, that macro definition implies that the suffix TGL is just appended at the end of whatever you put in the macro (I don't use CV, but I'm assuming the __CONCAT macro glues stuff together, as usual).

So, you have to feed it "PORTA.OUT" and the result is PORTA.OUTTGL, which is the toggle register. If you used just "PORTA" the macro would convert into PORTATGL, which doesn't correspond to a valid definition for the Mega4809.

 

And this is a good thing, because like that you can also use PORTA.DIR with the same macro if you want to toggle it between input and output for some reason.

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

I think you should use the Aussie way to refer to a pin, if it's male then you refer to it as Mate, if female you you refer to it as Love. The later may no longer be politically correct though.

 

It works with people here when you forget their names.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly