C++ class::method() as driver callback

ChibiOS public support forum for all topics not covered by a specific support forum.

Moderators: RoccoMarco, lbednarz, tfAteba

Post Reply
Debbio
Posts: 16
Joined: Fri Jan 11, 2013 10:42 am

C++ class::method() as driver callback

Post by Debbio »

I want to encapsulate for example the uart driver inside a protocol communication class :

Code: Select all

class protocol{
   UARTDriver  *uart_drv;
   UARTConfig uart_cfg;
 public :
   protocol(....){
    ...
    uart_cfg.rxEnd = (  ? cast ? ) protocol::method;
  }
private :
  method(){
    ...
 }
}


What kind of cast is it correct ?
Something like this ? (not really tried)

Code: Select all

 typedef void(cb*)(UARTDriver*);
  uart_cfg.rxEnd =  reinterpret_cast<cb>(&protocol::method);


Maybe encapsulation of drivers inside class wouldn't be the best design choice?

Thanks in advance.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: C++ class::method() as driver callback

Post by Giovanni »

The callbacks are part of the configuration structure usually, I don't think you should put driver and configuration structure in the same class. The reference to the configuration is already present in the driver so it would be a duplication.

Giovanni
Debbio
Posts: 16
Joined: Fri Jan 11, 2013 10:42 am

Re: C++ class::method() as driver callback

Post by Debbio »

About casting , this works :

Code: Select all

cfg.callback= (void(*)(GPTDriver*))&Protocol::method;


BUT IT IS USELESS , inside the call back you have no reference to your class members, complier won't complain but you cannot use you class::method or your class variable , for me it was not obvious .
You need to pass the reference of your class as argument as chibios does for the driver reference.

Code: Select all

/** Uart callback assigned to class method*/
void protocol::method(UARTDriver *d){
 classVariable = value;  /** Not affecting classVariable*/
 classMethod();  /** see the former */
}


So if you want to use some driver callback my conclusion is that you have to pass data , in this case chars on rx buffer , to the worker thread class with mailbox and so on.

Here some pseudo code , i haven't tried it yet:

Code: Select all


void txend(UARTDriver*, uint16_t c){
   
   chMbPostI(mb, c ); /** and you need to share mailbox with worker thread class*/
}

class protocol : public BaseStaticThread<256> {
  ...
  msg_t main(void){
   
    while(1){
         ...
        chMbFetch(mb,data,timeout);
         .....
     }
  }
}


And what about changing your class internal state synchronously to a driver callback ?
you will need to know the reference of the worker thread or to the class itself , is it the cleanest way ?

Code: Select all

 void rxend(GPTDriver* d , uint16_t c){
   
   classRef->resumeI(msg); /** clean solution ? */
 }


Design suggestions well accepted.
andyturk
Posts: 23
Joined: Tue May 01, 2012 7:22 pm

Re: C++ class::method() as driver callback

Post by andyturk »

I routinely implement driver callbacks in C++. To do this, I usually derive classes from configuration structs rather than the driver structs. Here's an example:

Code: Select all

  class UART : protected UARTConfig {
  protected:
    UARTDriver &uart;

    static void txend1_cb(UARTDriver *u);
    static void txend2_cb(UARTDriver *u);
    static void rxend_cb(UARTDriver *u);
    static void rxchar_cb(UARTDriver *u, uint16_t c);
    static void rxerr_cb(UARTDriver *u, uartflags_t e);

    virtual void txend1() {}
    virtual void txend2() {}
    virtual void rxend() {}
    virtual void rxchar(uint16_t c) {}
    virtual void rxerr(uartflags_t err) {}
   
  public:
    UART(UARTDriver &u, uint32_t baud = 115200, uartflags_t cr3_flags = 0);

    uartstate_t get_state() const       {return uart.state;}
    void set_baud(uint32_t baud);

    void start();
    void stop();

    void send (void *bytes, size_t len) {uartStartSend    (&uart, len, (uint8_t *) bytes);}
    void sendI(void *bytes, size_t len) {uartStartSendI   (&uart, len, (uint8_t *) bytes);}
    void recv (void *bytes, size_t len) {uartStartReceive (&uart, len, (uint8_t *) bytes);}
    void recvI(void *bytes, size_t len) {uartStartReceiveI(&uart, len, (uint8_t *) bytes);}
  };


The callback implementations look like this:

Code: Select all

  void UART::txend1_cb(UARTDriver *d) {
    UART *u = (UART *) d->config;
    u->txend1();
  }


The callback pointers in the config struct are initialized in the constructor:

Code: Select all

  UART::UART(UARTDriver &u, uint32_t baud, uint32_t cr3_flags) :
    UARTConfig(),
    uart(u)
  {
    UARTConfig::txend1_cb = &UART::txend1_cb;
    UARTConfig::txend2_cb = &UART::txend2_cb;
    UARTConfig::rxend_cb  = &UART::rxend_cb;
    UARTConfig::rxchar_cb = &UART::rxchar_cb;
    UARTConfig::rxerr_cb  = &UART::rxerr_cb;

    speed = baud;
    cr1 = 0;
    cr2 = 0;
    cr3 = cr3_flags;
  }
Post Reply