Turn set of functions and attribute into a class

Hi,

I have a .h file with a set of functions that use a set of attributes (some static). I would like to be able to use those functions into my main program as well as library included into the program. But if I include the file in both the main program and the library I want to use it, I have error for redeclaration.
I thought the making a class out of this was a solution, but since there are Static and Volatile attribute, I haven’t succeed.

Could anyone help me please?

Here is the code in dma.h :

//
//  dma.h
//  
//
//  Created by Théo Meyer on 10/11/2015.
//
//

#ifndef ____dma__
#define ____dma__

#include <SPI.h>
#define SPISPEED 12000000

#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)


// DMA   12 channels
typedef struct {
    uint16_t btctrl;
    uint16_t btcnt;
    uint32_t srcaddr;
    uint32_t dstaddr;
    uint32_t descaddr;
} dmacdescriptor ;
    
volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));
dmacdescriptor descriptor __attribute__ ((aligned (16)));

static uint32_t chnltx=0, chnlrx=1; // DMA channels
enum XfrType { DoTX, DoRX, DoTXRX};
static XfrType xtype;
static uint8_t rxsink[1], txsrc[1]= {0xff};
volatile uint32_t dmadone;

void DMAC_Handler() {
    // interrupts DMAC_CHINTENCLR_TERR DMAC_CHINTENCLR_TCMPL DMAC_CHINTENCLR_SUSP
    uint8_t active_channel;
    
    // disable irqs ?
    __disable_irq();
    active_channel =  DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // get channel number
    DMAC->CHID.reg = DMAC_CHID_ID(active_channel);
    dmadone = DMAC->CHINTFLAG.reg;
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; // clear
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
    __enable_irq();
}

 void dma_init() {
     
    // probably on by default
    PM->AHBMASK.reg |= PM_AHBMASK_DMAC ;
    PM->APBBMASK.reg |= PM_APBBMASK_DMAC ;
    NVIC_EnableIRQ( DMAC_IRQn ) ;
    
    DMAC->BASEADDR.reg = (uint32_t)descriptor_section;
    DMAC->WRBADDR.reg = (uint32_t)wrb;
    DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
}


Sercom *sercom = (Sercom   *)SERCOM4;  // SPI SERCOM

void spi_xfr(void *txdata, void *rxdata,  size_t n) {
    uint32_t temp_CHCTRLB_reg;
    
    // set up transmit channel
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
    DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnltx));
    temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
    DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_TX) | DMAC_CHCTRLB_TRIGACT_BEAT;
    DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
    DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
    descriptor.descaddr = 0;
    descriptor.dstaddr = (uint32_t) &sercom->SPI.DATA.reg;
    descriptor.btcnt =  n;
    descriptor.srcaddr = (uint32_t)txdata;
    descriptor.btctrl =  DMAC_BTCTRL_VALID;
    if (xtype != DoRX) {
        descriptor.srcaddr += n;
        descriptor.btctrl |= DMAC_BTCTRL_SRCINC;
    }
    memcpy(&descriptor_section[chnltx],&descriptor, sizeof(dmacdescriptor));
    
    // rx channel    enable interrupts
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
    DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnlrx));
    temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
    DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_RX) | DMAC_CHCTRLB_TRIGACT_BEAT;
    DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
    DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
    dmadone = 0;
    descriptor.descaddr = 0;
    descriptor.srcaddr = (uint32_t) &sercom->SPI.DATA.reg;
    descriptor.btcnt =  n;
    descriptor.dstaddr = (uint32_t)rxdata;
    descriptor.btctrl =  DMAC_BTCTRL_VALID;
    if (xtype != DoTX) {
        descriptor.dstaddr += n;
        descriptor.btctrl |= DMAC_BTCTRL_DSTINC;
    }
    memcpy(&descriptor_section[chnlrx],&descriptor, sizeof(dmacdescriptor));
    
    // start both channels  ? order matter ?
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
    DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
    
    while(!dmadone);  // await DMA done isr
    
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);   //disable DMA to allow lib SPI
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
}

void spi_write(void *data,  size_t n) {
    xtype = DoTX;
    spi_xfr(data,rxsink,n);
}
void spi_read(void *data,  size_t n) {
    xtype = DoRX;
    spi_xfr(txsrc,data,n);
}
void spi_transfer(void *txdata, void *rxdata,  size_t n) {
    xtype = DoTXRX;
    spi_xfr(txdata,rxdata,n);
}


#endif /* defined(____dma__) */

dma.h (4.22 KB)

Here is the code in dma.h :

Variable declarations belong in header files. Function implementations do not.

This is a standard to only declare function and variable in header (.h) and define function and variable in .cpp, but you should be able in c++ to define function in header as well, no?

but you should be able in c++ to define function in header as well, no?

You tried that. It didn't work, did it? Does that answer your question?

AloyseTech: This is a standard to only declare function and variable in header (.h) and define function and variable in .cpp, but you should be able in c++ to define function in header as well, no?

Only inline or template functions can be defined in headers safely.

.h files are copied directly into the places you include them. .cpp files are compiled as separate entities, or 'translation units'.

So if you include it in more than one .cpp file (sketch is treated as a .cpp) then your code is effectively compiled twice, hence the errors.

Thanks pYro_65 for this informative answer. I'll try to split the source in two files asap.

AloyseTech: Thanks pYro_65 for this informative answer. I'll try to split the source in two files asap.

You don't need to carve out the header and implementation files to work on your Class. Some find it a lot easier to work out the Class and its member functions integral to your program (i.e. your *.ino file).

Then, once you get the methods working, slice and dice.

just a thought...

@BulldogLowell
My initial header file works very well when included only one time in the needed file, in my case, the arduini program. I just need to “convert” it so it’s usable everywhere I need it : in the main program as well as libraries.

I did a class for my code, but the Static variable seems to cause issues.

I get this error :

../SPIdma.cpp:42: undefined reference to `DMA::txsrc'

Here is the header and associated cpp file I created :

header :

//
//  SPIdma.h
//  
//
//  Created by Théo Meyer on 16/11/2015.
//
//

#ifndef _SPIdma_h
#define _SPIdma_h

#include <SPI.h>
#define SPISPEED 12000000

#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,HEX)


class DMA{
public:
    // DMA   12 channels
    typedef struct {
        uint16_t btctrl;
        uint16_t btcnt;
        uint32_t srcaddr;
        uint32_t dstaddr;
        uint32_t descaddr;
    } dmacdescriptor ;

    volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));
    dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));
    dmacdescriptor descriptor __attribute__ ((aligned (16)));

    static const uint32_t chnltx=0, chnlrx=1; // DMA channels
    enum XfrType { DoTX, DoRX, DoTXRX};
    static XfrType xtype;
    static uint8_t rxsink[1], txsrc[1];
    volatile uint32_t dmadone;
    Sercom *sercom = (Sercom   *)SERCOM4;  // SPI SERCOM

    //DMA();
    void DMAC_Handler();
    void init();
    void spi_xfr(void *txdata, void *rxdata,  size_t n);
    void spi_write(void *data,  size_t n);
    void spi_read(void *data,  size_t n);
    void spi_transfer(void *txdata, void *rxdata,  size_t n);

};

#endif

cpp:

//
//  SPIdma.cpp
//  
//
//  Created by Théo Meyer on 16/11/2015.
//
//

#include "SPIdma.h"
#include <SPI.h>


// DMA   12 channels
//DMA::DMA(){}

void DMA::DMAC_Handler() {
    // interrupts DMAC_CHINTENCLR_TERR DMAC_CHINTENCLR_TCMPL DMAC_CHINTENCLR_SUSP
    uint8_t active_channel;
    
    // disable irqs ?
    __disable_irq();
    active_channel =  DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // get channel number
    DMAC->CHID.reg = DMAC_CHID_ID(active_channel);
    dmadone = DMAC->CHINTFLAG.reg;
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; // clear
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
    DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;
    __enable_irq();
}

void DMA::init() {
    DMA::txsrc[1]= {0xff};

    // probably on by default
    PM->AHBMASK.reg |= PM_AHBMASK_DMAC ;
    PM->APBBMASK.reg |= PM_APBBMASK_DMAC ;
    NVIC_EnableIRQ( DMAC_IRQn ) ;
    
    DMAC->BASEADDR.reg = (uint32_t)descriptor_section;
    DMAC->WRBADDR.reg = (uint32_t)wrb;
    DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
}


void DMA::spi_xfr(void *txdata, void *rxdata,  size_t n) {
    uint32_t temp_CHCTRLB_reg;
    
    // set up transmit channel
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
    DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnltx));
    temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
    DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_TX) | DMAC_CHCTRLB_TRIGACT_BEAT;
    DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
    DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
    descriptor.descaddr = 0;
    descriptor.dstaddr = (uint32_t) &sercom->SPI.DATA.reg;
    descriptor.btcnt =  n;
    descriptor.srcaddr = (uint32_t)txdata;
    descriptor.btctrl =  DMAC_BTCTRL_VALID;
    if (xtype != DoRX) {
        descriptor.srcaddr += n;
        descriptor.btctrl |= DMAC_BTCTRL_SRCINC;
    }
    memcpy(&descriptor_section[chnltx],&descriptor, sizeof(dmacdescriptor));
    
    // rx channel    enable interrupts
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
    DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << chnlrx));
    temp_CHCTRLB_reg = DMAC_CHCTRLB_LVL(0) |
    DMAC_CHCTRLB_TRIGSRC(SERCOM4_DMAC_ID_RX) | DMAC_CHCTRLB_TRIGACT_BEAT;
    DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
    DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // enable all 3 interrupts
    dmadone = 0;
    descriptor.descaddr = 0;
    descriptor.srcaddr = (uint32_t) &sercom->SPI.DATA.reg;
    descriptor.btcnt =  n;
    descriptor.dstaddr = (uint32_t)rxdata;
    descriptor.btctrl =  DMAC_BTCTRL_VALID;
    if (xtype != DoTX) {
        descriptor.dstaddr += n;
        descriptor.btctrl |= DMAC_BTCTRL_DSTINC;
    }
    memcpy(&descriptor_section[chnlrx],&descriptor, sizeof(dmacdescriptor));
    
    // start both channels  ? order matter ?
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);
    DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
    
    while(!dmadone);  // await DMA done isr
    
    DMAC->CHID.reg = DMAC_CHID_ID(chnltx);   //disable DMA to allow lib SPI
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
    DMAC->CHID.reg = DMAC_CHID_ID(chnlrx);
    DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
}

void DMA::spi_write(void *data,  size_t n) {
    xtype = DoTX;
    spi_xfr(data,rxsink,n);
}
void DMA::spi_read(void *data,  size_t n) {
    xtype = DoRX;
    spi_xfr(txsrc,data,n);
}
void DMA::spi_transfer(void *txdata, void *rxdata,  size_t n) {
    xtype = DoTXRX;
    spi_xfr(txdata,rxdata,n);
}
    DMA::txsrc[1]= {0xff};

An array of length 1 does not have an element at [1].

Line 42 appears to be a blank line. There is no reference to txsrc anywhere near it.

I changed the DMA::txsrc[0], but it still doesn’t compile, with the same error. line 42 is the end of the DMA::init() definition in SPIdma.cpp

Here is the test code :

#include <SPI.h>
#include <SPIdma.h>

DMA dma;

void setup() {
  // put your setup code here, to run once:
  dma.init();

}

void loop() {
  // put your main code here, to run repeatedly:

}

If I remove the static keyword before uint8_t rxsink[1], txsrc[1]; it compiles... But it then my code does not work...

Is the plan to change the size of the arrays at some point? If not, one element arrays are no advantage over scalar variables.

Why do you need static data and methods?

I’m not the author of the code, I don’t understand everything. But I think that those data are static because it uses some interrupt with one unique peripheral of the SAMD21G18A, the DMA. Those data should be unique as well then. I’m not an expert so I could be wrong with no surprise…
It comes from the work of Mantoui from the Arduino Zero forum. I only try to make it easier to use.
I’ve tried to keep the DMAC_handler function out of the DMA class, because it is the interrupt handler and should be available at any time. This way it works, but only if one unique instance of DMA is declared. So I still have my issue : I can’t use it in different library of my code…