I'm using Atmel Studio 7 with ASF4. I'd like to have two different projects in the solution, one as the Atmel Start project and the other as the Main project which calls the Start. In Start I have a DMA controller configured with dma_memory.h/c, hpl_dma.h, and hpl_dmac.c present. I have files dmac_intf.h/c in Main which make calls to dma_memory.c. A good example is registering callbacks:
dmac_intfc.c: (In Main)
int32_t dmac_init(void) { dma_memory_init(); // Use dma_memory.c to initialize the DMA Controller. dma_memory_register_callback(DMA_MEMORY_SUSPEND_CB, dma_suspend); dma_memory_register_callback(DMA_MEMORY_COMPLETE_CB, dma_transfer_complete); dma_memory_register_callback(DMA_MEMORY_ERROR_CB, dma_transfer_error); return ERR_NONE; }
dma_memory.h: (In Start)
/** * \brief memory with dma callback type */ typedef void (*dma_memory_cb_t)(struct _dma_resource*); /** * \brief Memory with DMA callback types */ enum dma_memory_callback_type { DMA_MEMORY_SUSPEND_CB, DMA_MEMORY_COMPLETE_CB, DMA_MEMORY_ERROR_CB }; /** * \brief Memory with DMA callbacks */ struct dma_memory_callbacks { dma_memory_cb_t suspend; dma_memory_cb_t transfer_complete; dma_memory_cb_t transfer_error; }; /** * \brief Initialize Memory with DMA * * \return Initialization status. */ int32_t dma_memory_init(void); /** * \brief Register Memory with DMA callback * * \param[in] type Callback type * \param[in] cb A callback function, passing NULL de-registers callback * * \return The status of callback assignment. * \retval ERR_INVALID_ARG Passed parameters were invalid * \retval ERR_NONE A callback is registered successfully */ int32_t dma_memory_register_callback(const enum dma_memory_callback_type type, dma_memory_cb_t cb);
dma_memory.c: (In Start)
/** * \brief Initialize DMA */ int32_t dma_memory_init(void) { _dma_get_channel_resource(&descr.resource, CONF_DMA_MEMORY_CHANNEL); descr.resource->dma_cb.suspend = dma_suspend; descr.resource->dma_cb.transfer_complete = dma_transfer_done; descr.resource->dma_cb.transfer_error = dma_memory_error; return ERR_NONE; } /** * \brief Register DMA callback */ int32_t dma_memory_register_callback(const enum dma_memory_callback_type type, dma_memory_cb_t cb) { switch (type) { case DMA_MEMORY_SUSPEND_CB: descr.memory_cb.suspend = cb; break; case DMA_MEMORY_COMPLETE_CB: descr.memory_cb.transfer_complete = cb; break; case DMA_MEMORY_ERROR_CB: descr.memory_cb.transfer_error = cb; break; default: return ERR_INVALID_ARG; } _dma_set_irq_state(CONF_DMA_MEMORY_CHANNEL, (enum _dma_callback_type)type, (cb != NULL)); return ERR_NONE; }
hpl_dma.h (In Start):
struct _dma_resource; /** * \brief DMA callback types */ enum _dma_callback_type { DMA_SUSPEND_CB, DMA_TRANSFER_COMPLETE_CB, DMA_TRANSFER_ERROR_CB }; /** * \brief DMA interrupt callbacks */ struct _dma_callbacks { void (*suspend)(struct _dma_resource *resource); void (*transfer_complete)(struct _dma_resource *resource); void (*transfer_error)(struct _dma_resource *resource); }; /** * \brief DMA resource structure */ struct _dma_resource { struct _dma_callbacks dma_cb; void * back; }; /** * \brief Retrieves DMA resource structure * * \param[out] resource The resource to be retrieved * \param[in] channel DMA channel to retrieve structure for * * \return status of operation */ int32_t _dma_get_channel_resource(struct _dma_resource **resource, const uint8_t channel); /** * \brief Enable/disable DMA interrupt * * \param[in] channel DMA channel to enable/disable interrupt for * \param[in] type The type of interrupt to disable/enable if applicable * \param[in] state Enable or disable */ void _dma_set_irq_state(const uint8_t channel, const enum _dma_callback_type type, const bool state);
hpl_dmac.c: (In Start)
/** * \brief Enable/disable DMA interrupt */ void _dma_set_irq_state(const uint8_t channel, const enum _dma_callback_type type, const bool state) { hri_dmac_write_CHID_reg(DMAC, channel); if (DMA_SUSPEND_CB == type) { hri_dmac_write_CHINTEN_SUSP_bit(DMAC, state); } else if (DMA_TRANSFER_COMPLETE_CB == type) { hri_dmac_write_CHINTEN_TCMPL_bit(DMAC, state); } else if (DMA_TRANSFER_ERROR_CB == type) { hri_dmac_write_CHINTEN_TERR_bit(DMAC, state); } } int32_t _dma_get_channel_resource(struct _dma_resource **resource, const uint8_t channel) { *resource = &_resources[channel]; return ERR_NONE; }
I hope that's enough code to go on. I didn't want to paste the whole monster here. So the code compiles fine but when it tries to link, basically any calls to hpl_dmac.c (such as _dma_set_irq_state and _dma_get_channel_resource) are 404'ed with "undefined reference to <function>".
In the Solution Explorer I right-clicked on Libraries > Add Library. I clicked "Project Libraries" and added the Start project. I can see the Start library under Libraries.
I tried some experiments with it. First I commented out all of the calls to dma_memory (In Start) from dmac_intfc (In Main). Links fine with no errors. That makes sense - no calls means there's nothing to link. Next, from main.c (In Main) there's a call to atmel_start_init() from Start. If I remove the Start library and compile I get an "undefined reference" error. Makes sense - without the Start library, Main won't be able to link to Start. So this tells me that 1) the calls from dmac_intfc (in Main) are offending and 2) the Start library and path are valid. My next thought was that perhaps hpl_dmac.c either wasn't being compiled or wasn't making it into the Start library. But looking at the Start compile:
...
Building file: ../hpl/dmac/hpl_dmac.c
Invoking: ARM/GNU C Compiler : 6.3.1
"C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\arm\arm-gnu-toolchain\bin\arm-none-eabi-gcc.exe" -x c -mthumb -D__SAMD21G18A__ -DDEBUG -I"C:\Program Files (x86)\Atmel\Studio\7.0\Packs\ARM\CMSIS\5.4.0\CMSIS\Core\Include" -I"C:\Program Files (x86)\Atmel\Studio\7.0\Packs\Atmel\SAMD21_DFP\1.3.395\samd21a\include" -I"../Config" -I".." -I"../examples" -I"../hal/include" -I"../hal/utils/include" -I"../hpl/core" -I"../hpl/dac" -I"../hpl/dmac" -I"../hpl/gclk" -I"../hpl/nvmctrl" -I"../hpl/pm" -I"../hpl/port" -I"../hpl/sercom" -I"../hpl/sysctrl" -I"../hpl/tc" -I"../hpl/usb" -I"../hri" -I"../usb" -I"../usb/class/cdc" -I"../usb/class/cdc/device" -I"../usb/device" -I"../dma_m2m" -O0 -ffunction-sections -mlong-calls -g3 -Wall -mcpu=cortex-m0plus -c -std=gnu99 -MD -MP -MF "hpl/dmac/hpl_dmac.d" -MT"hpl/dmac/hpl_dmac.d" -MT"hpl/dmac/hpl_dmac.o" -o "hpl/dmac/hpl_dmac.o" "../hpl/dmac/hpl_dmac.c"
Finished building: ../hpl/dmac/hpl_dmac.c
...
... and the linker:
Building target: libStart.a
Invoking: ARM/GNU Archiver : 6.3.1
"C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\arm\arm-gnu-toolchain\bin\arm-none-eabi-ar.exe" -r -o libStart.a atmel_start.o dma_m2m/dma_memory.o dma_m2m_main.o driver_init.o examples/driver_examples.o hal/src/hal_atomic.o hal/src/hal_dac_sync.o hal/src/hal_delay.o hal/src/hal_flash.o hal/src/hal_gpio.o hal/src/hal_i2c_m_sync.o hal/src/hal_i2c_s_async.o hal/src/hal_init.o hal/src/hal_io.o hal/src/hal_sleep.o hal/src/hal_usart_sync.o hal/src/hal_usb_device.o hal/utils/src/utils_assert.o hal/utils/src/utils_event.o hal/utils/src/utils_list.o hal/utils/src/utils_ringbuffer.o hal/utils/src/utils_syscalls.o hpl/core/hpl_core_m0plus_base.o hpl/core/hpl_init.o hpl/dac/hpl_dac.o hpl/dmac/hpl_dmac.o hpl/gclk/hpl_gclk.o hpl/nvmctrl/hpl_nvmctrl.o hpl/pm/hpl_pm.o hpl/sercom/hpl_sercom.o hpl/sysctrl/hpl_sysctrl.o hpl/tc/tc_lite.o hpl/usb/hpl_usb.o usb/class/cdc/device/cdcdf_acm.o usb/device/usbdc.o usb/usb_protocol.o usb_start.o
C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\arm\arm-gnu-toolchain\bin\arm-none-eabi-ar.exe: creating libStart.a
Finished building target: libStart.a
Sure enough, hpl/dmac/hpl_dmac.o is present when the Start library is built. Weird. So why do you think I might be getting the undefined reference errors? What am I missing?