kjl kjl - 2 months ago 8x
C Question

Pebble: How to pass an integer as callback_data with app_timer_register?

Summary of answer:
app_timer_register passes a pointer without type.
To pass an integer, create an integer and pass it's pointer.
Then type the pointer to integer.

example of using the untyped pointer:

static void timeHandler(void * untyped_pointer_to_an_integer){

I'm trying to pass an integer along with the code called by app_time_register.
When I try the following:

static void time_handler(int val){
app_timer_register(wait_time_ms, time_handler, 1);

I get the error:

../src/main.c:88:3: error: passing argument 2 of 'app_timer_register' from incompatible pointer type [-Werror]
In file included from ../src/main.c:1:0:
/app/sdk3/pebble/chalk/include/pebble.h:2656:11: note: expected 'AppTimerCallback' but argument is of type 'void (*)(int)'
../src/main.c:88:3: error: passing argument 3 of 'app_timer_register' makes pointer from integer without a cast [-Werror]
In file included from ../src/main.c:1:0:
/app/sdk3/pebble/chalk/include/pebble.h:2656:11: note: expected 'void *' but argument is of type 'int'

Pebble provides the following information about app_time_register (source https://developer.pebble.com/docs/c/Foundation/Timer/).

AppTimer * app_timer_register(uint32_t timeout_ms, AppTimerCallback callback, void * callback_data)
Registers a timer that ends up in callback being called some specified time in the future.
The expiry time in milliseconds from the current time
The callback that gets called at expiry time
The data that will be passed to callback

typedef void(* AppTimerCallback)(void *data)
The type of function which can be called when a timer fires. The argument will be the callback_data passed to app_timer_register().

I need helping figuring out how to use the callback_data to pass an integer.


Figure out what's wrong

Well, both the documentation and the error is very explicit: app_timer_register has the signature:

AppTimer * app_timer_register(uint32_t, AppTimerCallback, void *);

Thus, when calling it, you need to pass an uint32_t as first, an AppTimerCallback as second and a pointer to void (void *) as third argument.

You call it with ...

app_timer_register(wait_time_ms, time_handler, 1);

... a wait_time_ms (which is apparently fine), a pointer to the function time_handler (which is of type void (*)(int)) and an integer (which your compiler - rightfully - does not want to convert to a void * pointer).

Looking up the documentation, AppTimerCallback is basically a typedef for a (pointer to) function taking a void pointer as single argument, or more explicit: void (*)(void *)

As you see, the types of argument 2 (void (*)(int) vs the expected void (*)(void *)) and argument 3 (int vs the expected void *) do not match.

Think about how it needs to be changed and why

The reason to have void * as third argument (and as argument for the callback function) is to be able to pass arbitrary data to the callback function (as a void * can point to everything).

This is a idiom often found in C APIs that provide callbacks, it covers up the lack of closures of the language.

That arbitrary data is most often packed up in a structure (though a single integer - like in your case - could also be "smuggled" as pointer, but this is not scale-able and more over implementation defined):

struct time_handler_data {
  int val; // oh my ... we know it's a "value" ... make it more descriptive!

Thus, your callback needs to look something like ...

void time_handler(void * passed) {
  struct time_handler_data * data = passed;
  // ... do stuff with data->val

... and when you register the callback, you need to actually provide memory for such a structure:

struct time_handler_data * data = malloc(sizeof(*data));
// data->val = a fancy value to be used in time_handler
// also check if data != NULL!
AppTimer * you_certainly_need_this =
  app_timer_register(what_ever, &time_handler, data);

Note that using heap allocated memory is of course not mandatory, you could also use memory with static ("globals") or automatic ("stack") storage.


Registering a callback inside the callback itself is probably (I haven't looked into the documentation) not the way it's intended to be. Normally it's more like ...

void /* or not */ register_time_handler(/* fancy arguments */)
   // code calling app_timer_register from above

... and having no call to app_timer_register in the actual callback (time_handler).