Any tips for labeling pins with #define

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

My tried a true method for the mega series was this:

 

#define PIEZO_PORT     PORTB

#define PIEZO_POS_PIN    0

#define PIEZO_NEG_PIN    1

 

Then in code...

 

PIEZO_PORT=_BV(PIEZO_POS_PIN);

DDR(PIEZO_PORT)=_BV(PIEZO_POS_PIN) | _BV(PIEZO_NEG_PIN);

 

Because I use these macros as well:

 

#define PIN(x) (*(&x - 2))           // Address of Data Direction Register of Port X
#define DDR(x) (*(&x - 1))           // Address of Input Register of Port X

 

So now, I'm using an atmega4809 -

 

#define SPI_PORT         PORTE
#define MOSI_PIN         0
#define MISO_PIN         1
#define SCK_PIN          2
#define SS_PIN           3

 

The dot notation allows me to replace PORTE with SPI_PORT, but the PIN0CTRL is causing me a bit of trouble.  Should I also define a MOSI_PIN_CTRL as PIN0CTRL ?

 

  SPI_PORT.PIN0CTRL=PORT_PULLUPEN_bm;                      //PE0 mosi enable pullup
  SPI_PORT.DIRSET=_BV(MISO_PIN);                                  //PE1 miso output
  SPI_PORT.PIN2CTRL=PORT_PULLUPEN_bm;                      //PE2 sck enable pullup
  SPI_PORT.PIN3CTRL=PORT_ISC_RISING_gc | PORT_PULLUPEN_bm; //PE3 SS interrupt rising edge and enable pullup
  SPI0_CTRLA=SPI_ENABLE_bm;
  SPI0_INTCTRL=SPI_IE_bm;

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

Defines always start to get ugly real fast, but if using defines I would just eliminate defining the individual pins as they are a fixed set for a pin group. Just go one step above that where you can setup a group of pins by just specifying which group you want (and probably add the portmux setting in there somewhere)-

#define SPI_PINS_SLAVE(p,a,b,c,d) \
  p.PIN##a##CTRL=PORT_PULLUPEN_bm;  \
  p.DIRSET=_BV(b); \
  p.PIN##c##CTRL=PORT_PULLUPEN_bm;  \
  p.PIN##d##CTRL=PORT_ISC_RISING_gc | PORT_PULLUPEN_bm;

#define SPI_PINS_DEFAULT_SLAVE() SPI_PINS_SLAVE(PORTA, 4,5,6,7)
#define SPI_PINS_ALT1_SLAVE() SPI_PINS_SLAVE(PORTC, 0,1,2,3)
#define SPI_PINS_ALT2_SLAVE() SPI_PINS_SLAVE(PORTE, 0,1,2,3)

Now if you want to handle every possible combo of the spi pins, then it will get ugly- slave is easy, you have only one pin that has an option, master you have three.

 

 

In my c++ code, I have it setup so I can specify which pin group to use, and which optional pins I want the spi to use. I can also pass the transfer mode, bit order, buffer mode, etc. to the setup function (in any order), no defines anywhere-

#include "Spi.hpp"
#include "Port.hpp"

int main(){
    Spi< Spi0_Alt2<MISO> > spi0;//optional MISO in use (slave mode)
    spi0.setupSLAVE();          //slave mode, all default values, pins setup, spi on
    for(;;){}
}
Last Edited: Wed. Sep 4, 2019 - 06:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've been studying C++ for 3 years now. It's true that class templates are vastly more powerful than preprocessor macros in terms of hiding ugliness and beautifying code.

The drawback is that they are quite challenging to master. But you can do neat stuff, for example you can create a "pin<n>" class template with useful pin related functions like

 

template <uint8_t n>
class pin {
    static_assert( (n <= 7) && (n >= 0), "Invalid pin number!" );
    public:
    inline void is_input();
    inline void is_output();
    inline void toggle();
    inline void set();
    inline void clear();
    inline bool state();
    inline void config(uint8_t);
};

 

and then use like this

 

int main(void){

    // Cast PORTF into pin object pointers
    auto led = (pin<5>*) &PORTF;
    auto button = (pin<6>*) &PORTF;

    led->config(PORT_INVEN_bm);
    button->config(PORT_INVEN_bm | PORT_PULLUPEN_bm);
    led->is_output();

    while(true) {
        if (button->state())
            led->set();
        else
            led->clear();
    }
}

 

Now, inside the pin objects, the C++ "this" pointer has the address of PORTF, allowing the member functions to do anything you need. This is one of the functions:

 

template <uint8_t n>
void pin<n> :: config (uint8_t cfg_data) {
	( &(((volatile PORT_t*) this)->PIN0CTRL) )[n] = cfg_data;
}

 

You may think "Ha, but this will result in massive C++ bloat, right?" Wrong, if the compiler knows how to optimize and you know what you are doing. This is the generated code:

 

000000b2 <main>:
  b2:	80 e8       	ldi	r24, 0x80	; 128
  b4:	80 93 b5 04 	sts	0x04B5, r24	; 0x8004b5 <__TEXT_REGION_LENGTH__+0x7004b5>
  b8:	88 e8       	ldi	r24, 0x88	; 136
  ba:	80 93 b6 04 	sts	0x04B6, r24	; 0x8004b6 <__TEXT_REGION_LENGTH__+0x7004b6>
  be:	80 e2       	ldi	r24, 0x20	; 32
  c0:	80 93 a1 04 	sts	0x04A1, r24	; 0x8004a1 <__TEXT_REGION_LENGTH__+0x7004a1>
  c4:	90 91 a8 04 	lds	r25, 0x04A8	; 0x8004a8 <__TEXT_REGION_LENGTH__+0x7004a8>
  c8:	96 ff       	sbrs	r25, 6
  ca:	03 c0       	rjmp	.+6      	; 0xd2 <main+0x20>
  cc:	80 93 a5 04 	sts	0x04A5, r24	; 0x8004a5 <__TEXT_REGION_LENGTH__+0x7004a5>
  d0:	f9 cf       	rjmp	.-14     	; 0xc4 <main+0x12>
  d2:	80 93 a6 04 	sts	0x04A6, r24	; 0x8004a6 <__TEXT_REGION_LENGTH__+0x7004a6>
  d6:	f6 cf       	rjmp	.-20     	; 0xc4 <main+0x12>

 

Note: This test code was made for the Curiosity Nano mega4809 board, which has a LED in PF5 and a button in PF6.

Last Edited: Wed. Sep 4, 2019 - 06:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to go further-

 

https://github.com/cv007/ATmega4...

 

#include "Port.hpp"

int main(){
    Port< Pins::PF4, Pins::OUTH > led;      //output, high is on
    Port< Pins::PF6, Pins::INLPU > button;  //input, low is on, pullup on

    //or with pin types already created (in "Port.hpp")
    //PF4_OUTH_t led;
    //PF6_INLPU_t button; 

    for(;;){
        led.on( button.ison() );
    }

}

000000f4 <main>:
  f4:    10 92 b4 04     sts    0x04B4, r1    ; 0x8004b4 <__TEXT_REGION_LENGTH__+0x7f44b4>
  f8:    ac 98           cbi    0x15, 4    ; 21
  fa:    a4 9a           sbi    0x14, 4    ; 20

  fc:    88 e8           ldi    r24, 0x88    ; 136
  fe:    80 93 b6 04     sts    0x04B6, r24    ; 0x8004b6 <__TEXT_REGION_LENGTH__+0x7f44b6>
 102:    a6 98           cbi    0x14, 6    ; 20

 104:    b6 9b           sbis    0x16, 6    ; 22
 106:    02 c0           rjmp    .+4          ; 0x10c <__EEPROM_REGION_LENGTH__+0xc>
 108:    ac 9a           sbi    0x15, 4    ; 21
 10a:    fc cf           rjmp    .-8          ; 0x104 <__EEPROM_REGION_LENGTH__+0x4>
 10c:    ac 98           cbi    0x15, 4    ; 21
 10e:    fa cf           rjmp    .-12         ; 0x104 <__EEPROM_REGION_LENGTH__+0x4>

Let the constructor handle setting up the pin as you describe with the Port template parameters. Forget all the pointers, etc.- that just replaces the C world usage with similar C++ usage, and nothing much is gained.

 

The template usage is almost required on the 8bit avr for pins (and other things), because when using a 'normal' class for pins the pointer usage behind the scenes starts to look pretty bad (in comparison to template use anyway). For a 32bit mcu, getting along without templates is easier as the little extra code size and storage are probably worth it, and the resulting pointer usage is simple and straight forward.

 

When using the avr Port class, the template parameters are 'dragged' along everywhere they go, so the compiler knows everything it needs to know just by the use of an instantiated Port class. So what looks like could be a lot of code, turns out to be as little as anything else because the compiler has all it needs to know and can reduce it to the very simple.

 

You don't even have to instantiate the class- if you want a pullup on for PA1, just do-  

PA1_t::pullup( true ); //or

Port< Pins::PA1 >::pullup( true ); //same thing

since the pullup function does not need to know whether input/output/etc., can just use the basic info which is the pin name only. Every function is a static function so can be called directly if wanted.

 

(I also have a simulated open-drain mode, which is also taken care of automatically)

 

There are many nice things in C++, and you don't even have to use that many features to start realizing benefits. The first major benefit is you can start to do away with defines and start dealing with the compiler directly.

 

 

Last Edited: Wed. Sep 4, 2019 - 08:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
You need to go further-

 

I've seen your library, it's very nice, don't get me wrong, but you have to understand I'm just an assembly programmer pretending to be a C programmer pretending to know C++. So naturally my C++ looks C-ish.

I'm still not ready to throw away <avr/io.h> and redo everything from scratch (besides, I feel it's a waste of other people's hard work), that's why I only go this far. Maybe some day.

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

I too have been studying C++ for awhile, but I still prefer the simplicity of C.  Often I'll make a class just to organize data and code together, but I'm sure from the outside it still looks very C like.  I've done some reading on templates, but I can't say I am a huge fan of them.  For now, I'm going to go with this:

 

#define DEBUG_PORT       PORTC
#define DEBUG_PIN        5
#define DEBUG_PIN_CTRL   PIN5CTRL