/*************************************************************************** * Copyright (C) 2023 by Federico Amedeo Izzo IU2NUO, * * Niccolò Izzo IU2KIN * * Frederik Saraci IU2NRO * * Silvano Seva IU2KWO * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include #include #include #include #include #include #include #include "stm32_dac.h" struct ChannelState { struct streamCtx *ctx; // Current stream context uint32_t idleLevel; // Output idle level StreamHandler stream; // DMA stream handler }; struct DacChannel { volatile uint32_t *dacReg; // DAC data register Timer tim; // TIM peripheral for DAC trigger }; using Dma1_Stream5 = DmaStream< DMA1_BASE, 5, 7, 3 >; // DMA 1, Stream 5, channel 7, very high priority using Dma1_Stream6 = DmaStream< DMA1_BASE, 6, 7, 3 >; // DMA 1, Stream 6, channel 7, very high priority static constexpr DacChannel channels[] = { {&(DAC->DHR12R1), Timer(TIM6_BASE)}, {&(DAC->DHR12R2), Timer(TIM7_BASE)}, }; #pragma GCC diagnostic ignored "-Wpedantic" struct ChannelState chState[] = { { .ctx = NULL, .idleLevel = 0, .stream = Dma1_Stream5::init(10, DataSize::_16BIT, 1) }, { .ctx = NULL, .idleLevel = 0, .stream = Dma1_Stream6::init(10, DataSize::_16BIT, 1) } }; #pragma GCC diagnostic pop /** * \internal * Stop an ongoing transfer, deactivating timers and DMA stream. */ static void stopTransfer(const uint8_t chNum) { channels[chNum].tim.stop(); *channels[chNum].dacReg = chState[chNum].idleLevel; chState[chNum].ctx->running = 0; } /** * \internal * Actual implementation of DMA interrupt handler. */ void __attribute__((used)) DMA_Handler(uint32_t chNum) { if(chNum == 0) Dma1_Stream5::IRQhandleInterrupt(&chState[chNum].stream); else Dma1_Stream6::IRQhandleInterrupt(&chState[chNum].stream); } // DMA 1, Stream 5: data transfer for RTX sink void __attribute__((used)) DMA1_Stream5_IRQHandler() { saveContext(); asm volatile("mov r0, #0"); asm volatile("bl _Z11DMA_Handlerm"); restoreContext(); } // DMA 1, Stream 6: data transfer for speaker sink void __attribute__((used)) DMA1_Stream6_IRQHandler() { saveContext(); asm volatile("mov r0, #1"); asm volatile("bl _Z11DMA_Handlerm"); restoreContext(); } void stm32dac_init() { // Configure GPIOs gpio_setMode(GPIOA, 4, INPUT_ANALOG); gpio_setMode(GPIOA, 5, INPUT_ANALOG); // Enable peripherals RCC->APB1ENR |= RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_TIM7EN; __DSB(); // DAC common configuration DAC->CR = DAC_CR_DMAEN2 // Enable DMA | DAC_CR_TSEL2_1 // TIM7 as trigger source for CH2 | DAC_CR_TEN2 // Enable trigger input | DAC_CR_EN2 // Enable CH2 | DAC_CR_DMAEN1 // Enable DMA | 0x00 // TIM6 as trigger source for CH1 | DAC_CR_TEN1 // Enable trigger input | DAC_CR_EN1; // Enable CH1 // Register end-of-transfer callbacks chState[0].stream.setEndTransferCallback(std::bind(stopTransfer, 0)); chState[1].stream.setEndTransferCallback(std::bind(stopTransfer, 1)); } void stm32dac_terminate() { // Terminate streams before shutting of the peripherals for(int i = 0; i < 2; i++) { if(chState[i].ctx != NULL) { if(chState[i].ctx->running != 0) chState[i].stream.halt(); } } RCC->APB1ENR &= ~(RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_TIM7EN); __DSB(); } static int stm32dac_start(const uint8_t instance, const void *config, struct streamCtx *ctx) { if((ctx == NULL) || (ctx->running != 0)) return -EINVAL; if(chState[instance].stream.running()) return -EBUSY; __disable_irq(); ctx->running = 1; __enable_irq(); ctx->priv = &chState[instance]; chState[instance].ctx = ctx; chState[instance].idleLevel = reinterpret_cast< uint32_t >(config); /* * Convert buffer elements from int16_t to unsigned 12 bit values as required * by the DAC. Processing can be done in-place because the API mandates that * the function caller does not modify the buffer content once this function * has been called. */ S16toU12(ctx->buffer, ctx->bufSize); bool circ = false; if(ctx->bufMode == BUF_CIRC_DOUBLE) circ = true; chState[instance].stream.start(channels[instance].dacReg, ctx->buffer, ctx->bufSize, circ); // Configure DAC trigger channels[instance].tim.setUpdateFrequency(ctx->sampleRate); channels[instance].tim.start(); return 0; } static int stm32dac_idleBuf(struct streamCtx *ctx, stream_sample_t **buf) { ChannelState *state = reinterpret_cast< ChannelState * >(ctx->priv); *buf = reinterpret_cast< stream_sample_t *>(state->stream.idleBuf()); return ctx->bufSize/2; } static int stm32dac_sync(struct streamCtx *ctx, uint8_t dirty) { ChannelState *state = reinterpret_cast< ChannelState * >(ctx->priv); if((ctx->bufMode == BUF_CIRC_DOUBLE) && (dirty != 0)) { void *ptr = state->stream.idleBuf(); S16toU12(reinterpret_cast< int16_t *>(ptr), ctx->bufSize/2); } bool ok = state->stream.sync(); if(ok) return 0; return -1; } static void stm32dac_stop(struct streamCtx *ctx) { if(ctx->running == 0) return; reinterpret_cast< ChannelState * >(ctx->priv)->stream.stop(); } static void stm32dac_halt(struct streamCtx *ctx) { if(ctx->running == 0) return; reinterpret_cast< ChannelState * >(ctx->priv)->stream.halt(); } #pragma GCC diagnostic ignored "-Wpedantic" const struct audioDriver stm32_dac_audio_driver = { .start = stm32dac_start, .data = stm32dac_idleBuf, .sync = stm32dac_sync, .stop = stm32dac_stop, .terminate = stm32dac_halt }; #pragma GCC diagnostic pop