Class definition work in header file, not in cpp

Hi :slight_smile:

I'm working on some kind of library for the Arduino Zero, and I need global variables. When I put all my class code in the header file, the code compile fine, but when I want to move the definition in a cpp file, I have lot's of error of multiple definition of global variables. I'm very lost with this issue and any help is really welcome.

My code is on GitHub here.

For those who prefer to read the code here :

Arduino "sketch" :

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


DMA_SPI DMASPI(SERCOM2, 0, 1, SERCOM2_DMAC_ID_TX, SERCOM2_DMAC_ID_RX);

uint8_t buffer[256] = {0};

void setup()
{
  while (!SerialUSB);

  SPI.begin();
  SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0));

  //DMA setup for SPI transaction, arduino SPI class init must have been called already
  DMASPI.init();

  DMASPI.registerTXCallbacks(callback);

  for (int i = 0; i < 128; i++)
  {
    for (int i = 0; i < 256; i++)
      buffer[i] = random(0xFF);
    while (!DMASPI.transferDone());
    DMASPI.write(buffer, 256);
  }
  //Allow standard SPI use again
  DMASPI.disable();


}

void loop()
{

}

static bool led_state = true;
void callback()
{
  //blink led during transfer to prove callbacks are working
  digitalWrite(LED_BUILTIN, led_state);
  led_state = !led_state;
  delayMicroseconds(50000);
}

DMA Helper global stuff :

#ifndef _DMA_HELPER_
#define _DMA_HELPER_

#include "Arduino.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
  uint16_t btctrl;    //Block Transfer Control
  uint16_t btcnt;     //Block Transfer Count
  uint32_t srcaddr;   //Source Address
  uint32_t dstaddr;   //Destination Address
  uint32_t descaddr;  //Next Descriptor Address
} dmacdescriptor;

//Write Back Descriptor (x12)
volatile dmacdescriptor descriptor_wrb[12] __attribute__((aligned(16)));

//DMA Channels Descriptor (x12)
dmacdescriptor descriptor_section[12] __attribute__((aligned(16)));

//DMA Channel Interrupt Result (x12)
volatile uint32_t dmaChannelDone[12];

//DMA Channel Callback (x12)
void(*dma_callback[12])(void) = { NULL };

//DMAC already initialized
uint32_t dmac_initialized = 0;

//DMAC_Handler : Interrupt Service Routine :
//override DMAC_Handler (attribute is weak in Reset_Handler.c)
void DMAC_Handler() {
  uint8_t active_channel;

  //Disable global interrupts
  __disable_irq();

  //Get the triggering channel number
  active_channel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk;
  DMAC->CHID.reg = DMAC_CHID_ID(active_channel);

  //Get Channel Result Flags
  //DMAC_CHINTENCLR_TERR  = 0x01    =>Transfer Error
  //DMAC_CHINTENCLR_TCMPL = 0x02    =>Transfer Complete
  //DMAC_CHINTENCLR_SUSP  = 0x04    =>Transfer Suspended
  dmaChannelDone[active_channel] = DMAC->CHINTFLAG.reg;

  //Execute calback if registered
  if (dma_callback[active_channel] != NULL)
    dma_callback[active_channel]();

  //Clear interrupt flags
  DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TCMPL; // clear
  DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_TERR;
  DMAC->CHINTFLAG.reg = DMAC_CHINTENCLR_SUSP;

  //Enable global interrupts
  __enable_irq();
}

void DMAC_Init()
{
  if (!dmac_initialized)
  {
    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)descriptor_wrb;
    DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);
  }
}

void DMAC_End()
{
  if (dmac_initialized)
  {
    DMAC->CTRL.bit.DMAENABLE = 0;        // disable DMA controller
    DMAC->CTRL.bit.SWRST = 1;           // reset all DMA registers
    while (DMAC->CTRL.bit.SWRST) {};    // wait for reset to complete
  }
}

#ifdef __cplusplus
}
#endif

#endif //_DMA_HELPER_

My DMA_SPI class using the global stuff :

#ifndef _DMA_SPI_
#define _DMA_SPI_

#include "Arduino.h"
#include "project_DMA_Helper.h"


class DMA_SPI
{
  public:
    DMA_SPI(Sercom *s, uint32_t txc, uint32_t rxc, uint8_t txtrig, uint8_t rxtrig)
    {
      sercom = s;
      chnltx = txc;
      chnlrx = rxc;
      txTrigger = txtrig;
      rxTrigger = rxtrig;
    }

    Sercom *sercom;
    uint32_t chnltx, chnlrx; // DMA channels
    uint8_t txTrigger, rxTrigger;
    dmacdescriptor descriptor __attribute__((aligned(16)));

    void registerCallbacks(void(*tx)(), void(*rx)())
    {
      dma_callback[chnltx] = tx;
      dma_callback[chnlrx] = rx;
    }
    void registerRXCallbacks(void(*rx)())
    {
      dma_callback[chnlrx] = rx;
    }
    void registerTXCallbacks(void(*tx)())
    {
      dma_callback[chnltx] = tx;
    }

    void init()
    {
      DMAC_Init();
      txsrc[0] = 0xff;
      xfr(txsrc, rxsink, 1);
      while (!dmaChannelDone[chnltx] || !dmaChannelDone[chnlrx]);
      disable();
    }


    void transfer(void *txdata, void *rxdata, size_t n)
    {
      xtype = DoTXRX;
      xfr(txdata, rxdata, n);
    }
    void read(void *data, size_t n)
    {
      xtype = DoRX;
      xfr(txsrc, data, n);
    }
    void write(void *data, size_t n)
    {
      xtype = DoTX;
      xfr(data, rxsink, n);
    }

    bool transferDone()
    {
      return (dmaChannelDone[chnltx] || dmaChannelDone[chnlrx]);
    }

    void disable()
    {
      __disable_irq();
      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;
      __enable_irq();
    }

  private:
    enum XfrType { DoTX, DoRX, DoTXRX };
    XfrType xtype;
    uint8_t rxsink[1], txsrc[1];
    void(*callbackTX)(void);
    void(*callbackRX)(void);

    void xfr(void *txdata, void *rxdata, size_t n)
    {
      __disable_irq();

      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(txTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT;
      DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
      DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // enable all 3 interrupts
      dmaChannelDone[chnltx] = 0;
      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;
      }
      //copy all the descriptor data at once
      memcpy(&descriptor_section[chnltx], &descriptor, sizeof(dmacdescriptor));

      // rx channel
      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(rxTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT;
      DMAC->CHCTRLB.reg = temp_CHCTRLB_reg;
      DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // enable all 3 interrupts
      dmaChannelDone[chnlrx] = 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
      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;

      __enable_irq();
    }
};

#endif //_DMA_SPI_

Now if I create a DMA_SPI.cpp file with only this :

#include "project_DMA_SPI.h"

I get these error :

/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `DMAC_Handler'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_Init':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:66: multiple definition of `DMAC_Init'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:66: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:80: multiple definition of `DMAC_End'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:80: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `dmac_initialized'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/Users/TheoMeyer/Documents/Arduino/SAMD_DMA_Helper/SAMD_DMA_Helper.ino:41: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `dma_callback'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/Users/TheoMeyer/Documents/Arduino/SAMD_DMA_Helper/SAMD_DMA_Helper.ino:41: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `dmaChannelDone'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/Users/TheoMeyer/Documents/Arduino/SAMD_DMA_Helper/SAMD_DMA_Helper.ino:41: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `descriptor_section'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/Users/TheoMeyer/Documents/Arduino/SAMD_DMA_Helper/SAMD_DMA_Helper.ino:41: first defined here
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_SPI.cpp.o: In function `DMAC_End':
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/project_DMA_Helper.h:35: multiple definition of `descriptor_wrb'
/var/folders/v6/tj3__g0x16gfgz5vmcx385s80000gn/T/arduino_build_729172/sketch/SAMD_DMA_Helper.ino.cpp.o:/Users/TheoMeyer/Documents/Arduino/SAMD_DMA_Helper/SAMD_DMA_Helper.ino:41: first defined here
/Users/TheoMeyer/Library/Arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld: warning: changing start of section .bss by 8 bytes
/Users/TheoMeyer/Library/Arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld: warning: changing start of section .bss by 8 bytes
/Users/TheoMeyer/Library/Arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld: warning: changing start of section .bss by 8 bytes
collect2: error: ld returned 1 exit status

My DMA_SPI class using the global stuff :

What file is that code in?

When you compile a cpp file that includes project_DMA_SPI.h and the ino file that includes project_DMA_SPI.h, you get a copy of the global variables in each object module. The linker doesn't like that.

You should only include project_DMA_SPI.h in one compilation unit. The other compilation units need extern statements, to tell the compiler and the linker that the variables are declared and initialized somewhere else.

DMA_SPI class is defined in project_DMA_SPI.h

The global variables are in project_DMA_Helper.h, which is included in project_DMA_SPI.h

I thought that the ifdef / endif avoid whatever is between to be compiled more than once...

#ifndef _DMA_HELPER_
#define _DMA_HELPER_

/* global type, variables and functions here*/

#endif

If I have a class header and cpp, I have to include the header in the cpp... I also thought about the extern or static stuff, but I'm not really familiar with this.

If I have a class header and cpp, I have to include the header in the cpp... I also thought about the extern or static stuff, but I'm not really familiar with this.

You have to include the <file.h> in both the .cpp and the Arduino main tab. If the class is in the sketch folder, it will read

#include "./file.h"

ArduinoIDE will not automatically prototype your functions ... Do it yourself.

Ray

I thought that the ifdef / endif avoid whatever is between to be compiled more than once...

No. What it does is allow you to include the file several times in one compilation unit, without duplicate symbols resulting.

It has no affect when there are multiple compilation units, such as your sketch and your library.