EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: Simon on August 25, 2019, 11:20:27 am

Title: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 11:20:27 am
So if i write a function that has a number of input arguments but I don't use them all what happens? will they just be assumed as 0? i am sure i have seen this in Arduino library functions.
Title: Re: creating a function but not always using all the arguments
Post by: oPossum on August 25, 2019, 11:26:34 am
In C++ you indicate optional arguments by specifying default values.

For example...  int foo(int a, int b = 0, int c = 5, char * s = NULL);

The first argument has no default, so it is required. The others are optional. You can not skip an optional argument.

foo(1, , 3, "test");  <-- skip not allowed

foo(); <-- required argument missing

foo(1, 2);  <-- OK
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 11:29:05 am
So does this work in C?
Title: Re: creating a function but not always using all the arguments
Post by: MosherIV on August 25, 2019, 11:33:25 am
Arguments are passed by value, that is a copy is taken and put on the stack. The function then takes the value from the stack.
Nothing will happen if you do not use the value.
The compiler may warn you that you have not used a variable.

Since variables are passed as copies, you cannot change input parameters from inside functions.
Instead, you have to pass in by reference. In other words, pointers.

Edit: no, C does not support default parameter values.

Both C and C++ support parameter specifiers 'const' which tells the compiler that the copy of variable on the stack cannot be changed.
Title: Re: creating a function but not always using all the arguments
Post by: oPossum on August 25, 2019, 11:45:41 am
C does supports varargs, but you get no compile time sanity checking, and no defaults without some extra work.

printf() is an example of a function that uses varargs

https://en.wikipedia.org/wiki/Variadic_function
Title: Re: creating a function but not always using all the arguments
Post by: RoGeorge on August 25, 2019, 11:51:52 am
Optional arguments may or may not be possible, depending on the language and the function definition.  Same for arguments sent by value or by reference.  In some languages the default is to send arguments by value, in other languages the default is to send them by reference.

For C++ it is possible to have optional arguments, not so much for C.
https://stackoverflow.com/questions/27795767/optional-arguments-in-c-function

Arduino is a mixture of C and C++ libraries.

Note:  statistics show OP beginning with the conclusive word "so" have a 69% less chance to be read.   :)
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 12:06:26 pm


Note:  statistics show OP beginning with the conclusive word "so" have a 69% less chance to be read.   :)

And 50% of statistics are made up  ;D

"So" I am confused now as i seem to interpret people saying yes and no to the same question. The context is that i am wrining for the ARM GCC compiler which i beleive is C?

As i have the posibilitfy of setting up several pins on one register write and thus save a lot of repetition of code (that may or may not be optimised out) i aim to write a function that has as arguments the setup parameters followed by the pins that it has to affect. So I can just put the string in of bits that will go straight into the 16 bits of the register or i can specify the pins one at a time. But i will hardly ever setup all 16 so many of the arguments would remain blank.
Title: Re: creating a function but not always using all the arguments
Post by: donotdespisethesnake on August 25, 2019, 12:11:42 pm


Note:  statistics show OP beginning with the conclusive word "so" have a 69% less chance to be read.   :)


"So" I am confused now as i seem to interpret people saying yes and no to the same question.

Some people misinterpreted the question. I think you asked "what if I don't pass all the parameters?" (Answer: a compiler error).

Of course, if you pass parameters that are not used, the answer is largely irrelevant, so I'm not sure why people answered that.

You may also have seen overloaded functions in C++, which are not available in C.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 12:15:53 pm

Some people misinterpreted the question. I think you asked "what if I don't pass all the parameters?" (Answer: a compiler error).


Thankyou, i will have to look at how i acheive what i want then. I suppose I can build up my mask as a seperate operation and then pass it to the function.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 12:18:31 pm
On the other hand when I create a new project it asks if i want o create a new C/C++ project for AVR/ARM. but then i am still learning C never mind C++
Title: Re: creating a function but not always using all the arguments
Post by: oPossum on August 25, 2019, 12:26:42 pm
Some people misinterpreted the question. I think you asked "what if I don't pass all the parameters?" (Answer: a compiler error).

There will not be an error if the function has default arguments. (C++ only)

There will not be an error if it is a variadic function (C and C++)
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 12:28:09 pm
variadic ?
Title: Re: creating a function but not always using all the arguments
Post by: rhodges on August 25, 2019, 12:31:10 pm
I suppose I can build up my mask as a seperate operation and then pass it to the function.
That is probably a good option. Consider something like this:
Code: [Select]
#define OPTION_A 0x01 /* one bit */
#define OPTION_B 0x02
#define OPTION_C 0x04
#define OPTION_X 0x10 /* two bits */
#define OPTION_Y 0x20 /* two bits */
#define OPTION_Z 0x30 /* two bits */

something_init(OPTION_A | OPTION_C | OPTION_Y);
Title: Re: creating a function but not always using all the arguments
Post by: nctnico on August 25, 2019, 12:37:55 pm
So does this work in C?
No. Only in C++.
Title: Re: creating a function but not always using all the arguments
Post by: oPossum on August 25, 2019, 12:41:40 pm
variadic ?

Reply #4  ^^^
Title: Re: creating a function but not always using all the arguments
Post by: cv007 on August 25, 2019, 12:43:50 pm
>As i have the posibilitfy of setting up several pins on one register write and thus save a lot of repetition of code

Your solution may be worse- in size or usage, than just dealing with one pin at a time. Let's say you have 20 pins to deal with- in the big picture, this will result in code you may still have to get the magnifying glass out to find. If you can work out a way to 'combine' the register writes, you gain very little but now you have to figure out which pins belong to which ports, what their port settings will be such as pullup, input, output, etc. Keep it simple, deal with 1 pin at a time.

simple C example to setup in/out for a pin-
https://godbolt.org/z/EPZcAD

now do 20 of them, not a big deal.

Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 01:01:08 pm
Not really. If i write code to set up each pin i will be writing the same code ove and over with some change in the numbers. but with one of the registers i can send the same configuration to 16 pins at a time if needed. Granted I will end up using it over and over as i can only send one configuration at a time.

using bit shifts and andding multiple ones together can all be done in passing the argument and will be optimized out in the compiler as the final mask..

So now i have the fun of working out what they called the registers and pondering again the wisdom of just addressing the memory space directly, I note for example that SAMc and SAMd have the same addresses for same registers so it would still be portable.
Title: Re: creating a function but not always using all the arguments
Post by: cv007 on August 25, 2019, 01:25:03 pm
here is a very simple incomplete version of C++ templates-
https://godbolt.org/z/I-JA_0

>If i write code to set up each pin i will be writing the same code ove and over with some change in the numbers. but with one of the registers i can send the same configuration to 16 pins at a time if needed

How do you imagine your function call will look like? Start with what you want your code to look like, and work back from there.

this is what it appears you want to do (multiple optional arguments, which will end up being a macro of some kind)-
pin_outputs( PA02, PA05, PA15, PA20);
pin_inputs( PA03, PA07, PA11, PA25);

You are not using a pic10 with 256 bytes of flash. There are bigger battles to fight, and worrying about combining register writes is not worth your time, but you can certainly give it a go.
Title: Re: creating a function but not always using all the arguments
Post by: MosherIV on August 25, 2019, 01:39:25 pm
Hi

One trick which I did not understand when I first started, is to create a struct with the regsiter set that you are trying to control.
Create 1 instance of the struct for each set of registers that you want to control.
You have to somehow point the struct to the address of the registers.

Then, all you have to do is to change the register in the struct of the instance that you are interrested in.
Your function then just takes the struct and writes the whole lot.

I first saw this for initialising UARTs.
So you could initialise multiple UARTs with the same struct but pointing to UARTs at different register adresses.
Hope that makes sense.
I did not like it when I first saw this but now I understand and appreciate this technique.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 01:57:15 pm
Hi

One trick which I did not understand when I first started, is to create a struct with the regsiter set that you are trying to control.
Create 1 instance of the struct for each set of registers that you want to control.
You have to somehow point the struct to the address of the registers.

Then, all you have to do is to change the register in the struct of the instance that you are interrested in.
Your function then just takes the struct and writes the whole lot.

I first saw this for initialising UARTs.
So you could initialise multiple UARTs with the same struct but pointing to UARTs at different register adresses.
Hope that makes sense.
I did not like it when I first saw this but now I understand and appreciate this technique.

i need to remind myself what structs are/how they work. The problem i have with the supplied headers is that they are geared up for their own ASF or IAR systems. I am now at a paint where I don't see the point inconforming to their "standards" if tht does not work for me.

The other way of doing it is to pass the part name as a parameter/argumunt to the function, that "text" will simply be a define that is replaced by the base address ofthe port, so i can define each offset once and easily add them together in other defines so that I am not writing out every address and i have some chance of porting to another SAM device by just changing a fex offset definitions.
Title: Re: creating a function but not always using all the arguments
Post by: radiolistener on August 25, 2019, 02:07:49 pm
to write a function that has as arguments the setup parameters followed by the pins that it has to affect. So I can just put the string in of bits that will go straight into the 16 bits of the register or i can specify the pins one at a time. But i will hardly ever setup all 16 so many of the arguments would remain blank.

I don't recommend to use a lot of arguments in the function, it's better to use struct and pass pointer to this struct.

Regarding to pins and bit manipulation, it's better to define clear named constants with masks and values and combine it with | and & operations instead of passing each bit separately. It will be much easier to support such code. Because when you will use a lot of variables for bits and pins it will turns into nightmare  :)
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 02:12:44 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.
Title: Re: creating a function but not always using all the arguments
Post by: SiliconWizard on August 25, 2019, 02:21:20 pm
For C, the struct "trick" would be the way to go indeed IMO. (It's not a trick!)
I don't recommend using variadic functions for that. Would be cumbersome and error-prone, especially if you have several parameters of different types.

You can either reuse the same struct variable for subsequent calls, just modifying the fields you want modified (if the other fields are to retain their previous value), or if you want all the fields that are not explicitly set to be zeroed-out, you can use the C99 syntax (compound literals). Short example:

Code: [Select]
#include <stdio.h>
#include <stdint.h>

typedef struct
{
uint32_t n;
uint8_t m;
double x;
char s[32];

} MyParams_t;

static void MyFunction(MyParams_t *pParams)
{
if (pParams == NULL)
return;

printf("n = %u, m = %u, x = %g, s = '%s'\n", pParams->n, pParams->m, pParams->x, pParams->s);
}

int main(void)
{
MyParams_t Parameters;

// Initial values.
Parameters = (MyParams_t){ .n = 1, .x = 0.5 };

MyFunction(&Parameters);

// Only change selected fields.
Parameters.m = 2;
Parameters.x = -0.5;

MyFunction(&Parameters);

// Only set selected fields, the others will be zeroed-out.
Parameters = (MyParams_t){ .s = "Test" };

MyFunction(&Parameters);

return 0;
}

Title: Re: creating a function but not always using all the arguments
Post by: sokoloff on August 25, 2019, 02:30:30 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.
You could use a preprocessor macro if you wanted to break up a long binary number for the author and recombine it later.

If you're using gcc (in order to get support for 0bxxxxxxxx style binary literals), you can do something like this to break up your literals for reading:

Code: [Select]
#include <stdio.h>

#define BINARY_CONST(A, B) (0b ## A ## B)

int main(void) {
  int foo = BINARY_CONST(0101, 1111);
  printf("%s = 0x%x", "BINARY_CONST(0101, 1111)", foo);
  return 0;
}

You can also have a look at the BOOST_BINARY macro package (https://www.boost.org/doc/libs/1_42_0/libs/utility/utility.htm#BOOST_BINARY).
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 03:03:58 pm
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)

 #define PORTA_OS (PORTA_OS_STEP*PORTS_OS_STEP)
 #define PORTB_OS (PORTB_OS_STEP*PORTS_OS_STEP)
 #define PORTC_OS (PORTC_OS_STEP*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 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_ */
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff 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.
Title: Re: creating a function but not always using all the arguments
Post by: nctnico 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff 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.
Title: Re: creating a function but not always using all the arguments
Post by: cv007 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.
Title: Re: creating a function but not always using all the arguments
Post by: ogden 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 25, 2019, 05:51:50 pm
I'm working on SAMC
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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_ */
Title: Re: creating a function but not always using all the arguments
Post by: cv007 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'.



Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: nctnico 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: cv007 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: Nominal Animal 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: GPIO output state control:
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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 10:40:22 am
I think todays job is to thoroughly understand pointers.
Title: Re: creating a function but not always using all the arguments
Post by: Nominal Animal 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,

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.
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff 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...)
Title: Re: creating a function but not always using all the arguments
Post by: Nominal Animal 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  :-+
Title: Re: creating a function but not always using all the arguments
Post by: obiwanjacobi 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)
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: Simon 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.
Title: Re: creating a function but not always using all the arguments
Post by: obiwanjacobi 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/ (https://www.eevblog.com/forum/programming/)
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 04:39:53 pm
So I cannot directly address memory (registers). i cannot write a "memory address = value" to write to a register.
Title: Re: creating a function but not always using all the arguments
Post by: oPossum on August 26, 2019, 04:48:22 pm
So is that a question.

*(uint32_t *)0x1234 = 0x5678;
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff on August 26, 2019, 04:49:00 pm
You are the master of incompletely specified questions/comments...  :-DD

If you want to write a byte "value" to the memory address PORTC_OS, you could do something like:

Code: [Select]
unsigned char value = 0x80;  // Or whatever value you want
unsigned char *port_C_OS = (unsigned char *) PORTC_OS;
*port_C_OS = value;
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:02:15 pm
OK, i am confused here. My defines are clashing with the ones already loaded and if i don't load them nothing works. But how the hell do i use the existing definitions?

If I type "PORT" I get that as a suggestion frommicrochips defines and it's equivalent to the address that PORTA starts at. Great, that's no use to me. What is the chip maker actually giving me? other than no instructions as to what they are they expect me to use their own code generator. Looking at the header files that I can find no register is explicitly pointed to with a variable. So what the hell am I doing? This is so stupid. If the manufacturer wants to supply existing headers why not document them for the slighly less giften in unravelling files and files linked together.

Unless I rewrite everything I can't even program the thing.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:12:33 pm
So for example:

Code: [Select]
#define REG_PORT_DIR0              (*(RwReg  *)0x41000000UL) /**< \brief (PORT) Data Direction 0 */
#define REG_PORT_DIRCLR0           (*(RwReg  *)0x41000004UL) /**< \brief (PORT) Data Direction Clear 0 */
#define REG_PORT_DIRSET0           (*(RwReg  *)0x41000008UL) /**< \brief (PORT) Data Direction Set 0 */
#define REG_PORT_DIRTGL0           (*(RwReg  *)0x4100000CUL) /**< \brief (PORT) Data Direction Toggle 0 */
#define REG_PORT_OUT0              (*(RwReg  *)0x41000010UL) /**< \brief (PORT) Data Output Value 0 */
#define REG_PORT_OUTCLR0           (*(RwReg  *)0x41000014UL) /**< \brief (PORT) Data Output Value Clear 0 */
#define REG_PORT_OUTSET0           (*(RwReg  *)0x41000018UL) /**< \brief (PORT) Data Output Value Set 0 */
#define REG_PORT_OUTTGL0           (*(RwReg  *)0x4100001CUL) /**< \brief (PORT) Data Output Value Toggle 0 */
#define REG_PORT_IN0               (*(RoReg  *)0x41000020UL) /**< \brief (PORT) Data Input Value 0 */
#define REG_PORT_CTRL0             (*(RwReg  *)0x41000024UL) /**< \brief (PORT) Control 0 */
#define REG_PORT_WRCONFIG0         (*(WoReg  *)0x41000028UL) /**< \brief (PORT) Write Configuration 0 */
#define REG_PORT_EVCTRL0           (*(RwReg  *)0x4100002CUL) /**< \brief (PORT) Event Input Control 0 */
#define REG_PORT_PMUX0             (*(RwReg  *)0x41000030UL) /**< \brief (PORT) Peripheral Multiplexing 0 */
#define REG_PORT_PINCFG0           (*(RwReg  *)0x41000040UL) /**< \brief (PORT) Pin Configuration 0 */
#define REG_PORT_DIR1              (*(RwReg  *)0x41000080UL) /**< \brief (PORT) Data Direction 1 */
#define REG_PORT_DIRCLR1           (*(RwReg  *)0x41000084UL) /**< \brief (PORT) Data Direction Clear 1 */
#define REG_PORT_DIRSET1           (*(RwReg  *)0x41000088UL) /**< \brief (PORT) Data Direction Set 1 */
#define REG_PORT_DIRTGL1           (*(RwReg  *)0x4100008CUL) /**< \brief (PORT) Data Direction Toggle 1 */
#define REG_PORT_OUT1              (*(RwReg  *)0x41000090UL) /**< \brief (PORT) Data Output Value 1 */
#define REG_PORT_OUTCLR1           (*(RwReg  *)0x41000094UL) /**< \brief (PORT) Data Output Value Clear 1 */
#define REG_PORT_OUTSET1           (*(RwReg  *)0x41000098UL) /**< \brief (PORT) Data Output Value Set 1 */
#define REG_PORT_OUTTGL1           (*(RwReg  *)0x4100009CUL) /**< \brief (PORT) Data Output Value Toggle 1 */
#define REG_PORT_IN1               (*(RoReg  *)0x410000A0UL) /**< \brief (PORT) Data Input Value 1 */
#define REG_PORT_CTRL1             (*(RwReg  *)0x410000A4UL) /**< \brief (PORT) Control 1 */
#define REG_PORT_WRCONFIG1         (*(WoReg  *)0x410000A8UL) /**< \brief (PORT) Write Configuration 1 */
#define REG_PORT_EVCTRL1           (*(RwReg  *)0x410000ACUL) /**< \brief (PORT) Event Input Control 1 */
#define REG_PORT_PMUX1             (*(RwReg  *)0x410000B0UL) /**< \brief (PORT) Peripheral Multiplexing 1 */
#define REG_PORT_PINCFG1           (*(RwReg  *)0x410000C0UL) /**< \brief (PORT) Pin Configuration 1 */

So each port register is defined with a name and this seems to be the closest thing to what microchip provides for me to use out of the box.
Ports have now been renamed 1 and 2 instead of A and B, why not just do that, write all of the documentation in terms of port numbers instead of letters
Title: Re: creating a function but not always using all the arguments
Post by: cv007 on August 26, 2019, 05:14:23 pm
This thread is hard to nail down, in more ways than one.

>But how the hell do i use the existing definitions?

You didn't like my little example of using the headers?
https://www.eevblog.com/forum/programming/creating-a-function-but-not-always-using-all-the-arguments/msg2642181/#msg2642181 (https://www.eevblog.com/forum/programming/creating-a-function-but-not-always-using-all-the-arguments/msg2642181/#msg2642181)

You should just explain how you want your code to look- not how to implement. Once you decide what you want it to look like in use, then implementation becomes easier.


int main(){

  //I have 2 leds as outputs, 2 switches as input with pullups, this is how I would do it
  //forget that its not implemented, just how its going to be used when its all done

  //fill in what you want pin setup code to look like here...

}
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:20:35 pm
This thread is hard to nail down, in more ways than one.

>But how the hell do i use the existing definitions?

You didn't like my little example of using the headers?
https://www.eevblog.com/forum/programming/creating-a-function-but-not-always-using-all-the-arguments/msg2642181/#msg2642181 (https://www.eevblog.com/forum/programming/creating-a-function-but-not-always-using-all-the-arguments/msg2642181/#msg2642181)

You should just explain how you want your code to look- not how to implement. Once you decide what you want it to look like in use, then implementation becomes easier.


int main(){

  //I have 2 leds as outputs, 2 switches as input with pullups, this is how I would do it
  //forget that its not implemented, just how its going to be used when its all done

  //fill in what you want pin setup code to look like here...

}

It's my usual brain storm when i try to use a new chip and the toss up between trying to do it my own way that i understand or work out how to drive the manufacturers stuff around without it getting in the way. Yes the topic has shifted somewhat. At the moment i can't even care what my code will look like as i am wrestling with how the manufacturer has defined things ani can i throw out anything that clashes as I'm a little worried about how much of the predefined stuff is for manual coding and how much is for the atmel start bull.

The Mega 0 series was hard work at the start until I realized how they had done the headers and it all made sense:

peripery instance followed by instance number . register, plain and simple. But what i see here does not look like it was made for human consumption but atmel start. If i try to redefine things i could just make matters worse.

Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:24:07 pm
So is that a question.

*(uint32_t *)0x1234 = 0x5678;


Right, so that is the basis of naming a register and a value to put into it? Great. OK, I can see it uses pointers which i now gather is the only way C will let me access memory addresses. But that looks nothing like what several books on C for PC have to say about pointers. i can of course just take that as gospel and use it to address anf memory location (register) i need to but in makes no sense to me and sooner or later I will clash with a vendor definition. If i remove the vendor headers everything falls apart. I seem to be stuck in the middle.
Title: Re: creating a function but not always using all the arguments
Post by: cv007 on August 26, 2019, 05:28:13 pm
you need to ignore stuff inside-
#if (defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))
(but there could be an #else buried in the middle, so watch for it)

which is what you posted a few posts back

in include/yourdevice.h, the port address is defined as a pointer to a Port struct
#define PORT              ((Port     *)0x41004400UL) /**< \brief (PORT) APB Base Address */
#define PORT_IOBUS        ((Port     *)0x60000000UL) /**< \brief (PORT) IOBUS Base Address */

in include/component/port.h-
you will find all the structs, starting at the bottom of the file will be the outermost struct

in include/pio/yourdevice.h
will be pin number defines
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:31:36 pm
you need to ignore stuff inside-
#if (defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))
(but there could be an #else buried in the middle, so watch for it)

which is what you posted a few posts back

Yes so it says if using assembly use this lot otherwise use: what I posted which I assume is being used for C and sets up a define for each register. The naming is a bit weired but it's the best i have so far.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:33:32 pm
Port.h only has defines, no structs. I'll have a look for them. This is half my concern, this stuff is all over the place. What happens if we have to move to MAPLABX will everything still work?
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff on August 26, 2019, 05:36:53 pm
So for example:
Code: [Select]
#define REG_PORT_DIR0              (*(RwReg  *)0x41000000UL) /**< \brief (PORT) Data Direction 0 */

So each port register is defined with a name and this seems to be the closest thing to what microchip provides for me to use out of the box.
Ports have now been renamed 1 and 2 instead of A and B, why not just do that, write all of the documentation in terms of port numbers instead of letters
That macro definition is designed to make REG_PORT_DIR0 "look like" a variable. It contains the definition of the pointer:
Code: [Select]
(RwReg  *)0x41000000UL
and the dereferencing of the pointer:
Code: [Select]
(* <the pointer as above> )
so to assign an unsigned 32 bit value to it (the RwReg is defined as uint32_t), you just write:
Code: [Select]
REG_PORT_DIR0 = <the value you want to assign>
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff on August 26, 2019, 05:38:23 pm
which ends up functioning exactly like the code @oPossum wrote above. (I'm saying this just to help you understand pointers thoroughly "this afternoon"  :-DD )

Code: [Select]
(*(RwReg  *)0x41000000UL) = <the value>;
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 05:43:51 pm
so why does this asterisk keep moving around, the books put it before the variable that will be replaced with the address of what it points to yet all of this register stuff puts it after something follewed by a parenthisis. Is this declaration and assicnment all in one?
Title: Re: creating a function but not always using all the arguments
Post by: sokoloff on August 26, 2019, 05:50:10 pm
I'll take a stab at it, but it's not going to be second-nature for a while.

In the expression:
(*(RwReg *)0x41000000UL) = <the value>;

I think of it this way:
Take an unsigned long number 0x41000000
Treat is as a pointer to an RwReg.
Then, take that pointer and assign the location to which it points the value.
Title: Re: creating a function but not always using all the arguments
Post by: cv007 on August 26, 2019, 05:53:43 pm
an attempt at a simple explanation-

//include/yourdevice.h
#define PORT  ((Port     *)0x41004400UL)

//include/pio/yourdevice.h
#define PIN_PA05 5
#define PORT_PA05 (_UL_(1) <<  5)

//include/component/port.h
typedef struct {
       PortGroup Group[1]; //this has only one port (port A)
} Port;

typedef struct {
  __IO PORT_DIR_Type             DIR;
  __IO PORT_DIRCLR_Type          DIRCLR;
  __IO PORT_DIRSET_Type          DIRSET;
  __IO PORT_DIRTGL_Type          DIRTGL;
  __IO PORT_OUT_Type             OUT;   
  __IO PORT_OUTCLR_Type          OUTCLR;
  __IO PORT_OUTSET_Type          OUTSET;
  __IO PORT_OUTTGL_Type          OUTTGL;
  __I  PORT_IN_Type              IN;   
  __IO PORT_CTRL_Type            CTRL; 
  __O  PORT_WRCONFIG_Type        WRCONFIG;
       RoReg8                    Reserved1[0x4];
  __IO PORT_PMUX_Type            PMUX[16];
  __IO PORT_PINCFG_Type          PINCFG[32];
       RoReg8                    Reserved2[0x20];
} PortGroup;

//etc, other structs defined above this one


PORT->Group[0] will be the struct of the first PORT (port A)
PORT->Group[1] will be the struct of the second PORT (port B) if you had one


to set a pin PA05 as output-

without ->
(*PORT).Group[0].DIRSET.reg = 1<<PIN_PA05; //pin number
or with -> as its intended to make it simpler to use
PORT->Group[0].DIRSET.reg = 1<<PIN_PA05; //pin number
or
PORT->Group[0].DIRSET.reg = PORT_PA05; //this is a bitmask, but gives no info on what port it belongs to

or if you have more than port A, use the bit to figure out the port
as PIN_PxXX will be consecutive (PIN_PB02 is defined as 34)

PORT-Group[PIN_PB05/32].DIRSET.reg = 1<<(PIN_PB05%32);
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 06:00:41 pm
Right so (RwReg *)0x41000000UL is making the memory location appear to be a pointed to variable

(*(RwReg *)0x41000000UL) = <the value>; puts the value in the address pointed to. But the confusing bit is that there is no variable name. i think that is what makes it look so different from standard pointers that don't explicitly control the address they only point to whatever address the compiler gave the variable.
Title: Re: creating a function but not always using all the arguments
Post by: Nominal Animal on August 26, 2019, 07:05:58 pm
Let's examine the macro definition:
    #define  REG_PORT_DIR0  (*(RwReg  *)0x41000000uL)
If may be easier to understand if we split it in three parts:
    #define  REG_PORT_DIR0_ADDR  0x41000000uL
    #define  REG_PORT_DIR0_PTR   ((RwReg *)REG_PORT_DIR0_ADDR)
    #define  REG_PORT_DIR0       (*REG_PORT_DIR0_PTR)
So, the key here is that the two asterisks have different meanings.

In C, expressions of form (type)value, or equivalently (type)(value), are casts.  They convert the type of the value to type.  Above, 0x41000000uL is value, and RwReg * is type, a pointer to RwReg.

The REG_PORT_DIR0 macro dereferences the RwReg pointer to 0x41000000uL.  So, in the original macro, the first asterisk dereferences a pointer, whereas the second asterisk is part of a pointer type specification, as part of a cast from integer address to an RwReg pointer.

Most likely, somewhere in the header files you'll see a typedef similar to  typedef  volatile unsigned long  RwReg; which means that
    REG_PORT_DIR0 = 5;
is effectively the same as
    *((volatile unsigned long *)0x41000000uL) = 5;
which is effectively the same as
    ((volatile unsigned long *)0x41000000uL)[0] = 5;
i.e, the part in parentheses defines a pointer to a volatile unsigned long, and that pointer is immediately dereferenced without being assigned to any variable; it is how you use "pointer literals" in C.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 07:37:08 pm
Thank you, that makes more sense.

Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 08:13:42 pm
Well i have a square wave on the screen of my scope from toggling every pin of both ports. Just a bit mystifyed as to why all of port A does not seem to work.

So I am back to square one, writing directly to register memory locations is easy, but anf definitions i write are bound to clash with those already present. If i try to remove the monster of header files that are automatically attched to the project nothing will work any more.
Title: Re: creating a function but not always using all the arguments
Post by: Simon on August 26, 2019, 08:50:25 pm
aha, removed their setup code and my defines seem to work now and no errors. Porta still does not work. i am getting a wavefom through but it's like when you set an output to toggle but forget to set the pin direction up and the high impedance of the scope just manages to see the waveform anyway.