Author Topic: creating a function but not always using all the arguments  (Read 17006 times)

0 Members and 2 Guests are viewing this topic.

Offline sokoloff

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: us
Re: creating a function but not always using all the arguments
« Reply #25 on: August 25, 2019, 03:12:16 pm »
I think you want something like:
Code: [Select]
#ifndef PORTS_H_
 #define PORTS_H_

// architecture offsets
 #define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS_STEP)
 #define PORTB (PORTS_OS+PORTB_OS_STEP)
 #define PORTC (PORTS_OS+PORTC_OS_STEP)

// port register offset definitions
 #define DIR 0
 #define DIRCLR 0x04
 #define DIRSET 0x08
 #define DIRTGL 0x0C

 #define OUT 0x10
 #define OUTCLR 0x14
 #define OUTSET 0x18
 #define OUTTGL 0x1C

 #define IN 0x20

 #define CTRL 0x24
 #define WRCONFIG 0x28
 #define EVCTRL 0x2C

 #define PMUX_OS 0x30
 #define PMUX_OS_STEP 0x1

 #define PINCFG_OS 0x40
 #define PINCFG_OS_STEP 0x1

 #define REGISTER(A, B) (A + B)

 /* PORTS_H_ */

It seems unlikely that the ports are spaced by the square of the offset.
It seems like you had copy/paste errors in the final port address lines.
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 27985
  • Country: nl
    • NCT Developments
Re: creating a function but not always using all the arguments
« Reply #26 on: August 25, 2019, 03:55:19 pm »
Yes which is why i would pass one argument that is the final bit mask. That bit mask can be made up ad hoc in the function call. The compiler will work out the mask and put it into the program leaving out all the visible calculations. But with them being visible I can easily see what i wrote. I don't like just sending hex numbers to registers as it is not clear what is happening. You can use binary but at 8 digits that is hard work to easily identify which bit is which. Unfortunately you can't put spaces into long binary numbers. at 32 bits it just becomes silly.

The usual way is to use defines for each bit in a register. Like
#define UART_TX_ENABLE (1<<4)
#define UART_RX_ENABLE (1<<6)

and later on:
#define UART_CONTROL           (*(volatile unsigned char *) (some_address_goes_here))

So you can write:
UART_CONTROL = UART_TX_ENABLE | UART_RX_ENABLE;

I think the libraries which come from IAR and ASF will be setup in a similar way (or use structs mapped onto the register map). I strongly suggest to go that way instead of trying to re-invent the wheel. Using binary numbers instead of defines will make code hard to understand and maintain. From the defines for the various bits it is immediately clear which bit is set.
« Last Edit: August 25, 2019, 03:59:22 pm by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #27 on: August 25, 2019, 04:32:57 pm »
I think you want something like:
Code: [Select]
#ifndef PORTS_H_
 #define PORTS_H_

// architecture offsets
 #define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS_STEP)
 #define PORTB (PORTS_OS+PORTB_OS_STEP)
 #define PORTC (PORTS_OS+PORTC_OS_STEP)

// port register offset definitions
 #define DIR 0
 #define DIRCLR 0x04
 #define DIRSET 0x08
 #define DIRTGL 0x0C

 #define OUT 0x10
 #define OUTCLR 0x14
 #define OUTSET 0x18
 #define OUTTGL 0x1C

 #define IN 0x20

 #define CTRL 0x24
 #define WRCONFIG 0x28
 #define EVCTRL 0x2C

 #define PMUX_OS 0x30
 #define PMUX_OS_STEP 0x1

 #define PINCFG_OS 0x40
 #define PINCFG_OS_STEP 0x1

 #define REGISTER(A, B) (A + B)

 /* PORTS_H_ */

It seems unlikely that the ports are spaced by the square of the offset.
It seems like you had copy/paste errors in the final port address lines.


I don't get you, i multiply the por number by the ofset the ports are apart. PORTS_OS * PORTx + the start address for the ports
 

Offline sokoloff

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: us
Re: creating a function but not always using all the arguments
« Reply #28 on: August 25, 2019, 04:46:48 pm »
I think you want something like:
Code: [Select]
#ifndef PORTS_H_
 #define PORTS_H_

// architecture offsets
 #define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80                            // 128

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)     // 0
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)     // 128
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)     // 256

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS_STEP)      // 0x41000000UL
 #define PORTB (PORTS_OS+PORTB_OS_STEP)      // 0x41000080UL
 #define PORTC (PORTS_OS+PORTC_OS_STEP)      // 0x41000100UL

// port register offset definitions
 #define DIR 0
 #define DIRCLR 0x04
 #define DIRSET 0x08
 #define DIRTGL 0x0C

 #define OUT 0x10
 #define OUTCLR 0x14
 #define OUTSET 0x18
 #define OUTTGL 0x1C

 #define IN 0x20

 #define CTRL 0x24
 #define WRCONFIG 0x28
 #define EVCTRL 0x2C

 #define PMUX_OS 0x30
 #define PMUX_OS_STEP 0x1

 #define PINCFG_OS 0x40
 #define PINCFG_OS_STEP 0x1

 #define REGISTER(A, B) (A + B)

 /* PORTS_H_ */

It seems unlikely that the ports are spaced by the square of the offset.
It seems like you had copy/paste errors in the final port address lines.


I don't get you, i multiply the por number by the ofset the ports are apart. PORTS_OS * PORTx + the start address for the ports
Your original code was: (with my comments added as to what I believe the values are)


Code: [Select]
#define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80                            // 128

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)   // 0
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)   // 128
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)   // 256

 #define PORTA_OS (PORTA_OS_STEP*PORTS_OS_STEP)  // 0 * 128 = 0
 #define PORTB_OS (PORTB_OS_STEP*PORTS_OS_STEP)  // 128 * 128 = 16384
 #define PORTC_OS (PORTC_OS_STEP*PORTS_OS_STEP)  // 256 * 128 = 32768

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS)               // 0x41000000UL
 #define PORTB (PORTS_OS+PORTA_OS)               // 0x41000000UL or 0x41040000UL if you fix copy/paste error
 #define PORTC (PORTS_OS+PORTA_OS)               // 0x41000000UL or 0x41080000UL if you fix copy/paste error

It boils down to whether the address are 128 (PORTS_OS_STEP) bytes apart or 16384 (PORTS_OS_STEP * PORTS_OS_STEP) bytes apart.
I don't have the datasheet (or even know what part you're working on), but based on the way you wrote the macros, I was assuming that the ports are 128 bytes apart.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 855
Re: creating a function but not always using all the arguments
« Reply #29 on: August 25, 2019, 04:52:00 pm »
You can use the manufacturer includes, they are nothing special and just a way for them to do the same thing over and over for each chip. I don't particularly like them, but if you plan to move from chip to chip, you may as well learn and use them to save time.

Simple C version, using what they already provide-
Code: [Select]
#include <stdint.h>
 #include <stdbool.h>
 #include "sam.h"

static const bool INPUT = 0;
static const bool OUTPUT = 1;

static const uint32_t LED = PIN_PA02;
static const uint32_t SW = PIN_PA07;

static void pindir(const uint32_t pin, const bool io){
    if( io ) PORT->Group[pin/32].DIRSET.reg = 1<<(pin%32);
    else PORT->Group[pin/32].DIRCLR.reg = 1<<(pin%32);
}
static void pinout(const uint32_t pin, const bool v){
    if( v ) PORT->Group[pin/32].OUTSET.reg = 1<<(pin%32);
    else PORT->Group[pin/32].OUTCLR.reg = 1<<(pin%32);
}
static bool pinval(const uint32_t pin){
    return PORT->Group[pin/32].IN.reg & (1<<(pin%32));
}

int main(){

    pindir( LED, OUTPUT );
    pindir( SW, INPUT );

    for(;;){
        if( pinval( SW ) == 0 ) pinout( LED, 1 );
    }

}
(notice all the defines)
If the mcu has more than one port, its already taken care of and functions can be used for various mcu's if the headers were done right (I think so).
Or bypass the whole function thing and just use directly, which is what they do I'm sure.

Each pin is treated separately, as each pin is a separate thing, so it makes sense. Trying to group them in some way so you can save a few instructions on a setup write will be more trouble than its worth.

There are a thousand ways to go about this and they are all probably ok, except trying to use a pre-filled struct in this case.
 

Offline ogden

  • Super Contributor
  • ***
  • Posts: 3731
  • Country: lv
Re: creating a function but not always using all the arguments
« Reply #30 on: August 25, 2019, 05:03:59 pm »
C does supports varargs, but you get no compile time sanity checking, and no defaults without some extra work.

Right. Such kind of functions are main source of C software bugs and #1 attack vector for hacks. Usage of C functions with varargs shall be discouraged, especially in embedded applications that may set something on fire. As already said in this thread - you can easily avoid need for vararg functions in C.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #31 on: August 25, 2019, 05:50:54 pm »

Each pin is treated separately, as each pin is a separate thing, so it makes sense. Trying to group them in some way so you can save a few instructions on a setup write will be more trouble than its worth.

There are a thousand ways to go about this and they are all probably ok, except trying to use a pre-filled struct in this case.

It's actually the chip maker that encourages multiple pin setup. There is a register that will in one access do what it takes to do ot two register accesses if i was to go the other way around that is available to one pin at a time anyway. but yes no problem using the register that can set up more than one pin at once to setup one pin at a time, that is not the question now.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #32 on: August 25, 2019, 05:51:50 pm »
I'm working on SAMC
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #33 on: August 25, 2019, 06:02:57 pm »
I think you want something like:
Code: [Select]
#ifndef PORTS_H_
 #define PORTS_H_

// architecture offsets
 #define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80                            // 128

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)     // 0
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)     // 128
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)     // 256

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS_STEP)      // 0x41000000UL
 #define PORTB (PORTS_OS+PORTB_OS_STEP)      // 0x41000080UL
 #define PORTC (PORTS_OS+PORTC_OS_STEP)      // 0x41000100UL

// port register offset definitions
 #define DIR 0
 #define DIRCLR 0x04
 #define DIRSET 0x08
 #define DIRTGL 0x0C

 #define OUT 0x10
 #define OUTCLR 0x14
 #define OUTSET 0x18
 #define OUTTGL 0x1C

 #define IN 0x20

 #define CTRL 0x24
 #define WRCONFIG 0x28
 #define EVCTRL 0x2C

 #define PMUX_OS 0x30
 #define PMUX_OS_STEP 0x1

 #define PINCFG_OS 0x40
 #define PINCFG_OS_STEP 0x1

 #define REGISTER(A, B) (A + B)

 /* PORTS_H_ */

It seems unlikely that the ports are spaced by the square of the offset.
It seems like you had copy/paste errors in the final port address lines.


I don't get you, i multiply the por number by the ofset the ports are apart. PORTS_OS * PORTx + the start address for the ports
Your original code was: (with my comments added as to what I believe the values are)


Code: [Select]
#define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80                            // 128

//port offsets
 #define PORTA_OS_STEP (0*PORTS_OS_STEP)   // 0
 #define PORTB_OS_STEP (1*PORTS_OS_STEP)   // 128
 #define PORTC_OS_STEP (2*PORTS_OS_STEP)   // 256

 #define PORTA_OS (PORTA_OS_STEP*PORTS_OS_STEP)  // 0 * 128 = 0
 #define PORTB_OS (PORTB_OS_STEP*PORTS_OS_STEP)  // 128 * 128 = 16384
 #define PORTC_OS (PORTC_OS_STEP*PORTS_OS_STEP)  // 256 * 128 = 32768

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS)               // 0x41000000UL
 #define PORTB (PORTS_OS+PORTA_OS)               // 0x41000000UL or 0x41040000UL if you fix copy/paste error
 #define PORTC (PORTS_OS+PORTA_OS)               // 0x41000000UL or 0x41080000UL if you fix copy/paste error

It boils down to whether the address are 128 (PORTS_OS_STEP) bytes apart or 16384 (PORTS_OS_STEP * PORTS_OS_STEP) bytes apart.
I don't have the datasheet (or even know what part you're working on), but based on the way you wrote the macros, I was assuming that the ports are 128 bytes apart.

PORTC_OS_STEP these are 0, 1, 2.
0*0x80 = 0
1*0x80 = 0x80
2*0x08 = 0x100
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #34 on: August 25, 2019, 06:27:13 pm »
OK I see what i did, I repeated the same calculation

Code: [Select]
#ifndef PORTS_H_
 #define PORTS_H_

// architecture offsets
 #define PORTS_OS 0x41000000UL
 #define PORTS_OS_STEP 0x80

// port offsets
 #define PORTA_OS_CNT 0
 #define PORTB_OS_CNT 1
 #define PORTC_OS_CNT 2

 #define PORTA_OS (PORTA_OS_CNT*PORTS_OS_STEP)
 #define PORTB_OS (PORTB_OS_CNT*PORTS_OS_STEP)
 #define PORTC_OS (PORTC_OS_CNT*PORTS_OS_STEP)

// ports base address
 #define PORTA (PORTS_OS+PORTA_OS)
 #define PORTB (PORTS_OS+PORTA_OS)
 #define PORTC (PORTS_OS+PORTA_OS)

// port register offset definitions
 #define DIR 0x00
 #define DIRCLR 0x04
 #define DIRSET 0x08
 #define DIRTGL 0x0C

 #define OUT 0x10
 #define OUTCLR 0x14
 #define OUTSET 0x18
 #define OUTTGL 0x1C

 #define IN 0x20

 #define CTRL 0x24
 #define WRCONFIG 0x28
 #define EVCTRL 0x2C

 #define PMUX_OS 0x30
 #define PMUX_OS_STEP 0x01

 #define PINCFG_OS 0x40
 #define PINCFG_OS_STEP 0x01

 #define REGISTER(A, B) (A + B)

 /* PORTS_H_ */
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 855
Re: creating a function but not always using all the arguments
« Reply #35 on: August 25, 2019, 06:45:35 pm »
>It's actually the chip maker that encourages multiple pin setup

Not in Atmel Start-
Code: [Select]
#define LED GPIO(GPIO_PORTA, 0)
#define SW GPIO(GPIO_PORTA, 6)
#define OTHER GPIO(GPIO_PORTA, 25)

    // GPIO on PA00

    gpio_set_pin_level(LED,
                    // <y> Initial level
                    // <id> pad_initial_level
                    // <false"> Low
                    // <true"> High
                    false);

    // Set pin direction to output
    gpio_set_pin_direction(LED, GPIO_DIRECTION_OUT);

    gpio_set_pin_function(LED, GPIO_PIN_FUNCTION_OFF);

    // GPIO on PA06

    // Set pin direction to input
    gpio_set_pin_direction(SW, GPIO_DIRECTION_IN);

    gpio_set_pin_pull_mode(SW,
                        // <y> Pull configuration
                        // <id> pad_pull_config
                        // <GPIO_PULL_OFF"> Off
                        // <GPIO_PULL_UP"> Pull-up
                        // <GPIO_PULL_DOWN"> Pull-down
                        GPIO_PULL_OFF);

    gpio_set_pin_function(SW, GPIO_PIN_FUNCTION_OFF);

    // GPIO on PA25

    gpio_set_pin_direction(OTHER,
                        // <y> Pin direction
                        // <id> pad_direction
                        // <GPIO_DIRECTION_OFF"> Off
                        // <GPIO_DIRECTION_IN"> In
                        // <GPIO_DIRECTION_OUT"> Out
                        GPIO_DIRECTION_IN);

    gpio_set_pin_level(OTHER,
                    // <y> Initial level
                    // <id> pad_initial_level
                    // <false"> Low
                    // <true"> High
                    false);

    gpio_set_pin_pull_mode(OTHER,
                        // <y> Pull configuration
                        // <id> pad_pull_config
                        // <GPIO_PULL_OFF"> Off
                        // <GPIO_PULL_UP"> Pull-up
                        // <GPIO_PULL_DOWN"> Pull-down
                        GPIO_PULL_UP);

    gpio_set_pin_function(OTHER,
                        // <y> Pin function
                        // <id> pad_function
                        // <i> Auto : use driver pinmux if signal is imported by driver, else turn off function
                        // <GPIO_PIN_FUNCTION_OFF"> Auto
                        // <GPIO_PIN_FUNCTION_OFF"> Off
                        // <GPIO_PIN_FUNCTION_A"> A
                        // <GPIO_PIN_FUNCTION_B"> B
                        // <GPIO_PIN_FUNCTION_C"> C
                        // <GPIO_PIN_FUNCTION_D"> D
                        // <GPIO_PIN_FUNCTION_E"> E
                        // <GPIO_PIN_FUNCTION_F"> F
                        // <GPIO_PIN_FUNCTION_G"> G
                        // <GPIO_PIN_FUNCTION_H"> H
                        // <GPIO_PIN_FUNCTION_I"> I
                        GPIO_PIN_FUNCTION_OFF);

You can dig around for those functions somewhere in their produced code/headers but I'm just showing 'just enough' to prove they do not do 'multiple pin setup'. I'm sure they have examples somewhere that bitor a number of pins in bitmask form to set a group of pins, but I would not take what they do in Atmel Start, or any other example and turn it into 'the way its done'. Clearly they don't subscribe to a single method, and I'm not sure what method they 'encourage'.



 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #36 on: August 25, 2019, 06:56:32 pm »
No they would not advocate one way or the other. The available registers duplicate. On the one hand there are individual pin registers that configure the pin, but on the other hand there is a "bulk setup" register that will access several pin registers at a time. So the chips hardware is designed for both ways.
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 27985
  • Country: nl
    • NCT Developments
Re: creating a function but not always using all the arguments
« Reply #37 on: August 25, 2019, 09:02:03 pm »
No they would not advocate one way or the other. The available registers duplicate. On the one hand there are individual pin registers that configure the pin, but on the other hand there is a "bulk setup" register that will access several pin registers at a time. So the chips hardware is designed for both ways.
I've seen that on some chips as well. I'm not quite sure what the advantage is of having individual pin registers except for making sure that an operation doesn't touch a different pin. From a programming perspective I would either use the full register or the individual pin registers. Certainly not both because then you'll have two ways a peripheral can be setup. Sometimes you want to see where exactly a register gets set in the software (using a search) to hunt a bug. It is not helpful if you need to search for two different registers.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #38 on: August 25, 2019, 09:51:02 pm »
Well I think there is the 8 bit setup register per pin and the 8 bit mux register per pin. The register that can set the whole shebang up in one go is a single 32 bit register that cannot be read. I think it is just a proxy to set the other 2 up and to do it in a batch. A bit like the set, clr and tcl registers.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 855
Re: creating a function but not always using all the arguments
« Reply #39 on: August 25, 2019, 10:40:36 pm »
>It's actually the chip maker that encourages multiple pin setup
>No they would not advocate one way or the other.

I thought they encouraged it, so I show they don't. Now they don't advocate one way of the other. I'm confused.

>So the chips hardware is designed for both ways.

It allows a way to write a group of pins in a single write (lcd 8 bit data, etc.), but that doesn't mean you have to round up all your pins in use and set them up in a single write to one or more of the port registers, which would only be used one time in an app. Once the initial pin setup is done, you are back to dealing with separate pins for reads/writes/etc. Just skip trying to corral all your pins into a group, and deal with individual pins.

This is what it looks like in C++ to get a group of pins together for a single write-
https://godbolt.org/z/3mbwsi
(and you will still have to group the input pins and output pins separately)

it will look a lot worse in C if/when you can get your macros to handle splitting out individual var args (I suspect you will need 32 levels/macros if you want to handle settings all 32 bits at once- quite a mess). Not worth it.
« Last Edit: August 25, 2019, 10:43:51 pm by cv007 »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #40 on: August 26, 2019, 09:05:08 am »
Yes we are past all of that now and have been discussing how to address registers. At the end of the day I would only be setting up a few pins at a time with the same configuration. By configuration we are not talking: is it an input or output? that is done in a 32 bit DIR register. The configurations are about things like multiplexing pins to different functions so the scope to set them up all at once is near nil.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6880
  • Country: fi
    • My home page and email address
Re: creating a function but not always using all the arguments
« Reply #41 on: August 26, 2019, 10:11:20 am »
(Have I mentioned how much I hate the #hashtags Dave added to the forums, yet?  A lot.
Just to un-b0rk the preprocessor directives in code snippets, I have to prepend the first line with a non-breaking space, U+00A0.
Not to say the stupid "suggestions" the editor pops up, when you type a #. This is just evil.  :'()

I recently looked at SAM4S4/SAM4S2 in 48-pin LQFP, which has two GPIO ports (with max. 32 pins each), A and B. Port A offset is 0x400E0E00, and port B 0x400E1000.  Other SAM4S have port C at 0x400E1200 and port D at 0x400E1400.

GPIO input/output control:
  • Each bit in PIO_OSR (offset 0x0018) corresponds to a pin.  If zero, the pin is an input; otherwise, the pin is an output.
  • Writing PIN_OER (0x0010), each set bit sets the pin to an output (setting the corresponding bit in PIO_OSR).
  • Writing PIN_ODR (0x0014), each set bit sets the pin to an input (clearing the corresponding bit in PIO_OSR).
  • Each bit in PIO_PSR (0x0008) determines whether a pin is a GPIO pin (set), or used for a peripheral (clear).
  • Each bit pair in PIO_ABCDSR1 (0x0070) and PIO_ABCDSR2 (0x0074) for a pair of bits, that select between one of the peripherals for that pin, if PIO_PSR is zero for that pin.
GPIO output state control:
  • Each bit set in PIO_ODSR (0x0038) reflects the set output pin states.  You can write to this register to set all (max 32) output pins to specific states.
  • Each bit set in PIO_SODR (0x0030) sets the corresponding output pin high.
  • Each bit set in PIO_CODR (0x0034) sets the corresponding output pin low.
  • PIO_OWSR (0x00A8) is a read-only mask of output pins affected by writes to PIO_ODSR. It defaults to all zeros.
    If a bit is clear/zero, that bit in PIO_ODSR does affect the output pin state.  If a bit is one, the output pin state is unchanged by writes to PIO_OWSR.
  • You can set bits (disable PIO_ODSR from affecting the corresponding pins) by writing to PIO_OWER (0x00A0).
  • You can clear bits (enable PIO_ODSR from affecting the corresponding pins) by writing to PIO_OWDR (0x00A4).

Let's say you are using PA00..PA8 (first nine pins of port A) for some parallel communication task, and want a function that sets them, in parallel, to a specific pattern.  However, some of the other pins in port A are also GPIO outputs, and you don't want to affect those.
Code: [Select]
 #include <stdint.h>

 #define  PIOA_ODSR_ADDR  0x400E0EA0  /* RW */
 #define  PIOA_OWSR_ADDR  0x400E0EA8  /* R */
 #define  PIOA_OWDR_ADDR  0x400E0EA4  /* W */
 #define  PIOA_OWER_ADDR  0x400E0EA0  /* W */

static inline void set_a00_a08(unsigned int value)
{
    uint32_t  old_owsr;

    /* Save old mask. */
    old_owsr = *(volatile uint32_t *)PIOA_OWSR_ADDR;

    /* Enable writes to pins 0..8, disable to 9..15. */
    *(volatile uint16_t *)PIOA_OWDR_ADDR = 0xFE00;
    *(volatile uint16_t *)PIOA_OWER_ADDR = 0x01FF;

    /* Update output pins */
    *(volatile uint16_t *)PIOA_ODSR_ADDR = value;

    /* Restore old mask. */
    *(volatile uint32_t *)PIOA_OWDR_ADDR = old_owsr;
    *(volatile uint32_t *)PIOA_OWER_ADDR = ~old_owsr;
}
Note that if you have room for a look-up table of 2+2N words in ROM/flash, 32 bits each, you can use any set of N pins in any order of significance within a single port (A, B, C, or D).  For 9 pins, that is just 2056-byte look-up table.

(Why 9? Because it is one option for many LCD displays using a parallel data interface.  I looked at SAM4S as a possible low-cost "blitter" for 320x240 or 480x320 18-bit color LCDs.)

A variant of digitalWriteFast() implemented by an Arduino library and Teensyduino, that takes a pin number (using a mapping of your choice, can mix across all ports in any order), and the state it should be set to, on SAM4S can be written as:
Code: [Select]
 #include <stdint.h>

 #define  PIOA_CODR_ADDR  0x400E0E34  /* W */
 #define  PIOB_CODR_ADDR  0x40010E34  /* W */
 #define  PIOC_CODR_ADDR  0x40012E34  /* W */
 #define  PIOD_CODR_ADDR  0x40014E34  /* W */

 #define  PIOA_SODR_ADDR  0x400E0E30  /* W */
 #define  PIOB_SODR_ADDR  0x400E1030  /* W */
 #define  PIOC_SODR_ADDR  0x400E1230  /* W */
 #define  PIOD_SODR_ADDR  0x400E1430  /* W */

/* Pin port mapping, CODR and SODR addresses for the port, and the bit mask. */
static const uint32_t _pin_odr[PIN_COUNT][3] = {
    /* { PIOx_CODR_ADDR, PIOx_SODR_ADDR, 1<<0 }, */
};

void digitalWriteFast(unsigned int pin_number, unsigned int state)
{
 #ifdef CHECK_ARGS
    if (pin_number < PIN_COUNT)
        *(volatile uint32_t *)(_pin_odr[pin_number][!!state]) = _pin_odr[pin_number][2];
 #else
    *(volatile uint32_t *)(_pin_odr[pin_number][state]) = _pin_odr[pin_number][2];
 #endif
}
depending on whether you know your arguments are always safe or not.  Note that this does not interfere with parallel writes through ODSR.

If you wanted a function that sets a number of output pins to different states as simultaneously as possible (i.e., port A first at once, then port B pins, and so on), you can use gcc vector extensions:
Code: [Select]
 #include <stdint.h>

/* Pin number mapping. Each row has exactly one bit set. */
static const uint32_t _pin_bits[PIN_COUNT][4] = {
    /* { port_A_bitmask, port_B_bitmask, port_C_bitmask, port_D_bitmask }, */
};

typedef  uint32_t  uint32x8  __attribute__((__vector_size__ (64)));

 #define  SET_PIN(number)    ((uint32x8){ _pin_bits[number][0], _pin_bits[number][1], _pin_bits[number][2], _pin_bits[number][3], _pin_bits[number][0], _pin_bits[number][1], _pin_bits[number][2], _pin_bits[number][3] })
 #define  CLEAR_PIN(number)  ((uint32x8){ _pin_bits[number][0], _pin_bits[number][1], _pin_bits[number][2], _pin_bits[number][3], 0, 0, 0, 0 })

 #define  PIN(number, state)  ((uint32x8){ _pin_bits[number][0], _pin_bits[number][1], _pin_bits[number][2], _pin_bits[number][3], \
                                          (!!state) ? __pin_bits[number][0] : 0, \
                                          (!!state) ? __pin_bits[number][1] : 0, \
                                          (!!state) ? __pin_bits[number][2] : 0, \
                                          (!!state) ? __pin_bits[number][3] : 0 })
 
static inline void digitalWriteFastMany(const uint32x8  config)
{
    uint32_t  olda_owsr, oldb_owsr, oldc_owsr, oldd_owsr ;

    /* Save old masks. */
    olda_owsr = *(volatile uint32_t *)PIOA_OWSR_ADDR;
    oldb_owsr = *(volatile uint32_t *)PIOB_OWSR_ADDR;
    oldc_owsr = *(volatile uint32_t *)PIOC_OWSR_ADDR;
    oldd_owsr = *(volatile uint32_t *)PIOD_OWSR_ADDR;

    /* Disable writes to all pins; enable our mask pins. */
    *(volatile uint32_t *)PIOA_OWDR_ADDR = ~(uint32_t)0;
    *(volatile uint32_t *)PIOA_OWDR_ADDR = config[0];
    *(volatile uint32_t *)PIOB_OWDR_ADDR = ~(uint32_t)0;
    *(volatile uint32_t *)PIOB_OWDR_ADDR = config[1];
    *(volatile uint32_t *)PIOC_OWDR_ADDR = ~(uint32_t)0;
    *(volatile uint32_t *)PIOC_OWDR_ADDR = config[2];
    *(volatile uint32_t *)PIOD_OWDR_ADDR = ~(uint32_t)0;
    *(volatile uint32_t *)PIOD_OWDR_ADDR = config[3];

    /* Update output pins */
    *(volatile uint32_t *)PIOA_ODSR_ADDR = config[4];
    *(volatile uint32_t *)PIOB_ODSR_ADDR = config[5];
    *(volatile uint32_t *)PIOC_ODSR_ADDR = config[6];
    *(volatile uint32_t *)PIOD_ODSR_ADDR = config[7];

    /* Restore old masks. */
    *(volatile uint32_t *)PIOA_OWDR_ADDR = olda_owsr;
    *(volatile uint32_t *)PIOA_OWER_ADDR = ~olda_owsr;
    *(volatile uint32_t *)PIOB_OWDR_ADDR = oldb_owsr;
    *(volatile uint32_t *)PIOB_OWER_ADDR = ~oldb_owsr;
    *(volatile uint32_t *)PIOC_OWDR_ADDR = oldc_owsr;
    *(volatile uint32_t *)PIOC_OWER_ADDR = ~oldc_owsr;
    *(volatile uint32_t *)PIOD_OWDR_ADDR = oldd_owsr;
    *(volatile uint32_t *)PIOD_OWER_ADDR = ~oldd_owsr;
}
In this case, you could clear pin 15 and set pin 11 using digitalWriteFastMany(PIN(15,0) | PIN(11,1)) or digitalWriteFastMany(CLEAR_PIN(15) | SET_PIN(11)).  This works, because of the gcc support for the vector attribute and trivial arithmetic operations on them.

While the digitalWriteFastMany() code is not optimal, it does what it says on the tin (except a possible latency, loading the config[4..7] value between updates to port B, C, and D).  To avoid that, one could easily write it in gcc inline assembly for each particular instruction set, minimizing the latency between port changes.

The above shows that you usually do not want a variable number of arguments, and can instead use alternate ways to define multiple items at once.

Let's say you want a function that modifies output pins sequentially, and these sequences can be arbitrarily long.  C strings come to mind here:
Code: [Select]
/* Maximum number of pins = 127.
   HH = PIN_RESERVED ^ ((bit & 31) + 32*(port) + 128*(!state))
   where PIN_RESERVED = ((bit & 31) + 32*(port) + 128*(!state)) of an unused pin,
   and bit = 0..31, state = 0 or 1, and port = 0..3.
*/

 #define  PIN_RESERVED  0xB7  /* Port B pin 23 cannot be cleared. */
 #define  PIN_1_SET  "\xB7"  /* Port A pin 0 */
 #define  PIN_2_SET  "\xB6"  /* Port A pin 1 */
 #define  PIN_3_SET  "\xF2"  /* Port C pin 5 */
 /* : */
 #define  PIN_1_CLEAR  "\x37"  /* Port A pin 0 */
 #define  PIN_2_CLEAR  "\x36"  /* Port A pin 1 */
 #define  PIN_3_CLEAR  "\x72"  /* Port C pin 5 */

static inline void pin_sequence(const unsigned char *pins)
{
    while (*pins) {
        const uint32_t  p = (*pins++) ^ PIN_RESERVED;
        /* Note: ((p>>5)&3)*32 == p & 0x60, and ((p >> 7) & 1)*4 == (p & 0x80) >> 5. */
        *(volatile uint32_t *)(0x400E0E30 + (p & 0x60) + ((p & 0x80) >> 5)) = ((uint32_t)1) << (p & 31);
    }
}

As you can see, there are many interface approaches to choose from.  Picking one depends on exactly what you want to do; the above are just examples for SAM4S.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #42 on: August 26, 2019, 10:40:22 am »
I think todays job is to thoroughly understand pointers.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6880
  • Country: fi
    • My home page and email address
Re: creating a function but not always using all the arguments
« Reply #43 on: August 26, 2019, 11:23:23 am »
Looking at the SAMC20/C21 datasheet, it looks very similar to another Cortex-M0+ I know, Teensy LC.

For each port,
  • OUT (0x10) reflects the intended output state of the pins.
  • writing a value to OUTCLR (0x14) clears the pins corresponding to set bits.
  • writing a value to OUTSET (0x18) sets the pins corresponding to set bits.
  • writing a value to OUTTGL (0x1C) toggles the pins corresponding to set bits.

For these, one might wish to use an interface similar to say
Code: [Select]
#define  PORTA_OUT_ADDR  0x41000010
#define  PORTA_OUTCLR_ADDR  0x41000014
#define  PORTA_OUTSET_ADDR  0x41000018
#define  PORTA_OUTTGL_ADDR  0x4100001C

#define  PORTA_OUT_ADDR  0x41000090
#define  PORTA_OUTCLR_ADDR  0x41000094
#define  PORTA_OUTSET_ADDR  0x41000098
#define  PORTA_OUTTGL_ADDR  0x4100009C

typedef  uint32_t  uint32x2 __attribute__((__vector_size__ (8)));

#define  PA0  ((uint32x2){ ((uint32_t)1)<<0, 0 })
#define  PA1  ((uint32x2){ ((uint32_t)1)<<1, 0 })
#define  PA2  ((uint32x2){ ((uint32_t)1)<<2, 0 })
#define  PA30 ((uint32x2){ ((uint32_t)1)<<30, 0 })
#define  PA31 ((uint32x2){ ((uint32_t)1)<<31, 0 })

#define  PB0  ((uint32x2){ 0, ((uint32_t)1)<<0 })
#define  PB1  ((uint32x2){ 0, ((uint32_t)1)<<1 })
#define  PB2  ((uint32x2){ 0, ((uint32_t)1)<<2 })
#define  PB30  ((uint32x2){ 0, ((uint32_t)1)<<30 })
#define  PB31  ((uint32x2){ 0, ((uint32_t)1)<<31 })

/* Numeric pin mapping for digitalWriteFast() */
static const uint32x2  pin_mask[PIN_COUNT] = {
    PA0, PA1, PA4, PB6,
};

static void clear_pins(const uint32x2  pins)
{
    *(volatile uint32_t *)PORTA_OUTCLR = pins[0];
    *(volatile uint32_t *)PORTB_OUTCLR = pins[1];
}

static void set_pins(const uint32x2  pins)
{
    *(volatile uint32_t *)PORTA_OUTSET = pins[0];
    *(volatile uint32_t *)PORTB_OUTSET = pins[1];
}

static void toggle_pins(const uint32x2  pins)
{
    *(volatile uint32_t *)PORTA_OUTTGL = pins[0];
    *(volatile uint32_t *)PORTB_OUTTGL = pins[1];
}

static void digitalWriteFast(const unsigned int pin, const unsigned int state)
{
    if (pin < PIN_COUNT) {
        if (state)
            set_pins(pin_mask[pin]);
        else
            clear_pins(pin_mask[pin]);
    }
}

static void digitalToggleFast(const unsigned int pin)
{
    if (pin < PIN_COUNT)
        toggle_pins(pin_mask[pin]);
}

Above, digitalWriteFast(pin_number, state) provides the expected interface.

However, you can set/clear/toggle any set of pins, say PA1 and PB3, using set_pins(PA1 | PB3).  (For just two ports, as on SAMC20/C21, you could use uint64_t instead of uint32x2 vector, if you wish to support non-gcc compilers; just use the upper 32 bits for port B, and lower 32 for port A.)

While there is no register to set a set of pins at once, you can use toggle_pins() to update any set of output pins in a sequence.  If you have the current state of those output pins in curr , and the next state in next, all you need to do is toggle_pins(curr ^ next); curr = next; .
(This may feel a bit magical at first, but it does work.)

Again, if you have an N-bit parallel bus in one of the ports, you only need 4+22+N bytes (1+N uint32_t's) in a lookup table in ROM or Flash, and you can use whichever pins in that port in whatever order you want; you just do next=lookup(next_value); to map the value to the bit pattern. Oh, and you do need to set the very first bit pattern in the sequence using OUTSET and OUTCLR.
 

Offline sokoloff

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: us
Re: creating a function but not always using all the arguments
« Reply #44 on: August 26, 2019, 11:37:25 am »
I think todays job is to thoroughly understand pointers.
:-DD :-DD :-DD

"Today" .  BWHAHAHAAHAHAHA.

Thoroughly understanding pointers is a months' long, coding while learning, minimum endeavor. (Once you do, you'll probably wonder what was so hard about it, but until you do, it's not a "long afternoon" kind of learning project...)
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6880
  • Country: fi
    • My home page and email address
Re: creating a function but not always using all the arguments
« Reply #45 on: August 26, 2019, 11:38:44 am »
I think todays job is to thoroughly understand pointers.
Read pointer specifications from right to left, tokenized by *, reading each * as "a pointer to".

This means if you see
    static volatile uint32_t *ptr;
you read it as "ptr is a pointer to static and volatile uint32_t".

In a variable declaration,
    static means the variable is only visible in the current compilation unit
    volatile means the value can change without explicit access, so the compiler may not cache it
    const means immutable, not modified by the C code

In other words, the above declaration can also be read as "ptr is a pointer to an unsigned 32-bit integer.  The pointer may change at any point in time, so the compiler must not cache its value.  ptr is only visible in the current compilation unit".

Current compilation unit means the main source file and any files it includes.  If you flatten the source (include the include files, so you get one stream of source code), each declaration is visible only forwards.  A declaration does not define a value or body, it just specifies what that name is; a definition is when you attach a function body or set a value to that name.

The most complicated examples you might see are stuff like
    int *volatile *const  ptr = (int *volatile *const)0xDEADBEEF;
which reads as "ptr is an immutable pointer (to 0xDEADBEEF) to a volatile pointer to an int".

This means that the value of ptr is immutable (will not be changed by the C code).  If points to a pointer at memory address 0xDEADBEEF.  The contents of that pointer (at that memory address) can change without the compiler seeing any reason for it, so it is volatile.  That pointer points to a (signed) integer.  The value of that integer is **ptr; in machine code, this involves loading the pointer value from 0xDEADBEEF, and then loading the int from wherever that pointer points to.

Note that const volatile is valid, too.  It means the C code won't change it, but its value may change by some other magic, so the compiler must not cache its value.

Pointer arithmetic will take a second afternoon, though.

Hope this helps  :-+
 

Offline obiwanjacobi

  • Super Contributor
  • ***
  • Posts: 1013
  • Country: nl
  • What's this yippee-yayoh pin you talk about!?
    • Marctronix Blog
Re: creating a function but not always using all the arguments
« Reply #46 on: August 26, 2019, 11:43:50 am »
I would seriously consider making distinct dedicated functions for each use case of the parameter combinations.
This would be overloading in C++ and can be done in C - you just have to make each function name unique. Internally (privately) you can route to reusable function, but it makes you API a lot more revealing.

PS: You could exercise you power as a global admin and move this thread to programming... (hint)
« Last Edit: August 26, 2019, 12:06:48 pm by obiwanjacobi »
Arduino Template Library | Zalt Z80 Computer
Wrong code should not compile!
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #47 on: August 26, 2019, 11:45:02 am »
I think todays job is to thoroughly understand pointers.
:-DD :-DD :-DD

"Today" .  BWHAHAHAAHAHAHA.

Thoroughly understanding pointers is a months' long, coding while learning, minimum endeavor. (Once you do, you'll probably wonder what was so hard about it, but until you do, it's not a "long afternoon" kind of learning project...)

Well thanks for reassuring me that this indeed a complex subject and that I am not as thick as I thought. The main problem I have is that every book is written from a PC standpoint not an embedded standpoint so the concepts are not always explained from an embedded point of view. I guess pointers may be the slight exception as they are about dealing with memory space directly which is a universal concept. but I still am yet to see a book give examples of pointing like those i see in some embedded code starting with the header files of the chip.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18035
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: creating a function but not always using all the arguments
« Reply #48 on: August 26, 2019, 11:51:11 am »
PS: You could exercise you power as a global admin and move this thread to programming... (hint)

We have a section for that ? lost track with the forums that Dave created and then had to remove because the idiots complained about not having this mess in "microcontrollers and FPGA's" where every architecture and programming language under the sun gets discussed.
« Last Edit: August 26, 2019, 11:53:38 am by Simon »
 

Offline obiwanjacobi

  • Super Contributor
  • ***
  • Posts: 1013
  • Country: nl
  • What's this yippee-yayoh pin you talk about!?
    • Marctronix Blog
Re: creating a function but not always using all the arguments
« Reply #49 on: August 26, 2019, 12:07:32 pm »
PS: You could exercise you power as a global admin and move this thread to programming... (hint)

We have a section for that ? lost track with the forums that Dave created and then had to remove because the idiots complained about not having this mess in "microcontrollers and FPGA's" where every architecture and programming language under the sun gets discussed.

https://www.eevblog.com/forum/programming/
Arduino Template Library | Zalt Z80 Computer
Wrong code should not compile!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf