1
0
Fork 0
mirror of https://github.com/iNavFlight/inav.git synced 2025-07-23 00:05:28 +03:00
inav/src/main/drivers/timer_impl_stdperiph_at32.c
2023-07-27 00:41:52 +02:00

409 lines
14 KiB
C

/*
* This file is part of INAV.
*
* INAV 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.
*
* INAV 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 INAV. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
#include "platform.h"
#include "build/atomic.h"
#include "build/debug.h"
#include "common/utils.h"
#include "drivers/io.h"
#include "drivers/rcc.h"
#include "drivers/time.h"
#include "drivers/nvic.h"
#include "drivers/dma.h"
#include "drivers/timer.h"
#include "drivers/timer_impl.h"
const uint16_t lookupDMASourceTable[4] = { TMR_C1_DMA_REQUEST, TMR_C2_DMA_REQUEST, TMR_C3_DMA_REQUEST, TMR_C4_DMA_REQUEST };
const uint8_t lookupTIMChannelTable[4] = { TMR_C1_FLAG, TMR_C2_FLAG, TMR_C3_FLAG, TMR_C4_FLAG };
void impl_timerInitContext(timHardwareContext_t * timCtx)
{
(void)timCtx; // NoOp
}
// Configure the interrupt priority
void impl_timerNVICConfigure(TCH_t * tch, int irqPriority)
{
if (tch->timCtx->timDef->irq) {
nvic_irq_enable(tch->timCtx->timDef->irq, irqPriority, 0);
}
if (tch->timCtx->timDef->secondIrq) {
nvic_irq_enable(tch->timCtx->timDef->secondIrq, irqPriority, 0);
}
}
void impl_timerConfigBase(TCH_t * tch, uint16_t period, uint32_t hz)
{
tmr_type * tim = tch->timCtx->timDef->tim;
tmr_base_init(tim, (period - 1) & 0xffff, lrintf((float)timerGetBaseClock(tch) / hz + 0.01f) - 1);
tmr_clock_source_div_set(tim, TMR_CLOCK_DIV1);
tmr_cnt_dir_set(tim, TMR_COUNT_UP); //Count up (default)
}
void impl_enableTimer(TCH_t * tch)
{
tmr_counter_enable(tch->timHw->tim, TRUE);
}
void impl_timerPWMStart(TCH_t * tch)
{
tmr_output_enable(tch->timHw->tim,TRUE);
}
void impl_timerEnableIT(TCH_t * tch, uint32_t interrupt)
{
tmr_interrupt_enable(tch->timHw->tim, interrupt, TRUE);
}
void impl_timerDisableIT(TCH_t * tch, uint32_t interrupt)
{
tmr_interrupt_enable(tch->timHw->tim, interrupt, FALSE);
}
void impl_timerClearFlag(TCH_t * tch, uint32_t flag)
{
tmr_flag_clear(tch->timHw->tim, flag);
}
// calculate input filter constant
static unsigned getFilter(unsigned ticks)
{
static const unsigned ftab[16] = {
1*1, // fDTS !
1*2, 1*4, 1*8, // fCK_INT
2*6, 2*8, // fDTS/2
4*6, 4*8,
8*6, 8*8,
16*5, 16*6, 16*8,
32*5, 32*6, 32*8
};
for (unsigned i = 1; i < ARRAYLEN(ftab); i++) {
if (ftab[i] > ticks) {
return i - 1;
}
}
return 0x0f;
}
void impl_timerChConfigIC(TCH_t * tch, bool polarityRising, unsigned inputFilterTicks)
{
tmr_input_config_type tmr_input_config_struct;
tmr_input_default_para_init(&tmr_input_config_struct);
tmr_input_config_struct.input_channel_select = lookupTIMChannelTable[tch->timHw->channelIndex];
tmr_input_config_struct.input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT;
tmr_input_config_struct.input_polarity_select = polarityRising ? TMR_INPUT_RISING_EDGE:TMR_INPUT_FALLING_EDGE;
tmr_input_config_struct.input_filter_value = getFilter(inputFilterTicks);
tmr_input_channel_init(tch->timHw->tim,&tmr_input_config_struct,TMR_CHANNEL_INPUT_DIV_1);
}
void impl_timerCaptureCompareHandler(tmr_type *tim, timHardwareContext_t *timerCtx)
{
unsigned tim_status = tim->ists & tim->iden;
while (tim_status) {
// flags will be cleared by reading CCR in dual capture, make sure we call handler correctly
// currrent order is highest bit first. Code should not rely on specific order (it will introduce race conditions anyway)
unsigned bit = __builtin_clz(tim_status);
unsigned mask = ~(0x80000000 >> bit);
tim->ists = mask;
tim_status &= mask;
if (timerCtx) {
switch (bit) {
case __builtin_clz(TMR_OVF_INT): {
const uint16_t capture = tim->pr;
if (timerCtx->ch[0].cb && timerCtx->ch[0].cb->callbackOvr) {
timerCtx->ch[0].cb->callbackOvr(&timerCtx->ch[0], capture);
}
if (timerCtx->ch[1].cb && timerCtx->ch[1].cb->callbackOvr) {
timerCtx->ch[1].cb->callbackOvr(&timerCtx->ch[1], capture);
}
if (timerCtx->ch[2].cb && timerCtx->ch[2].cb->callbackOvr) {
timerCtx->ch[2].cb->callbackOvr(&timerCtx->ch[2], capture);
}
if (timerCtx->ch[3].cb && timerCtx->ch[3].cb->callbackOvr) {
timerCtx->ch[3].cb->callbackOvr(&timerCtx->ch[3], capture);
}
break;
}
case __builtin_clz(TMR_C1_INT):
timerCtx->ch[0].cb->callbackEdge(&timerCtx->ch[0], tim->c1dt);
break;
case __builtin_clz(TMR_C2_INT):
timerCtx->ch[1].cb->callbackEdge(&timerCtx->ch[1], tim->c2dt);
break;
case __builtin_clz(TMR_C3_INT):
timerCtx->ch[2].cb->callbackEdge(&timerCtx->ch[2], tim->c3dt);
break;
case __builtin_clz(TMR_C4_INT):
timerCtx->ch[3].cb->callbackEdge(&timerCtx->ch[3], tim->c4dt);
break;
}
}
else {
// timerConfig == NULL
volatile uint32_t tmp;
switch (bit) {
case __builtin_clz(TMR_OVF_INT):
tmp = tim->pr;
break;
case __builtin_clz(TMR_C1_INT):
tmp = tim->c1dt;
break;
case __builtin_clz(TMR_C2_INT):
tmp = tim->c2dt;
break;
case __builtin_clz(TMR_C3_INT):
tmp = tim->c3dt;
break;
case __builtin_clz(TMR_C4_INT):
tmp = tim->c4dt;
break;
}
(void)tmp;
}
}
}
void impl_timerPWMConfigChannel(TCH_t * tch, uint16_t value)
{
const bool inverted = tch->timHw->output & TIMER_OUTPUT_INVERTED;
tmr_output_config_type tmr_output_struct;
tmr_output_default_para_init(&tmr_output_struct);
tmr_output_struct.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A;
if (tch->timHw->output & TIMER_OUTPUT_N_CHANNEL) {
tmr_output_struct.oc_output_state = FALSE;
tmr_output_struct.occ_output_state = TRUE;
tmr_output_struct.occ_polarity = inverted ? TMR_OUTPUT_ACTIVE_LOW : TMR_OUTPUT_ACTIVE_HIGH;
tmr_output_struct.occ_idle_state = FALSE;
} else {
tmr_output_struct.oc_output_state = TRUE;
tmr_output_struct.occ_output_state = FALSE;
tmr_output_struct.oc_polarity = inverted ? TMR_OUTPUT_ACTIVE_LOW : TMR_OUTPUT_ACTIVE_HIGH;
tmr_output_struct.oc_idle_state = TRUE;
}
switch (tch->timHw->channelIndex) {
case 0:
tmr_output_channel_config(tch->timHw->tim,TMR_SELECT_CHANNEL_1, &tmr_output_struct);
tmr_channel_value_set(tch->timHw->tim, TMR_SELECT_CHANNEL_1, value);
tmr_output_channel_buffer_enable(tch->timHw->tim,TMR_SELECT_CHANNEL_1,TRUE);
break;
case 1:
tmr_output_channel_config(tch->timHw->tim,TMR_SELECT_CHANNEL_2, &tmr_output_struct);
tmr_channel_value_set(tch->timHw->tim, TMR_SELECT_CHANNEL_2, value);
tmr_output_channel_buffer_enable(tch->timHw->tim,TMR_SELECT_CHANNEL_2,TRUE);
break;
case 2:
tmr_output_channel_config(tch->timHw->tim,TMR_SELECT_CHANNEL_3, &tmr_output_struct);
tmr_channel_value_set(tch->timHw->tim, TMR_SELECT_CHANNEL_3, value);
tmr_output_channel_buffer_enable(tch->timHw->tim,TMR_SELECT_CHANNEL_3,TRUE);
break;
case 3:
tmr_output_channel_config(tch->timHw->tim,TMR_SELECT_CHANNEL_4, &tmr_output_struct);
tmr_channel_value_set(tch->timHw->tim, TMR_SELECT_CHANNEL_4, value);
tmr_output_channel_buffer_enable(tch->timHw->tim,TMR_SELECT_CHANNEL_4,TRUE);
break;
}
}
volatile timCCR_t * impl_timerCCR(TCH_t * tch)
{
switch (tch->timHw->channelIndex) {
case 0:
return &tch->timHw->tim->c1dt;
break;
case 1:
return &tch->timHw->tim->c2dt;
break;
case 2:
return &tch->timHw->tim->c3dt;
break;
case 3:
return &tch->timHw->tim->c4dt;
break;
}
return NULL;
}
// Set the channel control register
void impl_timerChCaptureCompareEnable(TCH_t * tch, bool enable)
{
tmr_channel_enable(tch->timHw->tim, lookupTIMChannelTable[tch->timHw->channelIndex],(enable ? TRUE : FALSE));
}
// lookupDMASourceTable
static void impl_timerDMA_IRQHandler(DMA_t descriptor)
{
if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) {
TCH_t * tch = (TCH_t *)descriptor->userParam;
tch->dmaState = TCH_DMA_IDLE;
dma_channel_enable(tch->dma->ref,FALSE);
tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], FALSE);
DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF);
}
}
bool impl_timerPWMConfigChannelDMA(TCH_t * tch, void * dmaBuffer, uint8_t dmaBufferElementSize, uint32_t dmaBufferElementCount)
{
dma_init_type dma_init_struct = {0};
tmr_type * timer = tch->timHw->tim;
tch->dma = dmaGetByTag(tch->timHw->dmaTag);
if (tch->dma == NULL) {
return false;
}
// If DMA is already in use - abort
if (tch->dma->owner != OWNER_FREE) {
return false;
}
// We assume that timer channels are already initialized by calls to
tmr_output_enable(timer, TRUE);
// enable The TMR periodic buffer register
tmr_period_buffer_enable(timer,TRUE);
tmr_channel_enable(timer, lookupTIMChannelTable[tch->timHw->channelIndex],TRUE);
tmr_counter_enable(timer, TRUE);
dmaInit(tch->dma, OWNER_TIMER, 0);
dmaSetHandler(tch->dma, impl_timerDMA_IRQHandler, NVIC_PRIO_TIMER_DMA, (uint32_t)tch);
dma_reset(tch->dma->ref);
dma_channel_enable(tch->dma->ref,FALSE);
dma_reset(tch->dma->ref);
dma_default_para_init(&dma_init_struct);
dma_init_struct.peripheral_base_addr = (uint32_t)impl_timerCCR(tch);
dma_init_struct.buffer_size = dmaBufferElementCount;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.loop_mode_enable = FALSE;
switch (dmaBufferElementSize) {
case 1:
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
break;
case 2:
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD;
break;
case 4:
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_WORD;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_WORD;
break;
default:
// Programmer error
while(1) {
}
}
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_base_addr = (uint32_t)dmaBuffer;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init(tch->dma->ref, &dma_init_struct);
//Set DMA request Mux mapping
dmaMuxEnable(tch->dma, tch->timHw->dmaMuxid);
dma_interrupt_enable(tch->dma->ref, DMA_IT_TCIF, TRUE);
return true;
}
void impl_timerPWMPrepareDMA(TCH_t * tch, uint32_t dmaBufferElementCount)
{
tch->dma = dmaGetByTag(tch->timHw->dmaTag);
if (tch->dma == NULL) {
return ;
}
// Make sure we terminate any DMA transaction currently in progress
// Clear the flag as well, so even if DMA transfer finishes while within ATOMIC_BLOCK
// the resulting IRQ won't mess up the DMA state
ATOMIC_BLOCK(NVIC_PRIO_MAX) {
dma_channel_enable(tch->dma->ref,FALSE);
tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], FALSE);
// clear dma flag
DMA_CLEAR_FLAG(tch->dma, DMA_IT_TCIF);
}
dma_data_number_set(tch->dma->ref, dmaBufferElementCount);
dma_channel_enable(tch->dma->ref,TRUE);
tch->dmaState = TCH_DMA_READY;
}
void impl_timerPWMStartDMA(TCH_t * tch)
{
uint16_t dmaSources = 0;
timHardwareContext_t * timCtx = tch->timCtx;
if (timCtx->ch[0].dmaState == TCH_DMA_READY) {
timCtx->ch[0].dmaState = TCH_DMA_ACTIVE;
dmaSources |= TMR_C1_DMA_REQUEST;
}
if (timCtx->ch[1].dmaState == TCH_DMA_READY) {
timCtx->ch[1].dmaState = TCH_DMA_ACTIVE;
dmaSources |= TMR_C2_DMA_REQUEST;
}
if (timCtx->ch[2].dmaState == TCH_DMA_READY) {
timCtx->ch[2].dmaState = TCH_DMA_ACTIVE;
dmaSources |= TMR_C3_DMA_REQUEST;
}
if (timCtx->ch[3].dmaState == TCH_DMA_READY) {
timCtx->ch[3].dmaState = TCH_DMA_ACTIVE;
dmaSources |= TMR_C4_DMA_REQUEST;
}
if (dmaSources) {
tmr_counter_value_set(tch->timHw->tim, 0);
tmr_dma_request_enable(tch->timHw->tim, dmaSources, TRUE);
}
}
void impl_timerPWMStopDMA(TCH_t * tch)
{
dma_channel_enable(tch->dma->ref,FALSE);
tmr_dma_request_enable(tch->timHw->tim, lookupDMASourceTable[tch->timHw->channelIndex], FALSE);
tch->dmaState = TCH_DMA_IDLE;
tmr_counter_enable(tch->timHw->tim, TRUE);
}