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
Related
I'm attempting to use the STM32L011K4's DMA controller to communicate with slave devices over I2C. Currently, I have no slave devices and am just trying to get the microcontroller to send the start condition out onto the I2C bus, but that is not happening.
When I run this code in debugging mode through the STM32CubeIDE, I notice that the start bit is set, but it never clears even though the reference manual says it should be cleared by hardware once the start condition occurs (page 656 for I2C_CR2).
Monitoring the SDA and SCL lines on my oscilloscope also show that they are a logical 1. Note: I'm using the NUCLEO-L011K4 on a breadboard, so the IO pins are tied to Vref through 1k resistors. All configuration registers appear to contain the desired value when the code is stuck sending the start condition, so I don't believe they are getting clobbered by a random line of code.
I'm not sure what's preventing the start condition from being sent, so any help would be greatly appreciated.
STM32L011K4 Datasheet:
https://www.st.com/content/ccc/resource/technical/document/datasheet/42/c0/ab/e5/71/7a/47/0b/DM00206508.pdf/files/DM00206508.pdf/jcr:content/translations/en.DM00206508.pdf
STM32L011K4 Reference Manual: https://www.st.com/resource/en/reference_manual/dm00108282-ultralowpower-stm32l0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
Initialization code:
void Init_I2C1_DMA() {
/* Basic I2C Initialization for 100 kHz I2C, 24 MHz SYSCLK, /1 APB1 scaler */
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable peripheral clock for I2C1
I2C1->CR1 &= ~(I2C_CR1_PE); // Disable I2C1
I2C1->CR1 = 0; // Reset CR1
I2C1->TIMINGR = 0; // Reset timer settings
/* APB1 clock (I2C1 clock) is set in RCC_CFGR reg -- keep at divide by 1 -- 24 MHz SYSCLK
* Refer to table 103 for timing value source. t_presc was found to be 250 ns for 100 kHz I2C, so PRESC was set to match that for SYSCLK = 24 MHz
* All subsequent settings are copied from table 103 from STM32L011K4 reference manual
*/
I2C1->TIMINGR |= (0x5 << 28)|(0x4 << 20)|(0x2 << 16)|(0x0F << 8)|(0x13 << 0);
/* Desired settings:
* RXDMAEN enable, ANF enable.
*/
I2C1->CR1 |= (0x8 << I2C_CR1_DNF_Pos)|I2C_CR1_ERRIE;
I2C1->CR2 = 0; // Reset contents (ACKs are enabled by default)
NVIC_EnableIRQ(I2C1_IRQn);
NVIC_SetPriority(I2C1_IRQn, 0);
/* DMA initialization */
/* Since this is peripheral to memory, we use I2C1_RX, which is available on DMA channels 3,7. We used channel 3, but 7 would work the same. */
RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Enable peripheral clock for DMA1
DMA1_Channel3->CCR &= ~(0x00000001); // Disable Channel 3 DMA
// Configure DMA channel mapping
DMA1_CSELR->CSELR &= ~0x00000F00; // Channel 3 re-mapping mask
DMA1_CSELR->CSELR |= 0x00000600; // Channel 3 re-mapped to I2C1_RX
/* Configure NVIC for DMA */
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
return;
}
void I2C1_DMA_Start_Read(uint8_t SlaveAddress, uint8_t RegisterAddress, int* MemoryBaseAddress, int BufferSize) {
// We need to put the device address out on the serial line before we can hand it over to the DMA
I2C1->CR1 &= ~(I2C_CR1_PE); // Disable I2C1
DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable Channel 3 DMA
DMA1_Channel3->CCR &= ~(0x00007FFF); // Channel 3 DMA mask
// Configure DMA Channel 3 for 16 bit memory and peripheral, and other aliased settings (reference manual page 249, 10.4.3)
DMA1_Channel3->CCR |= (0b01 << 10)|(0b01 << 8)|DMA_CCR_MINC|DMA_CCR_TEIE|DMA_CCR_TCIE;
DMA1_Channel3->CPAR = (uint32_t) RegisterAddress;
DMA1_Channel3->CMAR = (uint32_t) MemoryBaseAddress;
DMA1_Channel3->CNDTR = (uint16_t) BufferSize;
I2C1->CR1 &= (~I2C_CR1_TXDMAEN); // Disable TX DMA for I2C1
I2C1->CR1 |= I2C_CR1_RXDMAEN; // Enable RX DMA for I2C1
// I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); // Set up the slave address for DMA read
while(!(I2C1->ISR & I2C_ISR_TXE));
I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1)); // Set up the slave address for DMA read
I2C1->CR2 |= I2C_CR2_RD_WRN;
DMA1_Channel3->CCR |= DMA_CCR_EN; // Activate DMA channel 3
I2C1->CR1 |= I2C_CR1_PE; // Enable I2C1
I2C1->CR2 |= I2C_CR2_START; // Generate start condition
while(I2C1->CR2 & I2C_CR2_START); // Wait until hardware clears the start bit
// ???
return;
}
According with reference manual(p. 604)
you need uncomment I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); and comment I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1)); for set slave address, and you need set the number of bytes to be transferred.
I can't see your initialisation of GPIO. Check is GPIO settings right (Alternative function and open-drain mode).
Also in reference manual written this
PE must be kept low during at least 3 APB clock cycles in order to perform the software
reset. This is ensured by writing the following software sequence: - Write PE=0 - Check
PE=0 - Write PE=1.
I think you should try to do so.
Also I advise using 4.7k resistor for pulling to VDD.
I use Stm32f103c8t6 processor and I want to make 1 second counter. Normaly I dont use tımer update ınterrupt and my counter counts correctly but when I add timer update ınterrupt in code my counter doesnt count correctly. So fast increment. And ı cant remove update interrupt flag (UIF) in TIM1's SR register. If I remove this flag my code is entering infinty loop. I cant any solution for this problem. Thanks for help
This part is bring in startup_stm32f10x_md.s
/**
* #brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
*
* #param None
* #retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop -----------------------------> my code is stuck here
.size Default_Handler, .-Default_Handler
And this is a my code.
#include <stddef.h>
#include "stm32f10x.h"
void CLK_Config(){
RCC-> CR = 0x00000000; //CR Reset
RCC-> CR |= 0x00010000; //HSE enable
while(!(RCC-> CR & 0x00020000)); //HSE FLAG control
RCC-> CR |= 0x00080000; //CSS enable
RCC-> CR |= 0x01000000; //PLL On
RCC-> CFGR |= 0x00010000; //HSE Select PLL input
RCC-> CFGR |= 0x001C0000; //PLL Multi With 9 = 72 Mhz
RCC-> CFGR |= 0x00000002; //PLL Select as SYSCLK
RCC-> CFGR |= 0x00000400; //APB1 Clock divided by 2
RCC-> APB1ENR |= 0x18000000; //APB1 BKP Clock Enable
PWR-> CR |= 0x0100; //PWR BKP Access Enable
RCC-> APB2ENR |= 0x00000001; //APB2 AFIO Clock Enable
}
void TIM1_Config(){
RCC-> APB2ENR |= 0x00000800; //TIM1 CLK Enable
TIM1-> CR1 |= 0x0085; //Update Request Source, Counter Enable
TIM1-> DIER = 0x0001; //Update Interrupt Enable
TIM1-> ARR = 0x1F40; //8000 is set as Auto Reload Value
TIM1-> PSC = 0x2327; //9000 is set as Prescaler Value for 1 sn Formula: 1sn=1Hz=(PCLK/(PSC*ARR))
}
void USART1_Config(){
RCC-> APB2ENR |= 0x00000004; //GPIOA CLK Enable
GPIOA-> CRH |= 0x00000AA0; //GPIOA 10,9 Push-Pull Alternate Function 2Mhz
RCC-> APB2ENR |= 0x00004000; //USART1 CLK Enable
USART1-> BRR |= 0x00001D4C; //USART1 Baund Rate 9600
USART1-> CR1 |= 0x000020C8; //USART, TXE Interrupt, TC Interrupt, Transmitter Enable
}
void Interrupt_Config(){
NVIC-> ISER[0] |= 0x02000000; //NVIC TIM1 UP
NVIC-> ISER[1] |= 0x00000020; //NVIC USART1 Global Interrupt
NVIC-> IP[25] = 0x10; //TIM1 UP Interrupt Priority 2. 25th Interrupt
NVIC-> IP[37] = 0x40; //USART1 Global Interrupt Priority 5. 37th Interrupt
}
uint8_t count1sec; // Global variable
int main(void)
{
CLK_Config();
Interrupt_Config();
TIM1_Config();
USART1_Config();
while (1)
{
if(count1sec != 0){
USART1-> DR = count1sec;
}
}
}
void TIM1_UP_IRQHandler(){
TIM1-> SR = 0x00000000;
NVIC-> ICPR[0] = 0x02000000;
count1sec += 1;
}
I'm not able to detect the exact cause of your problem, but I can provide some suggestions and make some guesses.
1) Do not use magic numbers! Use predefined bit names. Here is an example:
USART2->BRR = 0x1a0; // 115200 bps # 24 MHz (OVER8=1, ONEBIT=1)
USART2->CR1 |= USART_CR1_OVER8 // Oversampling is reduced for higher baud rates
| USART_CR1_IDLEIE // Idle line detection interrupt is enabled
| USART_CR1_TE // Transmitter is enabled
| USART_CR1_RE; // Receiver is enabled
USART2->CR3 |= USART_CR3_ONEBIT // One bit mode for increased clock deviation tolerance
| USART_CR3_DMAT // DMA for TX
| USART_CR3_DMAR; // DMA for RX
USART2->CR1 |= USART_CR1_UE; // Enable USART2
2) Use CMSIS functions to access NVIC functionality. Here is an example:
NVIC_SetPriority(TIM6_DAC_IRQn, 2);
NVIC_EnableIRQ(TIM6_DAC_IRQn);
3) You have Auto-reload preload enable bit set in TIM1->CR1 register. This normally delays the update of the ARR register until the next update event. I'm not sure how it works during the initial run of the timer, but I suggest to avoid using it until you're sure everything works fine.
4) You normally don't need to clear pending bits in NVIC. I suggest removing that code from the ISR. Clearing the flags in the peripheral registers is all you need and you're already doing it with TIM1->SR = 0 line.
5) The reason you stuck in Infinite_Loop is probably the USART TX interrupt. It gets fired but as you didn't supply a ISR for it, it falls into the Default_Handler. You load DR manually in the main loop, so in this case you don't even need USART TX interrupts.
I have same problem today.
I'm using STM32H743zi.
In my case, after clearing SR, at least 13 'nop' required.
I don't know why.
My final solution is:
void TIM16_IRQHandler(void)
{
TIM16->SR = 0 ;
volatile uint32_t sr = TIM16->SR ;
UNUSED(sr) ;
}
Reading back make it work, but I don't know why.
I try to disable MPU and/or DCACHE, but it have no difference.
I am using a ATtiny817 xplained Mini, I want to toggle LED using overflow interrupt for every 10 sec from RTC, but i am not able generate the interrupt. Is the configurations correct?
I have configured the Main clock source 32kHz (Low Power Oscillator), using 1KHz from it to clock the RTC with no prescaler.
#define RTC_SAMPLE_PERIOD (1024 * 10)
void RTC_init(void)
{
/* Configuring the Clock Source */
_PROTECTED_WRITE(CLKCTRL.OSC32KCTRLA, CLKCTRL_RUNSTDBY_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, !CLKCTRL_PEN_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, !CLKCTRL_CLKOUT_bm | CLKCTRL_CLKSEL_OSCULP32K_gc);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_OSC32KS_bm));
/* Initialize RTC */
while (RTC.STATUS > 0)
; //Wait for All registers to be Synchronized
/* Set Period */
RTC.PER = RTC_SAMPLE_PERIOD;
/* Configuring RTC CLOCK */
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; //Selecting 1kHz from 32KHz Low Power Oscillator (OSCULP32K)
/* Run in debug: enabled */
RTC.DBGCTRL |= RTC_DBGRUN_bm;
RTC.CTRLA = RTC_RTCEN_bm /* Enable: enabled */
| RTC_RUNSTDBY_bm; /* Run In Standby: enabled */
/* Enable Overflow Interrupt */
RTC.INTCTRL |= RTC_OVF_bm;
}
ISR(RTC_CNT_vect)
{
LEDupdateFlg = 1;
RTC.INTFLAGS |= RTC_OVF_bm;
}
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.
I'm developing a C application using avr-libc on an AVR ATmega328P microcontroller. Since I don't have an ICE debugger for it, I followed these instructions and this tutorial for making the stdio.h functions such as printf able to use the hardware UART as stdout.
That works, and I can see the output on a PC terminal connected to my target board, but the strange thing is: When I have only one printf on main, but before the main loop something is causing the processor to reset, while if I have a printf only inside the main loop or before the main loop AND inside the loop it works fine. Something like this:
#include <stdio.h>
/* stream definitions for UART input/output */
FILE uart_output = FDEV_SETUP_STREAM(uart_drv_send_byte, NULL, _FDEV_SETUP_WRITE);
FILE uart_input = FDEV_SETUP_STREAM(NULL, uart_drv_read_byte, _FDEV_SETUP_READ);
int main() {
/* Definition of stdout and stdin */
stdout = &uart_output;
stdin = &uart_input;
/* Configures Timer1 for generating a compare interrupt each 1ms (1kHz) */
timer_init()
/* UART initialization */
uart_drv_start(UBRRH_VALUE, UBRRL_VALUE, USE_2X, &PORTB, 2);
/* Sets the sleep mode to idle */
set_sleep_mode(SLEEP_MODE_IDLE);
printf("START ");
/* main loop */
while(1) {
printf("LOOP ");
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
sleep_mode();
}
}
The code above produces the following output:
START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
which is expected. If we comment the printf("START ") line it produces this:
LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
which is also fine. The problem is, if I don't have any printf inside the while loop, it goes like this:
START START START START START START ... START
That clearly shows the processor is being restarted, since the expected output would be just one START and nothing else while the infinite loop goes on being awaken only on the 1 kHz timer interrupts. Why is this happening? I should stress there's no watchdog timer configured (if there was, the cases where only LOOP is printed would be interrupted by a new START also).
Monitoring execution using GPIO pins
To try to get some insight into the situation, I turned GPIO pins ON and OFF around the problematic print("START ") and sleep_mode in the main loop:
int main() {
/* Irrelevant parts suppressed... */
GPIO1_ON;
printf("START ");
GPIO1_OFF;
/* Main loop */
while(1) {
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
GPIO2_ON;
sleep_mode();
GPIO2_OFF;
}
}
It turned out that GPIO1 stays ON for 132 µs (printf("START ") call time) and then OFF for 6.6 ms - roughly the time to transmit the six characters at 9600 bit/s - and GPIO2 toggles 12 times (six times two interrupts: the UART-ready-to-transmit interrupt and the UART-empty-data-register interrupt), showing sleep active for another 1.4 ms before GPIO1 goes ON again indicating a new printf("START ") - hence after reset. I'll probably have to check out the UART code, but I'm pretty sure the non-interrupt UART version also shows the same problem, and that doesn't explain either why having a printf inside the main loop works OK, without a reset happening (I would expect the reset would happen in any case should the UART code be faulty).
(SOLVED!): For completeness, The UART init and TX code is below**
This was my first attempt in writing an interrupt driven UART driver for the AVR, but one that could be used either on a RS-232 or a RS-485, which requires activating a TX_ENABLE pin while transmitting data. It turned out that, since I had to make the code useable either on ATmega328P or ATmega644, the interrupt vectors have different names, so I used a #define TX_VECTOR to assume the right name according to the processor used. In the process of making and testing the driver the choosing of "TX_VECTOR" for the UDRE data empty interrupt ended up masking the fact I hadn't defined the USART0_TX_vect yet (this was work in progress, I might not even need both anyway...)
Right now I just defined an empty interrupt service routine (ISR) for USART0_TX_vect and the thing doesn't reset anymore, showing #PeterGibson nailed it right on. Thanks a lot!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_VECTOR USART_RX_vect
#define TX_VECTOR USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_VECTOR USART0_RX_vect
#define TX_VECTOR USART0_UDRE_vect
#endif
ISR(TX_VECTOR)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
/* If RS-485 mode is on and the interrupt was called with TXC0 set it
* means transmission is over. TX_ENABLED should be cleared. */
if ((TX_ENABLE_PORT) && (UCSR0A & _BV(TXC0) & _BV(UDR0))) {
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
}
FIXED UART CODE (NOW WORKING 100%!)
In order to help anyone interested or developing a similar interrupt driven UART driver for the AVR ATmega, here it goes the code with the problems above fixed and tested. Thanks to everyone who helped me spot the problem with the missing ISR!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_BYTE_AVAILABLE USART_RX_vect
#define TX_FRAME_ENDED USART_TX_vect
#define TX_DATA_REGISTER_EMPTY USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_BYTE_AVAILABLE USART0_RX_vect
#define TX_FRAME_ENDED USART0_TX_vect
#define TX_DATA_REGISTER_EMPTY USART0_UDRE_vect
#endif
/* I/O port containing the pin to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t* TX_ENABLE_PORT = NULL;
/** Pin from the I/O port to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t TX_ENABLE_PIN = 0;
ISR(RX_BYTE_AVAILABLE)
{
// Read the status and RX registers.
uint8_t status = UCSR0A;
// Framing error - treat as EOF.
if (status & _BV(FE0)) {
/* TODO: increment statistics */
}
// Overrun or parity error.
if (status & (_BV(DOR0) | _BV(UPE0))) {
/* TODO: increment statistics */
}
ringbuffer_write_byte(&rxrb, UDR0);
}
ISR(TX_FRAME_ENDED)
{
/* The end of frame interrupt will be enabled only when in RS-485 mode, so
* there is no need to test, just turn off the TX_ENABLE pin */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
}
ISR(TX_DATA_REGISTER_EMPTY)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
cli();
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
sei();
}
void uart_drv_send_byte(uint8_t byte, FILE *stream)
{
if (byte == '\n') {
uart_drv_send_byte('\r', stream);
}
uint8_t sreg = SREG;
cli();
/* Write byte to the ring buffer, blocking while it is full */
while(ringbuffer_write_byte(&txrb, byte)) {
/* Enable interrupts to allow emptying a full buffer */
SREG = sreg;
_NOP();
sreg = SREG;
cli();
}
/* Enables empty data register interrupt */
UCSR0B |= _BV(UDRIE0);
SREG = sreg;
}
uint8_t uart_drv_read_byte(FILE *stream)
{
uint8_t byte;
uint8_t sreg = SREG;
cli();
ringbuffer_read_byte(&rxrb, &byte);
SREG = sreg;
return byte;
}
You've possibly enabled the UDRE (Uart Data Register Empty) interrupt and not set a vector for it, so when the interrupt triggers the processor resets (according to the defaults). When printf is called continuously in the main loop, this interrupt is never triggered.
From the docs
Catch-all interrupt vector
If an unexpected interrupt occurs (interrupt is enabled and no handler
is installed, which usually indicates a bug), then the default action
is to reset the device by jumping to the reset vector. You can
override this by supplying a function named BADISR_vect which should
be defined with ISR() as such. (The name BADISR_vect is actually an
alias for __vector_default. The latter must be used inside assembly
code in case is not included.)
I ran in the same situation right now, but since I don't have a high reputation on stackoverflow, I can not vote.
here is a snippet of my initialization procedure that caused this problem to me:
void USART_Init()
{
cli();
/* Set baud rate */
UBRR0H = (uint8_t)(BAUD_PRESCALE>>8);
UBRR0L = (uint8_t)BAUD_PRESCALE;
/* Enable receiver and transmitter */
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
/* Set frame format: 8data, 1stop bit 8N1 => 86uS for a byte*/
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
/*enable Rx and Tx Interrupts*/
UCSR0B |= (1 << RXCIE0) | (1 << TXCIE0); //<- this was the problem
/*initialize the RingBuffer*/
RingBuffer_Init(&RxBuffer);
sei();
}
The problem was that I initially used interrupt based transmission, but later on I have changed the design and went for 10ms polling for Tx sequence, and forgotten to change this line as well in the init procedure.
Thanks very much for pointing this out Peter Gibson.