I'm a little bit stuck about timer synchronization with STM32F446RE.
I want to use 1 timer as master and two timers as slaves. The master timer (i.e. TIM2) has a period of 5 seconds and starts the other two timers at the same time.
The slave timers have own periods (1st slave has a period of 4 seconds and 2nd slave has a period of max 3 seconds). The 2nd slave timer (i.e. TIM1) will generate a one-pulse output. Both slaves should run 1 time and stop. They only should get activated again if the master timer sends a trigger. I want to use the 1. slave to adapt the period of the 2nd slave by calling an interrupt handler where I write the registers ARR and PSC and CCR1 (for one pulse).
I tried to do this with HAL but it's getting more and more confusing. Does anybody have a nice idea how to code this (little code-snippet would be very nice) with writing registers instead of HAL?
I also had a look to the timer cookbook of STM at chapter 6 but didn't get it working yet. https://www.st.com/content/ccc/resource/technical/document/application_note/group0/91/01/84/3f/7c/67/41/3f/DM00236305/files/DM00236305.pdf/jcr:content/translations/en.DM00236305.pdf
Thank you very much for any feedback!
Kind regards,
Tobi
OK the first part is done.
Configuration of TIM2:
- configure as master with a period of 10 seconds.
- use TIM_TRGO_UPDATE as output trigger for slave timer(s).
I first created the timer with STM32CubeMX and then examined the HAL-functions that got called.
static void Timer2_Init(){
/* activate clock for TIM2 peripheral */
__HAL_RCC_TIM2_CLK_ENABLE();
/* Prescaler */
TIM2->PSC = 44999; // bus is running with 90MHz
/* set counter mode */
TIM2->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
TIM2->CR1 |= TIM_COUNTERMODE_UP;
/* Auto-Reload Register */
TIM2->ARR = 20000;
/* Set Clock Division */
TIM2->CR1 &= ~ TIM_CR1_CKD;
TIM2->CR1 |= TIM_CLOCKDIVISION_DIV1;
/* set Auto-Reload-Preload */
//TIM2->CR1 |= (0 << 7);
/* Update Event - if this timer is configured as Master with output TRGO_UPDATE
* the slave timer TIM 1 will get a trigger and run one time
*
* This bit can be set by software, it is automatically cleared by hardware.
* 0: No action
* 1: Reinitialize the counter and generates an update of the registers. Note that the prescaler
* counter is cleared too (anyway the prescaler ratio is not affected). For more see manual. */
//TIM2->EGR = TIM_EGR_UG;
/* Set Clock Source */
TIM2->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_TS | TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
/* Master Configuration */
TIM2->CR2 &= ~TIM_CR2_MMS;
TIM2->CR2 |= TIM_TRGO_UPDATE;
TIM2->SMCR &= ~TIM_SMCR_MSM;
TIM2->SMCR |= TIM_SMCR_MSM;
TIM2->CR1 = TIM_CR1_CEN;
}
Next the initialization of TIM1:
- configure as master.
- set ARR for 5 seconds.
- set CCR1 for pulse lenght of 1 second.
Again I used STM32CubeMX to create the code first and then examined the content of all HAL functions.
static void Timer1_Init(){
/* activate clock for TIM1 peripheral */
__HAL_RCC_TIM1_CLK_ENABLE();
/* Edited Registers of HAL_TIM_Base_Init(&htim1) */
/* Prescaler */
TIM1->PSC = 17999; // bus is running with 180MHz
/* set counter mode */
TIM1->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
TIM1->CR1 |= TIM_COUNTERMODE_UP;
/* Auto-Reload-Register */
TIM1->ARR = 49999;
TIM1->CR1 &= ~ TIM_CR1_CKD;
TIM1->CR1 |= TIM_CLOCKDIVISION_DIV1;
/* repetition counter if pulse should be displayed more than 1 time */
TIM1->RCR = 0;
/* Auto-Reload Preload Enable */
//TIM1->CR1 |=TIM_CR1_ARPE;
/* update event */
TIM1->EGR = TIM_EGR_UG;
/* Edited registers of HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) */
TIM1->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_TS | TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
/* One Pulse Mode: Edited registers of HAL_TIM_OnePulse_Init(&htim1, TIM_OPMODE_SINGLE) */
//TIM1->CR1 &= ~TIM_CR1_OPM;
TIM1->CR1 |= TIM_CR1_OPM;
/* Slave Mode configuration: edited registers of HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) */
TIM1->SMCR &= ~TIM_SMCR_TS;
TIM1->SMCR |= TIM_TS_ITR1;
TIM1->SMCR &= ~TIM_SMCR_SMS;
TIM1->SMCR |= (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1); // = TIM_SLAVEMODE_TRIGGER -
// TIM1->DIER &= ~TIM_DIER_TIE;
// TIM1->DIER &= ~TIM_DIER_TDE;
/* HAL_TIM_PWM_ConfigChannel: HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) */
/* Disable the Channel 1: Reset the CC1E Bit */
// TIM1->CCER &= ~TIM_CCER_CC1E;
/* Reset the Output Compare Mode Bits */
TIM1->CCMR1 &= ~TIM_CCMR1_OC1M;
TIM1->CCMR1 &= ~TIM_CCMR1_CC1S;
/* Select the Output Compare (OC) Mode 1 */
TIM1->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1); // = TIM_OCMODE_PWM1
/* Reset and set the Output N Polarity level to LOW */
TIM1->CCER &= ~TIM_CCER_CC1P;
TIM1->CCER |= TIM_CCER_CC1P; // = TIM_OCPOLARITY_LOW
/* Reset the Output N State */
// TIM1->CCER &= ~TIM_CCER_CC1NP;
//TIM1->CCER |= 0x00000000U;
/* Reset the Output N State */
// TIM1->CCER &= ~TIM_CCER_CC1NE;
/* IS_TIM_BREAK_INSTANCE */
/* Reset the Output Compare and Output Compare N IDLE State */
// TIM1->CR2 &= ~TIM_CR2_OIS1;
// TIM1->CR2 &= ~TIM_CR2_OIS1N;
/* Set the Output Idle state */
//TIM1->CR2 |= 0x00000000U;
/* Set the Capture Compare Register: Pulse */
TIM1->CCR1 = 40000;
/* Set the Preload enable bit for channel 1 */
TIM1->CCMR1 |= TIM_CCMR1_OC1PE;
/* Configure the Output Fast mode */
// TIM1->CCMR1 &= ~TIM_CCMR1_OC1FE;
//TIM1->CCMR1 |= 0x00000000U;
/* Edited registers by HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) */
/* Enable the Capture compare channel */
TIM1->CCER |= (1 << 0); // = TIM_CCER_CC1E
/* Enable the main output */
TIM1->BDTR |= TIM_BDTR_MOE;
/* Initialize the GPIO Pin for output: HAL_TIM_MspPostInit(&htim1) */
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Enable Counter: will be automatically enabled with trigger event */
//TIM1->CR1 = TIM_CR1_CEN;
}
Next step is to configure a second slave timer (TIM3) that will edit the registers of TIM1.
static void Timer3_Init(){
/* activate clock for TIM1 peripheral */
__HAL_RCC_TIM3_CLK_ENABLE();
/* Edited Registers of HAL_TIM_Base_Init(&htim1) */
/* Prescaler */
TIM3->PSC = 50000; //44999;
/* set counter mode */
TIM3->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
TIM3->CR1 |= TIM_COUNTERMODE_UP;
/* Auto-Reload-Register */
TIM3->ARR = 11000;
TIM3->CR1 &= ~ TIM_CR1_CKD;
TIM3->CR1 |= TIM_CLOCKDIVISION_DIV1;
/* update event */
TIM3->EGR = TIM_EGR_UG;
/* Edited registers of HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) */
TIM3->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_TS | TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
/* One Pulse Mode: Edited registers of HAL_TIM_OnePulse_Init(&htim1, TIM_OPMODE_SINGLE) */
//TIM1->CR1 &= ~TIM_CR1_OPM;
TIM3->CR1 |= TIM_CR1_OPM;
/* Slave Mode configuration: edited registers of HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) */
TIM3->SMCR &= ~TIM_SMCR_TS;
TIM3->SMCR |= TIM_TS_ITR1;
TIM3->SMCR &= ~TIM_SMCR_SMS;
TIM3->SMCR |= (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1); // = TIM_SLAVEMODE_TRIGGER
/* HAL_TIM_PWM_ConfigChannel: HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) */
/* Disable the Channel 1: Reset the CC1E Bit */
/* Reset the Output Compare Mode Bits */
TIM3->CCMR1 &= ~TIM_CCMR1_OC1M;
TIM3->CCMR1 &= ~TIM_CCMR1_CC1S;
/* Select the Output Compare (OC) Mode 1 */
TIM3->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1); // = TIM_OCMODE_PWM1
/* Reset and set the Output N Polarity level to HIGH */
TIM3->CCER &= ~TIM_CCER_CC1P; // = TIM_OCPOLARITY_HIGH
/* Set the Capture Compare Register: Pulse */
TIM3->CCR1 = 0;
/* Set the Preload enable bit for channel 1 */
//TIM3->CCMR1 |= TIM_CCMR1_OC1PE;
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM3->DIER = TIM_DIER_CC1IE; //DMA Interrupt Enable Register (DIER): Interrupt "Capture/Compare 1 interrupt enable"
/* warning: setting this bit will cause the timer running continuously, but timer should only start with trigger,
* so don't set the CEN bit - let the trigger do the job automatically */
//TIM3->CR1 = TIM_CR1_CEN;
}
And finally the IRQ Handler for TIM3:
void TIM3_IRQHandler(void)
{
if (((TIM3->SR & TIM_FLAG_CC1) == TIM_FLAG_CC1) != RESET)
{
if (((TIM3->DIER & TIM_DIER_CC1IE) == TIM_DIER_CC1IE) != RESET)
{
TIM3->SR = ~ TIM_FLAG_CC1;
/* do something *
}
}
}
I'm happy for any feedback about this code.
I just noticed that the internal RC oscillator is not very accurate at my test environment. In the manual DM00135183.pdf in section "6.2.2 HSI Clock" you can read about the accuracy and how to trim the HSI. But I think it might be better to use an external crystal oscillator or ceramic resonator if you want more accurate timing.
If there is anything I did wrong or what I want to do will not work in the expected way, please also leave a comment.
Related
I was trying baremetal programming for STM32L412T6 controller. I got stuck at RTC wakeup interrupt. My code is jumping to while(1) loop without going to the interrupt handler, Below mentioning my code.
int main(void)
{
//LedConfig();
rtc_domain_access();
rtc_init();
initialize_rtc_wakeup();
NVIC_EnableIRQ(RTC_WKUP_IRQn);
/* Loop forever */
for(;;)
{
flag = 1;
}
}
void rtc_domain_access(void)
{
/* Enable Clock for Power interface.*/
RCC->APB1ENR1 |= (1U<<28);
/*Disable backup domain write protection*/
PWR->CR1 |= (1U<<8);
while((PWR->CR1 & (1U<<8))==0);
/*Reset the Backup domain*/
RCC->BDCR |= (1U<<16);
RCC->BDCR &= ~(1U<<16);
/*Enable LSE Clock source and wait until LSERDY bit to set*/
RCC->BDCR |= (1U<<0);
while ((RCC->BDCR & (1U<<1)) == 0);
/*Select LSE as RTC Clock*/
RCC->BDCR |= (1U<<8);
RCC->BDCR &= ~(1U<<9);
/*Enable RTC Clock*/
RCC->BDCR |= (1U<<15);
/*Disable access to RTC registers*/
RCC->APB1ENR1 &= ~(1U<<28);
}
void rtc_init(void)
{
/* Disable the write protection for RTC registers */
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
/* Check if the Initialization mode is set */
if((RTC->ICSR & (1U<<6))==0)
{
/* Set the Initialization mode */
RTC->ICSR |= (1U<<7);
/* Wait till RTC is in INIT state*/
while((RTC->ICSR & (1U<<6))==0);
}
/* Clear RTC_CR FMT, OSEL, POL and TAMPOE Bits */
RTC->CR &= ~(1U<<6); //FMT
RTC->CR &= ~(1U<<20); //POL
RTC->CR &= ~(1U<<21); //OSEL
RTC->CR &= ~(1U<<22); //OSEL
RTC->CR &= ~(1U<<26); //TAMPOE
/* Set RTC_CR register */
RTC->CR &= ~(1U<<6); //FMT bit set as Zero,Hour Format Selected as 24
RTC->CR &= ~(1U<<20); //POL bit set as Zero, Output polarity selected as high.
RTC->CR &= ~(1U<<21); //OSEL[22:21] set as zero, output selection disabled.
RTC->CR &= ~(1U<<22);
/* Configure the RTC PRER */
RTC->PRER = 0xFF; // Synchronus value set as 255
RTC->PRER |= (0x7F<<16); // Asynchronus value set as 127.
/* Exit Initialization mode */
RTC->ICSR &= ~(1U<<7); // Clear INIT bit.
/* If CR_BYPSHAD bit = 0, wait for synchro */
if((RTC->CR & (1U<<5))==0)
{
/* Clear RSF flag */
RTC->ICSR &= ~(1U<<5);
/* Wait the registers to be synchronised */
while((RTC->ICSR & (1U<<5))==0);
}
/* Clear RTC_CR TAMPALRM_PU, TAMPALRM_TYPE and OUT2EN Bits */
RTC->CR &= ~(1U<<29);
RTC->CR &= ~(1U<<30);
RTC->CR &= ~(1U<<31);
/*Set Output type as open drain pullup*/
RTC->CR |= (1U<<30);
/* Enable the write protection for RTC registers */
RTC->WPR = 0xFF;
}
void Pwr_Clear_Flag(void)
{
PWR->SCR = 0x1F;
}
void RTCEx_DeactivateWakeUpTimer(void)
{
/*Disable Write protection for RTC Registers*/
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
/*Disable the Wakeup Timer*/
RTC->CR &= ~(1U<<10);
/*In case of interrupt mode is used, the interrupt source must disabled*/
RTC->CR &= ~(1U<<14);
/* Wait till RTC WUTWF flag is set */
while ((RTC->ICSR & (1U<<2)) == 0);
/* Enable the write protection for RTC registers */
RTC->WPR = 0xFF;
}
void RTCEx_SetWakeUpTimer_IT(uint32_t RTC_WAKEUP_TIME_IN_SECONDS,uint32_t WakeUpAutoClr)
{
/* Disable the write protection for RTC registers */
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
/* Clear WUTE in RTC_CR to disable the wakeup timer */
RTC->CR &= ~(1U<<10);
/* Clear flag Wake-Up */
RTC->SCR = (1U<<2);
/* Poll WUTWF until it is set in RTC_ICSR to make sure the access to wakeup autoreload
counter and to WUCKSEL[2:0] bits is allowed. */
if((RTC->ICSR & (1U<<6))==0)
{
while((RTC->ICSR & (1U<<2))==0);
}
/* Configure the Wakeup Timer counter and auto clear value */
RTC->WUTR |= (RTC_WAKEUP_TIME_IN_SECONDS-1);
RTC->WUTR |= (WakeUpAutoClr<<16);
/* Configure the clock source */
RTC->CR &= ~(1U<<0);
RTC->CR &= ~(1U<<1);
RTC->CR |= (1U<<2);
/* RTC WakeUpTimer EXTI Configuration: Interrupt configuration */
EXTI->IMR1 |= (1U<<20);
EXTI->RTSR1 |= (1U<<20);
/* Configure the Interrupt in the RTC_CR register */
RTC->CR |= (1U<<14);
/* Enable the Wakeup Timer */
RTC->CR |= (1U<<10);
/* Enable the write protection for RTC registers */
RTC->WPR = 0xFF;
}
void initialize_rtc_wakeup(void)
{
RTCEx_DeactivateWakeUpTimer();
Pwr_Clear_Flag();
RTCEx_SetWakeUpTimer_IT(RTC_WAKEUP_TIME_IN_SECONDS,WakeUpAutoClr);
}
void RTC_WKUP_IRQHandler(void)
{
EXTI->PR1 = (1U<<20);
if((RTC->MISR & (1U<<2))!=0)
{
//GPIOB->ODR |= 1<<13;
RTC->SCR |= (1U<<2);
count++ ;
}
}
I tried to make a RTC wakeup interrupt in every 3 seconds, when it occurs I tried to increase the value of variable count, instead of that it goes to the infinite loop and changes the flag value.
The issue is solved, what happened is RTC_WUTR register has reset value 0x0000FFFF before giving my wakeup clock I had to set the register to zero, once I done that my issue is Solved.
I'm trying to read two ADC channels sequentially from my STM32F407ZGT6 using DMA. I'm just trying to get the values from two potentiometers independently on each channel. Although the program doesn't crash, I does not update my variable's value (sensor_val).
I'm using DMA2_Stream0 Channel 0, since I'm using ADC1. For my ADC1, I'm using PB1 (channel 9) and PA1 (channel 1). I tried to follow this tutorial, except that I do not want to trigger my ADC from a timmer just yet, and I've been also checking the example on this question. My ADC callback also never gets called. As far as my understanding goes, the sequence of calls should be:
ADC1->SR EOC --> ADC->CR1 EOCIE --> DMA2_Stream0_IRQHandler() --> dma_ADC_callback()┐
⮤─────────────────────────────────────────────────────────┘
Maybe I do need to include a periodic call to read the ADC?
All the ADC/DMA functions are on their separate .c/.h files. I've already tried to declare sensor_val as a global variable or as an extern variable from the adc.c file, and both give the same result. Here is an approximate mwe of my code:
#include <stdint.h> //uint32_t
#include <stdio.h> //printf
#include "stm32f407xx.h"
#define set(val, pos) ((val) << (pos))
#define msk(size, pos) (((1UL << (size)) - 1UL) << (pos))
static uint32_t sensor_val[2];
static void dma_ADC_callback(void);
void gpio_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Port A
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Port B
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Port C
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Port D
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Port F
RCC->AHB1ENR;
//PORT B
GPIOA->MODER |= set(m,(2)); //ADC || PA1 || ADC123_IN1
GPIOB->MODER |= set(m,(2)); //ADC || PB1 || ADC12_IN9
}
void adc_init(void)
{
/*Enable clock access to ADC*/
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
// RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
/* Config ADC parameters*/
/* Regular Sequence Register 3
* Since the sequence starts from
* the back, we need to set channels
* from SQ3[0] to SQ1[19]
*/
ADC1->SQR3 |= set(0b1001,0); //sets channel PB1 (ADC12_IN9) as 1st conversion
ADC1->SQR3 |= set(0b0001,5); //sets channel PA1 (ADC123_IN1) as 2nd conversion
ADC1->SQR1 |= set(0b0001,ADC_SQR1_L_Pos); //tells the channel sequence lenght = 2
/*If using more than one channel
* SCAN is required
*/
ADC1->CR1 |= set(1,ADC_CR1_SCAN_Pos);
/*Adjust ADC sample time
* The resulting frequency is
* APB2/#cycles:
* 000: 3 cycles
* 001: 15 cycles
* 010: 28 cycles
* 011: 56 cycles
* 100: 84 cycles
* 101: 112 cycles
* 110: 144 cycles
* 111: 480 cycles = 42MHz/480 = 87.5kHz
* */
ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP0_Pos); //channel 0
ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP9_Pos); //channel 9
/*Turn Interruption On*/
ADC1->CR1 |= set(1,ADC_CR1_EOCIE_Pos);
/*Enable ADC*/
ADC1->CR2 |= set(1,ADC_CR2_ADON_Pos);
}
void adc_start_conversion(void)
{
ADC1->CR2 |= set(1,ADC_CR2_EOCS_Pos); //enables multi-channel conversion
ADC1->CR2 |= set(1,ADC_CR2_CONT_Pos); //enables continuous conversion
ADC1->CR2 |= set(1,ADC_CR2_SWSTART_Pos); //starts conversion
}
/*ADC1 DMA2 => DMA2_Ch0_Stream0 and 4*/
/*ADC2 DMA2 => DMA2_Ch1_Stream2 and 3*/
/*ADC3 DMA2 => DMA2_Ch2_Stream0 and 1*/
void dma2_stream0_init(uint32_t memo, uint32_t periph, uint32_t len)
{
/*Enable clock acces to DMA*/
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
/*Diable DMA2 Stream 0*/
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
/*Clear all interrupt flags of Stream 0*/
DMA1->LIFCR |= DMA_LIFCR_CFEIF0;
DMA1->LIFCR |= DMA_LIFCR_CDMEIF0;
DMA1->LIFCR |= DMA_LIFCR_CTEIF0;
DMA1->LIFCR |= DMA_LIFCR_CHTIF0;
/*Set the source buffer*/ //Memory Address
DMA2_Stream0->M0AR = memo;
/*Set destination buffer*/ //Peripherial Address
DMA2_Stream0->PAR = periph;
/*Set the length*/
DMA2_Stream0->NDTR = len;
/*Set Control options
* Select Stream0_CH0 |
* Prioritu Lvl = High |
* Memory Increment On |
* Circular mode on |
* Direction Per->Mem (0b00)|
* Enable Transfer Complete interrupt
*/
DMA2_Stream0->CR &= ~(DMA_SxCR_CHSEL |
DMA_SxCR_PL |
DMA_SxCR_MSIZE |
DMA_SxCR_PSIZE |
DMA_SxCR_PINC);
DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos) |
set(2,DMA_SxCR_PL_Pos) |
set(1,DMA_SxCR_MINC_Pos) |
set(1,DMA_SxCR_CIRC_Pos) |
set(0,DMA_SxCR_DIR_Pos) |
set(1,DMA_SxCR_TCIE_Pos)
);
/*Enable direct mode and disable FIFO*/
DMA2_Stream0->FCR = 0;//set(0,DMA_SxFCR_FEIE_Pos);
/*!! Enable DMA1 Stream 6*/
DMA2_Stream0->CR |= set(1,DMA_SxCR_EN_Pos);
/*Enable ADC transmitter DMA*/
ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);
/*DMA Interrupt enable in NVIC*/
NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
void DMA2_Stream0_IRQHandler(void)
{
/*Check for transfer complete interrupt*/
if(DMA2->LISR & msk(1,DMA_LISR_TCIF0_Pos))
{
//Clear flag
DMA2->LIFCR |= msk(1,DMA_LIFCR_CTCIF0_Pos);
//Callback
dma_ADC_callback();
}
}
static void dma_ADC_callback(void)
{
printf("[ Readings Pots.: | %li | %li ]\r\n", sensor_val[0], sensor_val[1]);
}
int main(void)
{
/*Setup*/
//clock_init_168(); //SYSCLK = 168MHz, AHB = 84MHz, APB1 = 42MHz, APB2 = 84MHz
//init_systick_MS(SYSTICK_LOAD_VAL_MS);
gpio_init();
dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
adc_init();
adc_start_conversion(); //continuous conversion
char count = 0;
for(;;)
{
debug_msg("count : %d", __PRETTY_FUNCTION__, count);
delayMS(500);
count++;
}
}
I finally found a way to fix it!
First of all, the ADC has to be enabled before the DMA. With the code above, ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos) wouldn't get set because I was trying to set the DMA before the ADC. Hence, my interruption never got called. The correct order of function calls is:
adc_init();
dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
adc_start_conversion();
However, after ADC_CR2_DMA was getting set, my callback got called, but only once. So I had to disable DMA selection with ACD1->CR2 = ADC_CR2_DDS, so DMA would issue conversion requests recurrently:
/*Enable ADC transmitter DMA*/
ADC1->CR2 |= (set(1,ADC_CR2_DMA_Pos) | //<<make sure ADC is already enabled
set(1,ADC_CR2_DDS_Pos)); //<<without DDS, the DMA does a single conversion
Once I did that, my callback got called and I was reading data, but only sensor_val[0] was being updated. That was happening because I had PSIZE == 00, which is the value used to set MSIZE in direct mode. Since I'm using DMA for ADC (maximum 12-bit resolution), I changed sensor_val to uint16_t and PSIZE = 0b01:
DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos) |
set(2,DMA_SxCR_PL_Pos) |
set(1,DMA_SxCR_PSIZE_Pos) | //<<sets MSIZE=PSIZE in direct mode
set(1,DMA_SxCR_MINC_Pos) |
set(1,DMA_SxCR_CIRC_Pos) |
set(0,DMA_SxCR_DIR_Pos) |
set(1,DMA_SxCR_TCIE_Pos));
And voilá!
NOTES
PINC should be kept at reset value for this configuration, otherwise ADC will expect data with more than 16-bits.
If you need slower rates of acquisition, instead of setting DDS, a DMA request function can be created where the DMA bit is reset and set again:
void adc_new_dma_conversion(void)
{
/*DMA has to be reset first
* then re-enabled to generate
* a new DMA request
*/
ADC1->CR2 &= ~set(1,ADC_CR2_DMA_Pos);
ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);
}
I am learning to use the STM32F302R8 board and I have this problem.
TIM2 is configured with toggle output CH1 on PA0. (This works fine).
TIM15 is configured as input capture CH1 on PA2.
I have a jumper between PA0 and PA2.
The aim is that when TIM2 reaches the value of CCR1 it triggers the PA0 pin, which happens, and having the jumper with the PA2 pin should trigger the TIM15 input but this does not happen.
The while that checks the CC1IF flag never ends, it doesn't detect anything.
What can it be?
while (1)
{
// wait until input edge is captured
while ( !(TIM15->SR & TIM_SR_CC1IF)) {}
timestamp = TIM15->CCR1; // read captured counter value
}
void mi_GPIO_Init ( void ) {
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN;
GPIOA->MODER &= ~GPIO_MODER_MODER0; // clear PA0 mode
GPIOA->MODER |= GPIO_MODER_MODER0_1; // set pin to alt function
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0; // clear pin AF bits
GPIOA->AFR[0] |= 0x0000001; // set pin to AF1 for TIM2_CH1
GPIOA->MODER &= ~GPIO_MODER_MODER2; // clear PA2 mode
GPIOA->MODER |= GPIO_MODER_MODER2_1; // set pin to alt function
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL2; // clear pin AF bits
GPIOA->AFR[0] |= 0x0000900; // set pin to AF9 for TIM15_CH1
// Configure TIM2 to wrap around at 1 Hz and toggle CH1 output when the counter value is 0
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // enable TIM2 clock
TIM2->PSC = 800 - 1; // divided by 800
TIM2->ARR = 10000 -1; // divided by 10000
TIM2->CCMR1 = TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1; // set output to toggle on match
// The rest of the bits are set to 0.
TIM2->CCR1 = 0; // set match value
TIM2->CCER |= TIM_CCER_CC1E; // enable CH1 compare mode
TIM2->CNT = 0; // clear timer counter
TIM2->CR1 |= TIM_CR1_CEN; // enable TIM2
// Configure TIM15 to do input capture
RCC->APB2ENR |= RCC_APB2ENR_TIM15EN; // enable TIM15 clock
TIM15->PSC = 8000 - 1; // divided by 8000
TIM15->ARR = 0xFFFF; // count until ARR
TIM15->CCMR1 &= ~TIM_CCMR1_CC1S; // set CH1 to capture at every edge
TIM15->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1 as input, IC1 is mapped on TI1
TIM15->CCER |= TIM_CCER_CC1E; // enable CH1 capture rising edge
TIM15->CR1 |= TIM_CR1_CEN; // enable TIM15
}
I moved the clock trigger lines to the beginning of the code and instead of PA2 I use PB14 and now it works fine.
I am having an issue in getting my computer (virtual COM port, to be exact) to communicate with my STM32L053R8T6 (Nucleo) board by DMA and USART. Here is my code for the DMA and USART part:
#include "Device/Include/stm32l0xx.h" // Device header
#include "JB.h"
#include <string.h>
#define PCLK 32000000
#define BAUD 19200
uint8_t stringtosend[] = "test\n";
uint8_t stringtoreceive[] = " ";
void ENABLE_UART_DMA(void){
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //enable periph.clk for DMA1
/**Enabling DMA for transmission
* DMA1, Channel 4 mapped for USART2TX
* USART2 TDR for peripheral address
* stringtosend for data address
* Memory increment, memory to peripheral | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C4S) | (4 << (3 * 4));
DMA1_Channel4->CPAR = (uint32_t)&(USART2->TDR);
DMA1_Channel4->CMAR = (uint32_t)stringtosend;
DMA1_Channel4->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;
/**Enabling DMA for reception
* DMA1, Channel 5 mapped for USART2RX
* USART2 RDR for peripheral address
* stringtoreceive for data address
* Data size given
* Memory increment, peripheral to memory | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C5S) | (4 << (4 * 4));
DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);
DMA1_Channel5->CMAR = (uint32_t)stringtoreceive;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);
DMA1_Channel5->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_EN;
NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 0); //NVIC enabled, max priority, channels 4-7
NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn);
}
void CONFIGURE_UART_PARAM(void){
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
RCC->APB1ENR |= ( 1ul << 17); //Enable USART#2 clock
GPIOA->AFR[0] &= ~((15ul << 4* 3) | (15ul << 4* 2) ); //Clear PA2,PA3
GPIOA->AFR[0] |= (( 4ul << 4* 3) | ( 4ul << 4* 2) ); //Set PA2,PA3
GPIOA->MODER &= ~(( 3ul << 2* 3) | ( 3ul << 2* 2) ); //Same as above
GPIOA->MODER |= (( 2ul << 2* 3) | ( 2ul << 2* 2) );
USART2->BRR = PCLK/BAUD;
USART2->CR3 = USART_CR3_DMAT | USART_CR3_DMAR; //Enable DMA mode in transmit and receive
/*UART enabled for transmission and reception*/
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
while((USART2->ISR & USART_ISR_TC) != USART_ISR_TC)
{
/* add time out here for a robust application */
}
USART2->ICR = USART_ICR_TCCF;
}
void CONFIGURE_EXTI(void){
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
EXTI->FTSR |= EXTI_FTSR_TR13; //falling edge trigger
EXTI->IMR |= EXTI_IMR_IM13; //unmask
NVIC_SetPriority(EXTI4_15_IRQn, 0); //def interrupt
NVIC_EnableIRQ(EXTI4_15_IRQn);
}
/*************************************************************************************************************************************************************************************************************************/
/*************************************************************************************************************************************************************************************************************************/
/*Interrupt Handlers*/
void DMA1_Channel4_5_6_7IRQHandler(void){
if((DMA1->ISR & DMA_ISR_TCIF4) == DMA_ISR_TCIF4){
DMA1->IFCR = DMA_IFCR_CTCIF4; //Clear Channel 4 Transfer Complete flag
}
else if((DMA1->ISR & DMA_ISR_TCIF5) == DMA_ISR_TCIF5){
DMA1->IFCR = DMA_IFCR_CTCIF5; //Clear Channel 5 Transfer Complete flag
DMA1_Channel5->CCR &= ~DMA_CCR_EN;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);/* Data size */
DMA1_Channel5->CCR |= DMA_CCR_EN;
}
}
void EXTI4_15_IRQHandler(void){
if(!(GPIOC->IDR & (1 << 13))){
/* Clear EXTI 13 flag */
EXTI->PR = EXTI_PR_PIF13;
/* start 8-bit transmission with DMA */
DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
}
}
//void EXTI4_15_IRQHandler(void){
// if((EXTI->PR & EXTI_PR_PIF13) == EXTI_PR_PIF13){
// /* Clear EXTI 13 flag */
// EXTI->PR = EXTI_PR_PIF13;
//
// /* start 8-bit transmission with DMA */
// DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
// DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
// DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
// }
//}
Now then, this specific code is based on an example from the STM32L0 snippets package 1.20, USART/Communcation Using DMA. USART 1 was simply redefined to USART 2 (as that is the one used by the virtual COM port), and the DMA channels were redefined according to that as well. However, the problem here is very simple: it will only print stringtosend once (would like to do it every time button B1 is pressed), and will not receive data by RX either - as if it completely ignores the DMA interrupt handler - which I am not sure how to test (no trace features available on this board). What I have seems to reflect the reference manual well enough, and all the main does is:
int main(){
SystemCoreClockInit();
CONFIGURE_UART_PARAM();
ENABLE_UART_DMA();
pushbutton_def();
CONFIGURE_EXTI();
while(1){
}
...which should just react to the defined interrupts, however it does not, and for the life of me, I cannot see why. I would love if you could help me - I would also like to avoid HAL or LL APIs - this is not a complex enough project to warrant their usage (several inputs, outputs, comms between two boards by USART/DMA), plus I would prefer to learn working closer to the register level.
Thanks!
edit (in response to Berendi's suggestions):
1. GPIOC was defined in another file, called with pushbutton_def():
RCC->IOPENR |= (1UL << 2); //enable GPIOC
I understand exactly what you mean by your explanation (indeed, the register referred by those two is "the same", 0x00000020U), but I am not sure as to how to redefine it: here is my attempt after looking at the reference manual (SYSCFG part) and the source (still, it does not work):
SYSCFG->EXTICR[3] = ((SYSCFG->EXTICR[3] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC);
As suggested, I have added USART2->ICR = USART_ICR_TCCF; to the EXTIhandler, right after the DMA channels. I have kept it in the USART definition. The message is still only being sent once, though.
GPIOC is not enabled
Here,
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
you should enable GPIOC too.
EXTI13 is mapped to PA13
Here,
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
you are setting the configuration register for EXTI0-EXTI3, actually mapping EXTI1 to PC1. EXTI13 remains mapped to PA13, which is actually SWDIO, connected to the onboard debugger. I guess the traffic on SWDIO triggers the EXTI interrupt, the handler checks PC13 which is always reading 0 because the port is disabled, and enables DMA. DMA transmit works only once though, because
USART_ISR_TC is not cleared in the interrupt
but only once at startup. You should move this line
USART2->ICR = USART_ICR_TCCF;
to the EXTI interrupt handler.
I'm not sure why receiving doesn't work, perhaps the DMA handler has no chance to run, because EXTI is constantly retriggered by SWD traffic. Both interrupts have the same priority, the one with lower interrupt number wins, which is the EXTI handler. If it's always retriggered before it finishes, then it will be called again, not letting the other handler to run.
I am using a Kinetis KEA64 microcontroller from NXP.
The actual frequency of clock is 20 MHz, and a timer interrupt is generated at every 2.5ms. I have an interrupt handler that toggles an LED when this timer interrupt is generated. LED is toggling on this timer interrupt but i dont know exactly the frequency of LED. Does my LED toggles at 5kHz? Is it correct?
void interrupt_application_timer_FTM0()
{
SIM_SCGC |= SIM_SCGC_FTM0_MASK; /* Enable Clock for FTM0 */
FTM0_SC |= FTM_SC_PS(7); /* Select Preescaler in this case 128. 20 Mhz /128 =156.25 Khz. */
/* Counter increase by one every 6.4 us */
/* Enable Channle 0*/
FTM0_C0SC |= FTM_CnSC_CHIE_MASK; /* Enable channel 0 interrupt */
FTM0_C0SC |= FTM_CnSC_MSA_MASK; /* Channel as Output compare mode */
/*Select interrupt frequency*/
FTM0_C0V = FTM_CnV_VAL(391) ; /* Interrupt every 2.5ms */
FTM0_SC |= FTM_SC_CLKS(1); /*FTM0 use system clock*/
/* Set the ICPR and ISER registers accordingly */
NVIC_ICPR |= 1 << ((INT_FTM0-16)%32);
NVIC_ISER |= 1 << ((INT_FTM0-16)%32);
}
Here is my interrupt handler
void FTM0_IRQHandler()
{
if (1==((FTM0_C0SC & FTM_CnSC_CHF_MASK)>>FTM_CnSC_CHF_SHIFT) ) /* If the CHF of the channel is equal to 0 */
{
(void)FTM0_C0SC; /* Read to clear flag */
FTM0_C0SC ^= FTM_CnSC_CHF_MASK; /* Clear flag */
FTM0_C0V = FTM0_C0V + 391 ; /* Refresh interrupt period */
if (LED_counter>=50){
/* Toggle LED */
/* Reset counter */
LED0_TOGGLE;
LED_counter = 0;
}
LED_counter++;
}
}
Use scope for frequency measuring
In the MCU, you can enable bus clock to one pin (Busclockout), enable that and check the bus clock so that you can confirm your calculations
Then use scope at LED to confirm your frequency