Author Topic: storing function pointer and calling  (Read 2517 times)

0 Members and 1 Guest are viewing this topic.

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #25 on: June 03, 2021, 04:02:54 pm »
Tinkercad simulator is based on Code::Blocks simulator, which is an API-level simulator. It simulates based on the function calls, not based on what the hardware register values et cetera are.  As far as I know, it does not support timer interrupts, because the base API does

Tinkercad simulator works perfectly fine. it actually blinks the led driven by the timer.
but it does not understand the typedef for a pointer. it throws a compile error.
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2731
  • Country: fi
    • My home page and email address
Re: storing function pointer and calling
« Reply #26 on: June 03, 2021, 04:46:30 pm »
Tinkercad simulator works perfectly fine. it actually blinks the led driven by the timer.
but it does not understand the typedef for a pointer. it throws a compile error.
"It works perfectly fine.  It's just that it throws a compile error when I do (a very normal thing)."

Don't you understand how self-contradictory that statement is?  By definition, it does not work perfectly fine, because it apparently does not support function pointers at all (at least via a typedef).

You claim the problem must be in Arduino or C or C++, when it is in your beloved Tinkercad simulator; but because you love it, you refuse to acknowledge that that is where the problem lies.  This is unbelievably frustrating.  I'm out.
 
The following users thanked this post: newbrain

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #27 on: June 03, 2021, 07:15:06 pm »
Tinkercad simulator works perfectly fine. it actually blinks the led driven by the timer.
but it does not understand the typedef for a pointer. it throws a compile error.
"It works perfectly fine.  It's just that it throws a compile error when I do (a very normal thing)."

Don't you understand how self-contradictory that statement is?  By definition, it does not work perfectly fine, because it apparently does not support function pointers at all (at least via a typedef).

You claim the problem must be in Arduino or C or C++, when it is in your beloved Tinkercad simulator; but because you love it, you refuse to acknowledge that that is where the problem lies.  This is unbelievably frustrating.  I'm out.
OK,  i should have formulated that better

- tinkercad can simulate the hardware timer and interrupt code. (tested in separate program, works correct. simple blinky led program)
- tinkercad can NOT handle typedef or 'using' for function pointers. it throws a compilation error. i found one other recent report of someone having issues with typedef. same code compiles without error/warning in arduino ide.

for the record:
- i do NOT like Tinkercad. it's just the only simulator i could find. nothing worse than a simulator that has bugs in it, except silicon with bugs in it.
- i do not like Arduino or its IDE. Visual studio is way better.

Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #28 on: June 03, 2021, 08:10:18 pm »
found a better simulator : wokwi
this one integrates with a web based GDB so i can monitor and trace code as it executes.

so far i can trace into the interrupt handler
but the function does not get called :
Code: [Select]
Daemons[x].state = Daemons[x].handler(0);
this does nothing..
« Last Edit: June 03, 2021, 08:15:55 pm by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 21278
  • Country: nl
    • NCT Developments
Re: storing function pointer and calling
« Reply #29 on: June 03, 2021, 08:21:09 pm »
The best way forward is to test & debug your code on a PC (=compile for and run on a PC) and then transfer to an embedded platform. There is nothing in your code preventing you from doing that. Running on a PC will allow for much easier debugging.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 6964
  • Country: fr
Re: storing function pointer and calling
« Reply #30 on: June 03, 2021, 08:47:34 pm »
- tinkercad can NOT handle typedef or 'using' for function pointers. it throws a compilation error. i found one other recent report of someone having issues with typedef. same code compiles without error/warning in arduino ide.

I don't know what Tinkercad uses as a compiler. I find that odd. For a simulator, I would expect a compiler such as GCC compiling to object code, and then the object code to be simulated. I dunno how Tinkercad does this. Because unless there is something we haven't seen from your code, a compiler having problems with basic typedef declarations would be pretty nasty, and I also wouldn't quite get why Tinkercad would implement their own compiler (if that's the case) while there are good open-source compilers out there.
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #31 on: June 03, 2021, 08:52:26 pm »
The best way forward is to test & debug your code on a PC (=compile for and run on a PC) and then transfer to an embedded platform. There is nothing in your code preventing you from doing that. Running on a PC will allow for much easier debugging.
sure, please tell, how do i attach 3 led's to my pc so i can see the i/o ? and what about the inputs ?
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 610
Re: storing function pointer and calling
« Reply #32 on: June 03, 2021, 08:56:25 pm »
Code: [Select]
if (Daemons[x].state=1){
you want ==


You can also give you handlers access to the daemon, so they can control their own interval/status-
https://godbolt.org/z/GGdrqPeq3
« Last Edit: June 03, 2021, 08:59:04 pm by cv007 »
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 21278
  • Country: nl
    • NCT Developments
Re: storing function pointer and calling
« Reply #33 on: June 03, 2021, 09:26:06 pm »
The best way forward is to test & debug your code on a PC (=compile for and run on a PC) and then transfer to an embedded platform. There is nothing in your code preventing you from doing that. Running on a PC will allow for much easier debugging.
sure, please tell, how do i attach 3 led's to my pc so i can see the i/o ? and what about the inputs ?
Simple: printf("led 1 on\n");
Input are best simulated (don't forget to include invalid values so you can test the code for that as well).

Again: there is nothing in your software that needs the actual hardware in the loop and with testing on a PC you can go through a large number of input combinations and check the output whether it is valid. This is called unit testing which is extremely useful for the kind of problem you are trying to tackle.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #34 on: June 03, 2021, 09:49:18 pm »
Code: [Select]
if (Daemons[x].state=1){

you want ==


You can also give you handlers access to the daemon, so they can control their own interval/status-
https://godbolt.org/z/GGdrqPeq3
yup, i found that.
but i still can't get this to work.

if i call the functions directly from the ISR they work ( led toggles)
if i call the function by functionpointer the behavior is total whack.

Code: [Select]
// working
ISR(TIMER1_COMPA_vect)
{
   led2(0);
  };


Code: [Select]
// not working
ISR(TIMER1_COMPA_vect)
{
   Daemons[1].handler(0);
};


it calls all 3 daemons and called function loses its internal data... it's like the processor restarts .
another thing is , when i call createdaemon the function executes

i can not figure this out ... is it the compiler ? is it the simulator ? is it the code ? what the hell is going on ? i am using wokwi now as tinkercad cannot handle the typedef.
Code: [Select]

typedef int (*functionpointer) (int);
#define max_daemons 10
struct Daemon {
   volatile int counter;              // current counter value
   volatile int interval;             // given interval to reload
   volatile int state;                // runtime state 0 = stop 1 =run
   functionpointer handler;  // function to be called when counter reaches zero
};
int daemoncounter =0;                // keep track of how many we already have
volatile Daemon Daemons[max_daemons]; // the array of daemons
 
int CreateDaemon (int interval, functionpointer handler )
{
   Daemons[daemoncounter].interval = interval;
   Daemons[daemoncounter].handler = handler;
   daemoncounter++;         
   return daemoncounter -1;
};

void StartDaemon (int daemon)
{
   Daemons[daemon].counter = Daemons[daemon].interval;
   Daemons[daemon].state = 1;
};

void PauseDaemon (int daemon)
{
   Daemons[daemon].state = 0;
};
void ResumeDaemon (int daemon)
{
   Daemons[daemon].state = 1;
};
           
void AbortDaemon (int daemon)
{
   Daemons[daemon].state = 0;
   Daemons[daemon].counter = 0;
};

void RunDaemon (int daemon, int command)
{
  Daemons[daemon].state = Daemons[daemon].handler(command);
};


// some dummy routines
int led1 (int command){
  static int l1 ;
  int exitcode = 1;
  pinMode(3,OUTPUT);
  if (command == 0){l1 = ~l1;};             // toggle x
  if (command == 1){l1 = 1 ;};             // make high
  if (command == 2){l1 = 0 ;};             // make low
  if (command == 255){                    // emergency stop
    l1=0;           // turn the led off (safe)
    exitcode =0;   // kill ourselves
  } 
  if (l1 ==0){
    digitalWrite(3,HIGH);
  }
  else {
    digitalWrite(3,LOW);
  }
  Serial.println("l1");
  return exitcode;                        // signal we want to keep running
};


int led2 (int command){
  static int l2 ;
  //pinMode(4,OUTPUT);
  if (l2 == 1 ){
    //digitalWrite(4,HIGH);
    l2 = 0;
  }
  else {
    //digitalWrite(4,LOW);
    l2 = 1;
  }
  Serial.print("led2 :");
  Serial.println(l2);
  return 1;
};

int led3 (int command){
  static volatile int l3 ;
  //pinMode(5,OUTPUT);
  l3 = ~(l3);
  if (l3 ==0){
    digitalWrite(5,HIGH);
  }
  else {
    digitalWrite(5,LOW);
  }
  Serial.println("l3");
  return 1;
};

volatile int dummy;
// the interrupt handler. this fires at a 1 second interval
ISR(TIMER1_COMPA_vect)
{
  //led1(0);
  led2(0);
  //led3(0);
  // Daemons[0].handler(0);
  // Daemons[1].handler(0);
   //Daemons[2].handler(0);
  // for (int x = 0; x <daemoncounter; x++){
    //if (Daemons[x].state==1){                       // if in run state
    //  if (Daemons[x].counter == 0) {                // and we hit zero
    //    dummy = Daemons[x].handler(0);   // -> execute the handler and store return value in state
                                                    //    this allows a function to stop itself by returning 0
                                                    //    we call with argument 0 : normal operation
    //    Daemons[x].counter = Daemons[x].interval;   // -> reload the counter
    //  }
    //  else {
    //    Daemons[x].counter --;                    // else decrement the counter 
    //  };
    //};
 // };
 
}

int led1_daemon =0;
int led2_daemon =0;
int led3_daemon =0;

void setup() {
    Serial.begin(115200);
    led1_daemon = CreateDaemon (1, led1(0));  // blink once a second
    led2_daemon = CreateDaemon (2, led2(0));  // blink every two second
  //  led3_daemon = CreateDaemon (5, led3(0));  // blink every three seconds

    StartDaemon (led1_daemon)  ;
    StartDaemon (led2_daemon)  ;
    StartDaemon (led3_daemon)  ;
    // Set up the hardware countern
    // - mode 4 (CTC)
    // - set prescaler to 1024
    // - using TCCR1A and TCCR1B registers
    // - see datasheet starting on page 170
       TCCR1A = 0b00000000; // set WGM11 and WGM10 bits (table 20-6, p171)
       TCCR1B = 0b00001101; // set WGM13, WGM12 (table 20-6), CS12, CS11, and CS10 bits (table 20-7, p 173)
       OCR1A = 15625;       // set the output compare value (timer interrupt will trigger when the timer reaches this value)
                            // one clock tick is 62.5 nanoseconds @ 16MHz
                            // prescaler * OCR1A * 62.5nS = seconds.
       TIMSK1 = 0b00000010; // enable the interrupt - p184
       sei();               // allow interrupts
}

void loop() {
  // do whatever logic here 
 // led1 (0);
  //led2(0);
  //led3(0);
 // delay(1000);
}
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #35 on: June 03, 2021, 09:55:04 pm »
yup. confirmed. the fucker just restarts when calling the function pointer ...
so the function pointers do not work.
another thing i cannot figure out is why the passed fucntion executes when calling CreateDaemon.

Code: [Select]
typedef int (*functionpointer) (int arg1);

#define max_daemons 10
struct Daemon {
   volatile int counter;              // current counter value
   volatile int interval;             // given interval to reload
   volatile int state;                // runtime state 0 = stop 1 =run
   functionpointer handler;           // function to be called when counter reaches zero
};
int daemoncounter =0;                // keep track of how many we already have
volatile Daemon Daemons[max_daemons]; // the array of daemons
 
int CreateDaemon (int interval, functionpointer handler )
{
   Daemons[daemoncounter].interval = interval;
   Daemons[daemoncounter].handler = handler;
   daemoncounter++;         
   return daemoncounter -1;
};

void StartDaemon (int daemon)
{
   Daemons[daemon].counter = Daemons[daemon].interval;
   Daemons[daemon].state = 1;
};

void PauseDaemon (int daemon)
{
   Daemons[daemon].state = 0;
};
void ResumeDaemon (int daemon)
{
   Daemons[daemon].state = 1;
};
           
void AbortDaemon (int daemon)
{
   Daemons[daemon].state = 0;
   Daemons[daemon].counter = 0;
};

void RunDaemon (int daemon, int command)
{
  Daemons[daemon].state = Daemons[daemon].handler(command);
};


// some dummy routines
int led1 (int command){
  static int l1 ;
  int exitcode = 1;
  pinMode(3,OUTPUT);
  if (command == 0){l1 = ~l1;};             // toggle x
  if (command == 1){l1 = 1 ;};             // make high
  if (command == 2){l1 = 0 ;};             // make low
  if (command == 255){                    // emergency stop
    l1=0;           // turn the led off (safe)
    exitcode =0;   // kill ourselves
  } 
  if (l1 ==0){
    digitalWrite(3,HIGH);
  }
  else {
    digitalWrite(3,LOW);
  }
  Serial.println("l1");
  return exitcode;                        // signal we want to keep running
};


int led2 (int command){
  static int l2 ;
  //pinMode(4,OUTPUT);
  if (l2 == 1 ){
    //digitalWrite(4,HIGH);
    l2 = 0;
  }
  else {
    //digitalWrite(4,LOW);
    l2 = 1;
  }
  Serial.print("led2 :");
  Serial.println(l2);
  return 1;
};

int led3 (int command){
  static volatile int l3 ;
  //pinMode(5,OUTPUT);
  l3 = ~(l3);
  if (l3 ==0){
    digitalWrite(5,HIGH);
  }
  else {
    digitalWrite(5,LOW);
  }
  Serial.println("l3");
  return 1;
};

volatile int dummy;
// the interrupt handler. this fires at a 1 second interval
ISR(TIMER1_COMPA_vect)
{
   Serial.println("START interrupt handler");
   Daemons[0].handler(0); // <- this crashes the processor. it restarts
   Daemons[1].handler(0);
   Daemons[2].handler(0);
  //led1(0);
  //led2(0);
  //led3(0);
   Serial.println("END   interrupt handler");
  // for (int x = 0; x <daemoncounter; x++){
    //if (Daemons[x].state==1){                       // if in run state
    //  if (Daemons[x].counter == 0) {                // and we hit zero
    //    dummy = Daemons[x].handler(0);   // -> execute the handler and store return value in state
                                                    //    this allows a function to stop itself by returning 0
                                                    //    we call with argument 0 : normal operation
    //    Daemons[x].counter = Daemons[x].interval;   // -> reload the counter
    //  }
    //  else {
    //    Daemons[x].counter --;                    // else decrement the counter 
    //  };
    //};
 // };
 
}

int led1_daemon =0;
int led2_daemon =0;
int led3_daemon =0;

void setup() {
    Serial.begin(115200);
    Serial.println ("Entering setup");
    Serial.println ("Creating Daemons");
    led1_daemon = CreateDaemon (1, led1(0));  // blink once a second
    led2_daemon = CreateDaemon (2, led2(0));  // blink every two second
    led3_daemon = CreateDaemon (5, led3(0));  // blink every three seconds
    Serial.println ("Starting Daemons");
    StartDaemon (led1_daemon)  ;
    StartDaemon (led2_daemon)  ;
    StartDaemon (led3_daemon)  ;
    Serial.println ("Starting Hardware Timer");
    // Set up the hardware countern
    // - mode 4 (CTC)
    // - set prescaler to 1024
    // - using TCCR1A and TCCR1B registers
    // - see datasheet starting on page 170
       TCCR1A = 0b00000000; // set WGM11 and WGM10 bits (table 20-6, p171)
       TCCR1B = 0b00001101; // set WGM13, WGM12 (table 20-6), CS12, CS11, and CS10 bits (table 20-7, p 173)
       OCR1A = 15625;       // set the output compare value (timer interrupt will trigger when the timer reaches this value)
                            // one clock tick is 62.5 nanoseconds @ 16MHz
                            // prescaler * OCR1A * 62.5nS = seconds.
       TIMSK1 = 0b00000010; // enable the interrupt - p184
       sei();               // allow interrupts
}

void loop() {
  // do whatever logic here 
 // led1 (0);
  //led2(0);
  //led3(0);
 // delay(1000);
}
« Last Edit: June 03, 2021, 10:11:06 pm by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #36 on: June 03, 2021, 10:13:29 pm »
success !

the problem lies here :
when Creating the Daemon , the passed function must be called without arguments.
Code: [Select]
led1_daemon = CreateDaemon (1, led1);   // <- this works correctly
Code: [Select]
led1_daemon = CreateDaemon (1, led1(0));   // <- this crashes the cpu when attempting to call the pointer
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 2491
  • Country: dk
Re: storing function pointer and calling
« Reply #37 on: June 03, 2021, 10:27:41 pm »
success !

the problem lies here :
when Creating the Daemon , the passed function must be called without arguments.
Code: [Select]
led1_daemon = CreateDaemon (1, led1);   // <- this works correctly
Code: [Select]
led1_daemon = CreateDaemon (1, led1(0));   // <- this crashes the cpu when attempting to call the pointer

ofcourse as expected, the one that works sets your function pointer to the address of led1, the one that doesn't work sets you function pointer to the return value of led1(0)

the second one should also produce a warning like "makes pointer from integer without a cast"


 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #38 on: June 03, 2021, 10:35:52 pm »
success !

the problem lies here :
when Creating the Daemon , the passed function must be called without arguments.
Code: [Select]
led1_daemon = CreateDaemon (1, led1);   // <- this works correctly
Code: [Select]
led1_daemon = CreateDaemon (1, led1(0));   // <- this crashes the cpu when attempting to call the pointer

ofcourse as expected, the one that works sets your function pointer to the address of led1, the one that doesn't work sets you function pointer to the return value of led1(0)

the second one should also produce a warning like "makes pointer from integer without a cast"

hindsight is always 20:20 ... no warnings or errors were thrown.
this is why i hate these kind if systems. you spend hours on a tiny little detail and the IDe is not very forthcoming with what is wrong.

if the language had two simple operators : address_of  and call_by_reference  or something similar and a proper debugger and a correct simulator. and an ide where you could set watchpoints and properly trace through interrupts ... and and and.

Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 21278
  • Country: nl
    • NCT Developments
Re: storing function pointer and calling
« Reply #39 on: June 03, 2021, 10:45:08 pm »
hindsight is always 20:20 ... no warnings or errors were thrown.
this is why i hate these kind if systems. you spend hours on a tiny little detail and the IDe is not very forthcoming with what is wrong.
That is why you develop on a PC first and then move to an embedded platform. On a PC you could have single stepped through the code and spot the problem right away. But you'd likely got a warning during compilation which hinted towards the problem. Instead you tried to run before you can walk.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 2491
  • Country: dk
Re: storing function pointer and calling
« Reply #40 on: June 03, 2021, 11:51:33 pm »
success !

the problem lies here :
when Creating the Daemon , the passed function must be called without arguments.
Code: [Select]
led1_daemon = CreateDaemon (1, led1);   // <- this works correctly
Code: [Select]
led1_daemon = CreateDaemon (1, led1(0));   // <- this crashes the cpu when attempting to call the pointer

ofcourse as expected, the one that works sets your function pointer to the address of led1, the one that doesn't work sets you function pointer to the return value of led1(0)

the second one should also produce a warning like "makes pointer from integer without a cast"

hindsight is always 20:20 ... no warnings or errors were thrown.
this is why i hate these kind if systems. you spend hours on a tiny little detail and the IDe is not very forthcoming with what is wrong.

if the language had two simple operators : address_of  and call_by_reference  or something similar

and it does, & and *

and a proper debugger and a correct simulator. and an ide where you could set watchpoints and properly trace through interrupts ... and and and.

run it on a PC or pick a tool and mcu that isn't stuck in 80's

 
The following users thanked this post: newbrain

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #41 on: June 04, 2021, 12:27:32 am »
hindsight is always 20:20 ... no warnings or errors were thrown.
this is why i hate these kind if systems. you spend hours on a tiny little detail and the IDe is not very forthcoming with what is wrong.
That is why you develop on a PC first and then move to an embedded platform. On a PC you could have single stepped through the code and spot the problem right away. But you'd likely got a warning during compilation which hinted towards the problem. Instead you tried to run before you can walk.
no warnings during compilation. the arduino ide said : compilation complete and spat out a binary.

I don't like the your approach of having to emulate the i/o code. i want to see the led turn on and the display display what it needs. How do you emulate things like rotary encoders or hardware timers and interrupts ? or a control loop that interacts with the analog world ?
That becomes very difficult to emulate. That's where you hook up a board to a JTAG probe and have a true debugger that lets you snoop around while the machine is running. alas. this arduino stuff is braindead in that world... the second best is a system emulator , but those are also braindead as they are not true machine simulators as i foudn out. although this WokWi seems to be the real thing , it lets you connect to a GDB session and you can actually set breakpoints and trace the program with all bells and whistles of GDB, that's how i found out most of the issues i had.

But you are right, i am used to having a real IDE like visual Basic (visual studio) or the IAR/ Keil tools in combination with hardware emulators.

This whole arduino ecosystem is like being in the 60's .. debugging using printf. and then simulators that don't work right.
i don't have patience for this kind of stuff anymore. All these little intricacies. i want simple things that work.  i'm going back to Keil and Cortex processors.
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline IanB

  • Super Contributor
  • ***
  • Posts: 9940
  • Country: us
Re: storing function pointer and calling
« Reply #42 on: June 04, 2021, 12:37:15 am »
This whole arduino ecosystem is like being in the 60's .. debugging using printf. and then simulators that don't work right.
i don't have patience for this kind of stuff anymore. All these little intricacies. i want simple things that work.  i'm going back to Keil and Cortex processors.

There's more to the Arduino ecosystem than the basic Arduino IDE.

Here's a video with some pointers to better tools:
https://youtu.be/sm6QxJkWcSc
I'm not an EE--what am I doing here?
 
The following users thanked this post: free_electron

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #43 on: June 04, 2021, 12:38:51 am »

and it does, & and *
and they are a pain to get working... typedefs and other chinese ...

Quote
]

run it on a PC or pick a tool and mcu that isn't stuck in 80's
running on a pc : how ? the problem is the i/o emulation. i want to test this attached to a bunch of switches , leds' and displays. and analog stuff. The final function of this code a solar thermostat. it needs to monitor boundaries coming from analog world, timers telling it what to do when. it's all interrupt driven. emulating that on a pc is impossible.

As for stuck in 80's: The cpu is not the issue. The development environment is ! i can develop code for 8051 faster because i have proper tools and correctly working simulators/jtag probes. the Avr processor is not the issue, the environment is the problem.
in that aspect the Wokwi compiler is much more informative about typo's and bugs than the arduino ide. it really helped me finding the issues.



Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 610
Re: storing function pointer and calling
« Reply #44 on: June 04, 2021, 01:22:50 am »
Turn on -Werror, so you cannot proceed until any/all problems are resolved. There are no warnings that are ok, because they all have a solution. Also not a bad idea to turn on -Warray-bounds.

your code with -Werror
https://godbolt.org/z/5z1Grc71z

There is also a possibility your handlers may not be set, so only call them when not null unless you want to be back at the reset vector someday.

I have a number of links posted in this thread, so it should now be obvious I use that online compiler to test out ideas all the time (avr, cortex-m, pc).
Here is what I would do to figure out what is going on in function pointers for an avr-
https://godbolt.org/z/n8EvxPETT
You can see the asm output and figure out what is going on. If you do not know avr asm, you can learn as you go.

Probably easier than segfaulting you way through a pc version, or resetting your way on the avr. If you can understand the connection from your code to the asm output, that goes a long way to solving problems that may be from something that is unclear to you in the language. Maybe 5 minutes in the online compiler will make it clear what you get when you do x.
 
The following users thanked this post: free_electron

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #45 on: June 04, 2021, 01:59:45 am »
Turn on -Werror, so you cannot proceed until any/all problems are resolved. There are no warnings that are ok, because they all have a solution. Also not a bad idea to turn on -Warray-bounds.

your code with -Werror
https://godbolt.org/z/5z1Grc71z

the online compiler is nice but: it can't compile the real code. There are too many arduino specific functions and constants that it does not know. It's good for trying little things left and right but not really usefull for large code blocks. it is more a tool to see what the compiler produces and compare compiler output based on the flags.
That is the issue using something else than the actual system. Same reason i don't want to emulate on a pc.

For me the only acceptable solution is either a true simulator with watchpoints , breakpoints and hardware emulation , or an actual board with a jtag debugger.
I would prefer an emulator where you can step forward AND backward and do inline code edits without requiring recompilation. Have an immediate mode where you can try out simple statements, call functions, examine variables etc. like what you can do in visual basic.
You can develop code interactively and step forward, backward inject lines , alter data at will. That WOKWI allows me part of it. i can at least see what is going on. Just the GDB user interface is cranky. Too much keyboard pounding to examine variables. It needs a memory viewer, register viewer, stack viewer, watchpoints. variable watchpoints. kind of like DDD .. if Wokwi would be able to connect to DDD that would be a huge step forward to trace and analyse code. It would be even better if you had a debugger like visual studio. Stop the code hover mouse over variable and you can see the contents. click the variable and a input box comes up where you can alter the contents. Have an instruction pointer you can move. But then on these little microcontroller that would be hard. it's not like they can do JIT. At least interactively invoking a routine should be possible. Put the thing in break and simply go "print led(1);" and see what the return code is and be able to trace the function.
Those are simple , but extremely helpful to debug a block of code. interactively launch a routine and trace it out while being able to see and modify all internal variables used. The IAR/Keil  tools can do that. I woudl have thought that Arduino woudl have evolved to such capabilities by now.

i wonder if that PlatformIO system can do such things ? it is visual studio based. But then i think it does not have simulation abilities... but if it can do it via a JTAG connection then it would be golden.

At this point that Labcenter electronics VSM is starting to look attractive ... you can draw a schematic including parts like displays and whatnot , hook up virtual logic analyser and oscilloscope and actually run/trace the code. i need to look into that.
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 2252
  • Country: nz
  • Formerly SiFive, Samsung R&D
Re: storing function pointer and calling
« Reply #46 on: June 04, 2021, 02:39:04 am »
PlatformIO is not Visual Studio based.

It can use VSCode (a different, cross-platform, thing) as the IDE. It can use many other things too. Eclipse, for example. Or Atom.

It does debugging using JTAG, gdb and a GUI.
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 7653
  • Country: us
    • SiliconValleyGarage
Re: storing function pointer and calling
« Reply #47 on: June 04, 2021, 05:19:01 am »
PlatformIO is not Visual Studio based.

It can use VSCode (a different, cross-platform, thing) as the IDE. It can use many other things too. Eclipse, for example. Or Atom.

It does debugging using JTAG, gdb and a GUI.
oh ? i saw in that video he was installing visual studio code and then installing platformio plugin. so i thought it was visual studio based.
i need to look into those things. i'm getting out of touch with what is out there.

I was just trying to help someone out with a proof of concept based on an arduino cause that is what they had laying around ( student in india...  ).

This microscheduler is something i have used many many times but written in PL/M on 8051 or Basic on various other processors ( Mikroe basic for pic, avr , arm  , Bascom avr , oshonsoft basic, swordfish basic for pic f18 ,  B4R  , Great Cow Basic , Annex on ESP32 and ESP8266

Porting it to Arduino C proved to be a painful learning curve. First because of the C compiler idiosyncrasies (tinkercad can't compile it at all , Arduino IDE compiles without error/warning but the binary doesn't work (i initially did not have an arduino around so i was banking on the simulators, this afternoon i went out and bought a board to try it on) , Wokwi finally let me trace it using GDB ) and second because of the bad simulators. I'm not a c wizard. i can read it , but writing it and knowing all the intimate details is not for me. (like knowing how to create a function pointer and then store that in a struct. A function pointer is nothing but an address , which in this case is a 16 bit number so it can be stored in an u_int16. i first attempted to simply store that as an integer but that didn't work. C deos't work that way.
in PL/M anything is passed by reference . whether variable or function.

if can do
Code: [Select]

Procedure Createdaemon (word interval , word *function_to_be _called)  // the * signifies what follows is not a variable but a function so it stores addressof
  table (1,1) = interval
  table(1,2) = function_to_be_called
end procedure

...
call table(1,2)  : calls the stored address
of course call table (1,1) would crash the program as that is a regular value. basically the 'call operator' moves the value stored at (1,1) onto the instruction pointer. if you put arguments , it performs push and pop operations on the stack
Code: [Select]
result = call table(1,2)(some_argument, some_other_argument)

compiles to
push some_other_argument
push some_argument
push ip+4 ' push return address
mov ip @table(1,2)  ' move content of table 1,2 to instruction pointer
pop result;

the code of the function is
[code]
pop return_address
pop arg1
pop arg2
..... do what needs doing
push result
mov ip return_address
as simple as can be.

It irritates me that a platform that is so popular is in such a poor state and so difficult to get started with. A little bit from here, a bit from there , futz it together , drop some more gobbledegook from somewhere else. I'm used to tools that just work. Launch setup and start coding. No need to puzzle it together.

Anyway, got it running, student understand the logic of a scheduler.
No more delay and sleep operations that block the CPU. you can create little tasks , set an interval and start/stop/pause/resume them and the tasks can stop themselves and react to emergency signals. that was the goal. non concurrent , cooperative multitasking micro threads on small microcontrollers.

The task was to write a small monitor for a water boiler.
if the boiler is on : blink an led at 1 second interval and count up two counters (one lifetime run time , one current run time)
display lifetime and current runtime on lcd
also display temperature (read from analog input)

so we have a simple function that , upon calling , toggles an i/o and increments 2 counters.
in the main program we simply control the runstate of that routine. the hardware timer does the rest.
basically the main program reads a digital input which directly controls the run state of the routine.
the main program then displays the data.

int temperature, totalrun, currentrun;
createdaemon (1,turn_off_all_leds)
forever :{
  DaemonState(counters) = digitalread(3)
  daemonstate(runled) = digitalread(3)
  daemonstate(overtemp) = (temperature >60)   // fire his as long as we are too hot  , sets the overtemp led to on
  daemonstate(toocold) = (temperature < 10)    // sets the toocold led to on
  daemonstate(frostprotection) = (temperature <1) // opens a bleedvalve
  lcdprint totalrun , currentrun,temperature
  if digitalread(3) = 0 then currentrun=0;
 }

since it runs on a hardware timer i don't care how long the lcd processing routine really takes. it will fire at exactly 1 second interval. as long as the called routines can all complete within the timer interval this program will run correctly. and 1 second is a very generous interval.
« Last Edit: June 04, 2021, 05:43:07 am by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 17430
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: storing function pointer and calling
« Reply #48 on: June 04, 2021, 06:39:34 am »
Some more lecturing/philosophizing -- if it helps:

- C isn't BASIC.  Why are you trying to use it as something it's not?

- Have you considered putting BASIC on it and just doing that?

Example:
https://hackaday.com/2011/08/28/basic-programming-on-an-arduino/
TinyBasic maybe too simple/trivial for your purposes, or doesn't expose enough (any) Arduino or hardware resources, fair.

Looks like there's a more fleshed out version here?  Or maybe it's pretty similar, Idunno.
https://github.com/robinhedwards/ArduinoBASIC
It's probably? easy enough to add wrapper functions around Arduino built-ins?  Doesn't exactly avoid working in C, in a project much more complicated than the original problem, of course...

I should note, it's classic BASIC, not QBASIC and the like, there's no SUB or FUNCTION in there.  So you have to define new keywords, by the looks of it, not just create your own subroutines/functions and call into them (whether declared in the program or preexisting in the environment).

If you can define your platform (AVR, ARM, which model?), there may also be a more bare-metal version available too.  Of course then you'll have to import Arduino libs entirely, YMMV.

- Since you're stuck with C (the above options notwithstanding?), and not especially well versed in it... why get frustrated at it?  It's a huge messy pile of grammar, syntax and logic.  I wouldn't expect to get very far in something like that, myself.  (I think I've taken a good month or a few at a time, at various stages through my life, learning the language.)  Take it slow, read up, find all the manuals, find relevant StackOverflow articles, the works.  Compilers are horrendously complex tools, and their documentation is only slightly less inscrutable.  Draw on a mix of resources until you're comfortable with how things are described (e.g. often in terms of other objects or relationships, which are described in terms of..).

- If you just want to get some stupid thing done, don't be afraid to give the specific example and ask about it.  (This is a bit redundant I suppose, as this has more or less already happened in this thread.)

- As for the actual problem, as has been more or less covered already -- as you can see, C doesn't really like not knowing what it's doing.  You need correct types at all times, or else you're just casting everything to void and praying...  Getting complex types right is  PITA, but it's still a bit better than the alternative.

As far as dynamic execution goes, I don't know that there's really a good or standard way to do that, and it'll be platform-dependent to some extent.  Like on AVR, you need to flash a payload to PROGMEM before you can execute it.  If you had an operating system, you could use whatever features it provides -- you might load a DLL on the PC, a surprisingly simple operation actually, from the program's perspective.  But even with an RTOS, you're likely calling static (defined at compile time) pointers, or not even that but just hard-wiring all the required function calls into the main() loop or whatever.

An architectural point: why make something code-dependent, like passing an actual (physical) pointer over serial?  Besides errors (one bit off and it's probably a crash), this exposes way too much implementation over the interface.  Nevermind possible vulnerabilities (an actual arbitrary-call op is a hacker's wet dream; is it even a zero-day if it's part of the spec!?).  A better option would be a function table (maybe this is what you were after in the first place and I've skimmed over that?) so you just say, "give me operation 12", instead of "execute memory location 0x0BADF00D".  With bounds checking of course, so you aren't executing pointers that don't exist.  Most importantly, each binary can then be updated by itself, without the other side having to know that.  In short, creating an API.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 2252
  • Country: nz
  • Formerly SiFive, Samsung R&D
Re: storing function pointer and calling
« Reply #49 on: June 04, 2021, 06:51:45 am »
Porting it to Arduino C proved to be a painful learning curve. First because of the C compiler idiosyncrasies (tinkercad can't compile it at all , Arduino IDE compiles without error/warning but the binary doesn't work

C is a very very simple language. It maps directly to assembly language, with just the addition of a little syntax and a little type checking. But you can always cast a value to any other type of the same size if you know what it really is and the compiler doesn't. In particular casting between functions or pointers and integers is trivial and done all the time.

It would be well worth your time to learn it properly.

"Arduino C" is entirely standard gcc. The only difference is a preprocessor finds your global declarations and automatically prefixes your code with forward declarations for everything, so you can put your code in any order (like in Java and others) and it works. This doesn't affect standard C code.

Of course Arduino provides their own library to hide differences between different hardware, and to enable its use on machines with often only 2 KB of RAM and 32 KB of flash (and I've used it on AVRs with a quarter of that). A standard C printf() implementation would blow out the program ROM on many devices.

Quote
I'm not a c wizard. i can read it , but writing it and knowing all the intimate details is not for me. (like knowing how to create a function pointer and then store that in a struct. A function pointer is nothing but an address , which in this case is a 16 bit number so it can be stored in an u_int16. i first attempted to simply store that as an integer but that didn't work. C deos't work that way.

C is very simple, but you do have to actually read a manual, not just try to intuit it.

Quote
in PL/M anything is passed by reference . whether variable or function.

Ouch!

In C everything is passed by value, whether variable or function.

If you mention a function or array just by the name, rather than calling it or indexing it, then the value is a pointer to it.

Quote
if can do
Code: [Select]

Procedure Createdaemon (word interval , word *function_to_be _called)  // the * signifies what follows is not a variable but a function so it stores addressof
  table (1,1) = interval
  table(1,2) = function_to_be_called
end procedure

...
call table(1,2)  : calls the stored address

It's very little different in C.

Here's a complete example.

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

typedef void (*fn_t)(void);

#define NDAEMONS 10
#define NDAEMON_ATTRS 2

intptr_t table[NDAEMONS][NDAEMON_ATTRS];

void CreateDaemon(int daemon, intptr_t interval, fn_t fn){
  table[daemon][0] = interval;
  table[daemon][1] = (intptr_t)fn;
}

void call(int daemon){
  ((fn_t)table[daemon][1])();
}

void foo(){
  printf("hi from daemon\n");
}

int main(){
  CreateDaemon(7, 13, foo);
  call(7);
  return 0;
}

Quote
of course call table (1,1) would crash the program as that is a regular value. basically the 'call operator' moves the value stored at (1,1) onto the instruction pointer. if you put arguments , it performs push and pop operations on the stack

Stack? Arguments on the stack?

None of the common Arduino CPU types do that. AVR, ARM, RISC-V ... all pass arguments in registers.

Here's the previous program compiled for 32 bit RISC-V...

Code: [Select]
00010106 <foo>:
   10106:       1141                    addi    sp,sp,-16
   10108:       c606                    sw      ra,12(sp)
   1010a:       6549                    lui     a0,0x12
   1010c:       50450513                addi    a0,a0,1284 # 12504 <__errno+0x8>
   10110:       2acd                    jal     10302 <puts>
   10112:       40b2                    lw      ra,12(sp)
   10114:       0141                    addi    sp,sp,16
   10116:       8082                    ret

00010118 <CreateDaemon>:
   10118:       00351793                slli    a5,a0,0x3
   1011c:       07418513                addi    a0,gp,116 # 13d9c <table>
   10120:       953e                    add     a0,a0,a5
   10122:       c10c                    sw      a1,0(a0)
   10124:       c150                    sw      a2,4(a0)
   10126:       8082                    ret

00010128 <call>:
   10128:       1141                    addi    sp,sp,-16
   1012a:       c606                    sw      ra,12(sp)
   1012c:       00351793                slli    a5,a0,0x3
   10130:       07418513                addi    a0,gp,116 # 13d9c <table>
   10134:       953e                    add     a0,a0,a5
   10136:       415c                    lw      a5,4(a0)
   10138:       9782                    jalr    a5
   1013a:       40b2                    lw      ra,12(sp)
   1013c:       0141                    addi    sp,sp,16
   1013e:       8082                    ret

00010140 <main>:
   10140:       1141                    addi    sp,sp,-16
   10142:       c606                    sw      ra,12(sp)
   10144:       6641                    lui     a2,0x10
   10146:       10660613                addi    a2,a2,262 # 10106 <foo>
   1014a:       45b5                    li      a1,13
   1014c:       451d                    li      a0,7
   1014e:       37e9                    jal     10118 <CreateDaemon>
   10150:       451d                    li      a0,7
   10152:       3fd9                    jal     10128 <call>
   10154:       4501                    li      a0,0
   10156:       40b2                    lw      ra,12(sp)
   10158:       0141                    addi    sp,sp,16
   1015a:       8082                    ret

Quote
It irritates me that a platform that is so popular is in such a poor state and so difficult to get started with. A little bit from here, a bit from there , futz it together , drop some more gobbledegook from somewhere else. I'm used to tools that just work. Launch setup and start coding. No need to puzzle it together.

Arduino is *extremely* easy to get started with. Literally millions of people have done so. Before Arduino microcontrollers were completely unapproachable.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf