Code for increasing clock speed of STM32F401 not working - arm

I was trying to increase the clock speed of STM32F401 to 84Mhz using PLL. I tried for log time. But the code is not working. Can anyone check what needs to be done? External crystals is functional , because I checked that using HAL library.
The reason I am trying without HAL is that I've seen several document saying that the HAL consumes too much memory and it is better to avoid it.
void Enable_PLL_F401(void)
{
//Enable The HSE
RCC->CR |= RCC_CR_HSEON;
//Wait Until HSE Stabilizes
//Remove While Loop; If the HSE isn't stabilized, code will stuck
while(!(RCC->CR & RCC_CR_HSERDY))
;
/* Activate Prefetch Buffer*/
/* Optional */
//Configure the PLL registers
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC; //PLL Source HSE
RCC->PLLCFGR &=~ (RCC_PLLCFGR_PLLM); //Clearing PLLM values
RCC->PLLCFGR |= (RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLM_4); //Divided by 25
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLN; //Clearing PLLM values
RCC->PLLCFGR |= (RCC_PLLCFGR_PLLN_4|RCC_PLLCFGR_PLLN_6|RCC_PLLCFGR_PLLN_8); //Multiplied by 336
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLP; //Clearing PLLP values
RCC->PLLCFGR |= RCC_PLLCFGR_PLLP_0; //Divided by 4
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLQ; //Clearing PLLP values
RCC->PLLCFGR |= RCC_PLLCFGR_PLLQ_2; //Divided by 4 [USB-OTG Clock]
RCC->CFGR &=~ RCC_CFGR_HPRE; //System Clock Not Divided [AHB Prescalar]
RCC->CFGR &=~ RCC_CFGR_PPRE1_DIV16; //Clearing PPRE1 values
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
RCC->CFGR &=~ RCC_CFGR_PPRE2_DIV16; //Clearing PPRE2 Values
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; //AHB not divided [APB2 Prescalar]
RCC->CR |= RCC_CR_PLLON;
//Wait Until HSE Stabilizes
//Remove While Loop; If the HSE isn't stabilized, code will stuck
while(!(RCC->CR & RCC_CR_PLLRDY))
;
RCC->CFGR &=~ RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while(!(RCC->CFGR & RCC_CFGR_SWS_PLL))
;
//SystemInit();
SystemCoreClockUpdate();
}
This is the clock configuration in CubeMX:

Although the question is still missing some information, let me try to give you some pointers:
Writing to MCU registers
First of all, writing to the PLLCFGR register in many small steps is generally not the way to go. See the following piece of your code:
//Configure the PLL registers
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC; //PLL Source HSE
RCC->PLLCFGR &=~ (RCC_PLLCFGR_PLLM); //Clearing PLLM values
RCC->PLLCFGR |= (RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLM_4); //Divided by 25
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLN; //Clearing PLLM values
RCC->PLLCFGR |= (RCC_PLLCFGR_PLLN_4|RCC_PLLCFGR_PLLN_6|RCC_PLLCFGR_PLLN_8); //Multiplied by 336
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLP; //Clearing PLLP values
RCC->PLLCFGR |= RCC_PLLCFGR_PLLP_0; //Divided by 4
RCC->PLLCFGR &=~ RCC_PLLCFGR_PLLQ; //Clearing PLLP values
RCC->PLLCFGR |= RCC_PLLCFGR_PLLQ_2; //Divided by 4 [USB-OTG Clock]
This performs 9 writes to the same register. In the first write, you select the HSE oscillator clock. In the second write, you clear the division factor for the main PLL (PLLM). However, the value 0 is an invalid value. This might already cause issues (I don't think the behavior in this case is specified, so we can only guess what will happen).
This can be easily avoid by first calculating the correct value and then writing it to the register in a single operation like this:
uint32_t reg = RCC->PLLCFGR;
//Configure the PLL registers
reg |= RCC_PLLCFGR_PLLSRC; //PLL Source HSE
reg &=~ (RCC_PLLCFGR_PLLM); //Clearing PLLM values
reg |= (RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLM_4); //Divided by 25
reg &=~ RCC_PLLCFGR_PLLN; //Clearing PLLM values
reg |= (RCC_PLLCFGR_PLLN_4|RCC_PLLCFGR_PLLN_6|RCC_PLLCFGR_PLLN_8); //Multiplied by 336
reg &=~ RCC_PLLCFGR_PLLP; //Clearing PLLP values
reg |= RCC_PLLCFGR_PLLP_0; //Divided by 4
reg &=~ RCC_PLLCFGR_PLLQ; //Clearing PLLP values
reg |= RCC_PLLCFGR_PLLQ_2; //Divided by 4 [USB-OTG Clock]
RCC->PLLCFGR = reg;
The same applies as well to other registers in your code.
Dynamically reconfiguring clocks
It's not entirely clear to me if you want to increase the clock speed of the MCU dynamically (so when it's already running) or statically (so changing the initial configuration).
In case you want to change the clock speed dynamically, there a few additional things to consider:
The Main PLL configuration cannot be changed once the PLL is enabled. So if you want to change the clock speed, you will first have to disable it.
You have to make sure that there is at least one valid system clock at all times. If you're reconfiguring the Main PLL, you will have to temporarily switch to the internal clock (HSI).
Since I don't know your startup clock configuration, I can't go into more details, but I hope this will provide enough information.
Flash wait states
As mentioned by old_timer and Tagli, you might also need to adjust the flash wait states.
This is described in section 3.4.1 "Relation between CPU clock frequency and Flash memory read time" of the STM32F401 Reference Manual (RM0368) (revision 5). The required value depends on your supply voltage and CPU clock frequency.
Power Regulator Voltage
To be able to achieve 84 MHz on the STM32F4, the regulator voltage scaling output must be set to Scale 2 (VOS = 2 in the PWR_CR register). This is the default after reset, but if your initial configuration was different, you may have to set it again.
NB: Regarding your statement "The reason I am trying without HAL is that I've seen several document saying that the HAL consumes too much memory and it is better to avoid it."... There's a valid point in that statement, but it's not a general rule that you should follow. For low-level stuff like clock configuration, the HAL code is pretty basic and won't cost that much memory. And given the complexity of it, I wouldn't bother rewriting this, unless I really needed to save every byte of memory. The high-level HAL functions, e.g. for UART or I2C, are pretty big, since they try to cover many use cases, and you might save quite some memory by writing only the code you need. Still, I'd start with the HAL functions and only optimize when needed.

Related

STM32L011K4 with DMA using I2C Start Condition not Occurring

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.

STM32F407 Register Level Clock Configuration Issue

I am working on an STM32F407 Discovery Board. But I didn't solve my clock configuration problem. I want to 168 MHz working frequency and I get help from CubeMX Clock Configuration Manager. And this is my PLLCFGR Register value from CubeMX: 0x4405408. (I have problem with this register). Then I was copy paste all RCC registers to my CMSIS code. This is my clock configuration code:
RCC->CFGR = 0x4008940A; //MCO2 Source is PLLI2S (4), HSE Divided by 8 for RTC (8), APB2 Divided by 2 for 84Mhz, APB1 Divided by 4 for 42 Mhz
RCC->CR = 0x0F0B6783; //PLL, PLLI2S, HSE, CSS ON
RCC->PLLCFGR = 0x04405408; //PLLQ 4 (4), PLLSRC = HSE (4), PLLP 2 (0), PLLN 336 (54), PLLM 8 (8)
RCC->PLLI2SCFGR = 0x50003C00; //PLLI2SR 5 (5), PLLI2SN 240 (3C)
But I can't read the same values from SFRs menu in Atollic. All registers are correct but the PLLCFGR register value is 0x04405419. This issue effects my clock speed, peripheral speed etc. I want to set PLLM bits to 8 but I read 19. So I get less speed then I want, because PLLM bits decide PLL input clock division. How can I solve this register problem?
When doing the clock configuration, you can't just load the registers with their final values. You need to follow some logical order and satisfy the limits mentioned in the reference manual. You must also consider the flash memory wait states.
Here is an example code to run STM32F407 at 48 MHz, using 8 MHz HSE. I feel lazy to modify & test it for 168 MHz, but this should give you the idea and a starting point.
FLASH->ACR |= FLASH_ACR_LATENCY_1WS; // 1 wait state for 48 MHz
RCC->CR |= RCC_CR_HSEON; // Activate external clock (HSE: 8 MHz)
while ((RCC->CR & RCC_CR_HSERDY) == 0); // Wait until HSE is ready
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLQ;
RCC->PLLCFGR |= 4 << RCC_PLLCFGR_PLLQ_Pos; // PLL-Q: /4
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // PLL source is HSE
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLP; // PLL-P: /2
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLN;
RCC->PLLCFGR |= 96 << RCC_PLLCFGR_PLLN_Pos; // PLL-N: x96
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLM;
RCC->PLLCFGR |= 4 << RCC_PLLCFGR_PLLM_Pos; // PLL-M: /4
RCC->CR |= RCC_CR_PLLON; // Activate the PLL (Output: 96 MHz)
while ((RCC->CR & RCC_CR_PLLRDY) == 0); // Wait until PLL is ready
RCC->CFGR |= RCC_CFGR_HPRE_DIV2 // AHB divider: /2 (48 MHz)
| RCC_CFGR_PPRE1_DIV2 // APB1 divider: /2 (24 MHz)
| RCC_CFGR_PPRE2_DIV2; // APB2 divider: /2 (24 MHz)
RCC->CFGR |= RCC_CFGR_SW_PLL; // Switching to PLL clock source
I found my Issue where did came from. system_stm32f4xx.c and stm32f4xx.h files need changes for Stm32f4 Discovery board. This codes writing for 25Mhz HSE crystal but Discovery Board has 8Mhz HSE crystal. Then PLL bits. These bits defined in system_stm32f4xx.c file. You must be change PLL bits as you want. Thanks all of us;)

How to get a PWM signal on port D6 (PE_9) on a STM board?

I'm programming a board and want a PWM signal to appear on a pin to drive a LED. I'm using a STM32 NUCLEO-F207ZG board, and only low-level register programming. It is not working.
I've looked into the manual, datasheet and application note. Also some Google searches have been done.
//Enable timer 1 clock:
RCC->APB2ENR |= BIT0;
//Output mode on PWM
TIM1->CCMR1 |= BIT5 | BIT6;
//Period:
TIM1->ARR = 0x0000FFFF;
//Duty cycle:
TIM1->CCR1 = 0x00007FFF;
//Enable preload
TIM1->CCMR1 |= BIT3;
TIM1->CR1 |= BIT7;
//Enable CC1 output
TIM1->CCER |= BIT0;
//Enable timer
TIM1->CR1 |= BIT0;
//Enable GPIOE clock
RCC->AHB1ENR |= BIT4;
//Alternate function mode voor pin PE_9
GPIOE->MODER |= BIT19;
GPIOE->AFR[1] |= BIT4;
I expect a PWM signal on pin D6 (PE_9), to drive a LED. But the LED doesn't seem to do anything.
I didn't check your code bit by bit, but it seems OK in general. But I suspect a possible cause of the problem: Normally you should wait a few clock cycles before accessing any peripheral just after enabling its clock. I may be wrong, but it's possible that these 2 lines of code are ignored by the peripherals, as they are executed just after enabling the clocks:
TIM1->CCMR1 |= BIT5 | BIT6;
GPIOE->MODER |= BIT19;
I suggest using a debugger to check if all the peripheral registers are loaded with the correct values.
I also suggest trying other PWM channels. There may be some conflicts with the pin you're using because of the board hardware configuration.

Data Not Being Stored in Receive Data Register, (UART RXNE Flag not setting)

I have been programming the stm32l412kb nucleo board, attempting to achieve basic UART communication. Transmission from the board works great but the board is not appearing to receive any data.
For the software side, I have tried using standard HAL code in a few ways different, in both interrupt and non-interrupt mode. I have tied a more basic approach (shown below). From debugging line by line I have found that the Receive Data register (RDR) is not filling (and consequently the flag which sets when there is data there is not setting). This has been the error in each case.
This aim of this code is to send back the character entered.
#include "stm32l4xx.h"
int main(void)
{
/* USER CODE BEGIN 1 */
/*The Usart2 peripheral needs its clock to be enabled.*/
RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
/*The 72 MHz APB1 bus clock with a 9600baud rate gives a baud rate for the register of 0x1D4C*/
USART2->BRR = 0x1D4C;
/*For USART2 we need to enable the overall UART (U) driver, the transmission lines(T) and the reading lines(R). UART Enable is last.*/
USART2->CR1 |= USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;
/*Setting transmission pin*/
GPIOA->MODER |= GPIO_MODE_AF_PP;
GPIOA->OSPEEDR |= GPIO_SPEED_FREQ_HIGH;
/* USER CODE END 1 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (USART2->ISR & USART_ISR_RXNE) //if RX is not empty
{
char temp = USART2->RDR; //fetch the data received
USART2->TDR = temp; //send it back out
while (!(USART2->ISR & USART_ISR_TC)); //wait for TX to be complete
}
}
return 0;
}
To send the data I have used RealTerm Serial Capture and have tried also the stm32cubeIDE console. One possibility of the source of the problem is that the DataSheet says
"In the USART, the start bit is detected when a specific sequence of samples is recognized. This sequence is: 1 1 1 0 X 0 X 0 X 0 0 0 0."
I have not coded any way of leading my data with this, however, from all the examples I have seen from a couple of books as well as videos, they did not need to think about this and it worked perfectly. Could it be a hardware problem? Is there something I'm not initialising? I have even tried different cables.
Many thanks in advance for any help,
Harry
/*********************************UPDATE**************************************/
First and foremost, thank you very much for the help, I now understand basics such as how to use the datasheet to configure the registers. It is much appreciated. I have updated my code but still the problem remains.
So I have updated my configuration as so:
/*Configuring GPIO Pins*/
/*Clearing whatever is held in the mode registers for pins 2 and 3 (Inverting with their masks.)*/
GPIOA -> MODER &= ~(GPIO_MODER_MODE2_Msk | GPIO_MODER_MODE3_Msk);
/*The 2 bits 10 are being shifted to the position which configures Mode of pin 2 and also for pin 3 in the mode register.
*(10 is alterntive function mode).*/
GPIOA -> MODER |= (0b10 << GPIO_MODER_MODE2_Pos) | (0b10 << GPIO_MODER_MODE3_Pos);
/*Clearing whatever is held in the output speed registers for pins 2 and 3*/
GPIOA -> OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED2_Msk | GPIO_OSPEEDR_OSPEED3_Msk);
/*Setting the speed of pins 2 and 3 to be very high(11)*/
GPIOA -> OSPEEDR |= (0b11 << GPIO_OSPEEDR_OSPEED2_Pos) | (0b11 << GPIO_OSPEEDR_OSPEED3_Pos);
/*Clearing whatever is held in the alternative function registers for pins 2 and 3.*/
GPIOA -> AFR[0] &= ~(GPIO_AFRL_AFSEL2_Msk | GPIO_AFRL_AFSEL3_Msk);
/*Setting the pins 2 and 3 to their alternative functions(TX and RX)*/
GPIOA -> AFR[0] |= (7 << GPIO_AFRL_AFSEL2_Pos) | (7 << GPIO_AFRL_AFSEL3_Pos);
/*Clock Configuration*/
/*Enabling the USART2 peripheral clock.*/
RCC->APB1ENR1 &= ~(RCC_APB1ENR1_USART2EN_Msk);
RCC->APB1ENR1 |= (0b1 << RCC_APB1ENR1_USART2EN_Pos);
/*Enabling the GPIOA port peripheral clock*/
RCC->AHB2ENR &= ~(RCC_AHB2ENR_GPIOAEN_Msk);
RCC->AHB2ENR |= (0b1 << RCC_AHB2ENR_GPIOAEN_Pos);
/*USART Configuartion*/
/*The 72 MHz APB1 bus clock with a 9600baud rate gives a baud rate for the register of 0x1D4C*/
USART2->BRR = 0x1D4C;
/*For USART2 we need to enable the overall UART (U) driver, the transmission lines(T) and the reading lines(R). UART Enable is last.*/
USART2->CR1 &= ~(USART_CR1_RE_Msk | USART_CR1_TE_Msk | USART_CR1_UE_Msk);
USART2->CR1 |= USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;
Which has greatly developed my understanding of how to properly configure the device. However, I'm still having a problem with the overall aim of the code to bounce back a character, as the data is still not being read by the MCU. I will pursue on and update if it's successful. I'm thankful for any further suggestions.
this does not initialize the GPIO MODER or OPEEDR regiters.
GPIOA->MODER |= GPIO_MODE_AF_PP;
GPIOA->OSPEEDR |= GPIO_SPEED_FREQ_HIGH;
GPIO_MODE_AF_PP & GPIO_SPEED_FREQ_HIGH are HAL definitions and cant be used on the register level.
You need to set the appropriate values for every pin you use:
It will never receive or send anything as you forgot to set the GPIO -> AF registers and the hardware is not connected to the pins internally.
You can find the alternate functions mappin in the Datasheet
and the AF GPIO registers in the Reference Manual
this sequence should be:
GPIOA -> MODER &= ~(GPIO_MODER_MODE2_Msk | GPIO_MODER_MODE3_Msk);
GPIOA -> MODER |= (0b10 << GPIO_MODER_MODE2_Pos) | (0b10 << GPIO_MODER_MODE3_Pos);
GPIOA -> OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED2_Msk | GPIO_OSPEEDR_OSPEED23Msk);
GPIOA -> OSPEEDR |= (0b11 << GPIO_OSPEEDR_OSPEED2_Pos) | (0b11 << GPIO_OSPEEDR_OSPEED3_Pos);
GPIOA -> AFR[0] &= ~(GPIO_AFRL_AFSEL2_Msk | GPIO_AFRL_AFSEL3_Msk);
GPIOA -> AFR[0] |= (7 << GPIO_AFRL_AFSEL2_Pos) | (7 << GPIO_AFRL_AFSEL3_Pos);

STM32F030 PWM Setup Issues

I'm trying to get PWM functioning on two pins of my STM32030R8T6, it's on a Nucleo development board and I'm using Keil. For learning, I've mostly been following the material on this website, but with adaptations as that site uses a different MCU. There really isn't much to setting up the PWM so I'm not quite sure what I've done wrong, I know the timer is working because the on-board LED blinks 1.5 times per second, but when I monitor the Ch1 and Ch2 output pins with my scope I get nothing. I'm pretty sure the pins are correctly set in Alternate Function Push-Pull because they're set the same as the MCO pin which is functioning and showing 24 MHz (Though my cheap scope has some problems determining that...). I've attached all of my relevant and even remotely possibly relevant code. And for your convenience:
UM0360 Reference Manual (STM32F030...)
I'd post links to the Nucleo User Manual and Device Datasheet as well but I can't post more than two links, since this is my first question and my reputation is less than ten.
Any help on what I might be doing wrong is appreciated, I'm sure it's something stupid.
#include "stm32f0xx.h"
void Initializations(void);
int main(void)
{
Initializations();
while(1)
{
/* Toggle onboard LED whenever timer overflows */
if((TIM3->SR & TIM_SR_UIF))
{
TIM3->SR &= ~TIM_SR_UIF;
GPIOA->ODR ^= GPIO_ODR_5;
}
}
}
void Initializations(void)
{
/* CLK CONFIG */
RCC->CFGR |= RCC_CFGR_HPRE_DIV2 |
RCC_CFGR_PPRE_DIV16 |
RCC_CFGR_MCO_SYSCLK |
RCC_CFGR_PLLMUL6;
/* Activate PLL, wait */
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* Enable IO CLKs */
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
/* Enable peripheral CLKs */
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
/* PIN INITIALIZATIONS */
GPIOA->MODER |= GPIO_MODER_MODER5_0 | // Onboard LED (General output)
GPIO_MODER_MODER2_1 | // USART2 TX (Alternate function)
GPIO_MODER_MODER3_1 | // USART2 RX (Alternate function)
GPIO_MODER_MODER6_1 | // TIM3 CH1 (Alternate function)
GPIO_MODER_MODER7_1 | // TIM3 CH2 (Alternate function)
GPIO_MODER_MODER8_1 | // MCO (Alternate function)
GPIO_MODER_MODER9_1 | // USART1 TX (Alternate function)
GPIO_MODER_MODER10_1; // USART1 RX (Alternate function)
/* TIMER INITS */
TIM3->PSC = 7;
TIM3->ARR = 59999;
/* CCM1 */
TIM3->CCMR1 |= TIM_CCMR1_OC1M_0 |
TIM_CCMR1_OC1M_1;
TIM3->CCR1 |= 4499;
TIM3->CCER |= TIM_CCER_CC1E; // Enable Ch1
/* CCM2 */
TIM3->CCMR1 |= TIM_CCMR1_OC2M_0 |
TIM_CCMR1_OC2M_1;
TIM3->CCR2 |= 29999;
TIM3->CCER |= TIM_CCER_CC2E; // Enable Ch2
TIM3->CR1 |= TIM_CR1_CEN; // Enable TIM3
/* USART INITS */
RCC->CFGR3 |= RCC_CFGR3_USART1SW_0; // Clock USART1 from SYSCLK
}
In addition to setting the pin to use an alternate function, you must also set which alternate function to use.
This is described in section 8.3.2 (pdf page 128) of the document you linked.
These are the AFRL (for pins 0-7) and AFRH (for pins 8-15) registers on the port.
For example, based on your code, and if TIM3 uses alternate function 2 and is on pins 6 and 7, (and assuming the alternate code was currently 0) you'd do
GPIOA->AFRL |= (2 << (6 * 4)) | (2 << (7 * 4));
If it isn't 0 or you want to be sure, mask off the bits first (each pin gets 4 bits).
(Note, your header may name registers differently than mine, and your alternate functions may also be different; I usually work with STM32F407 or STM32F334. To find the table of alternate functions to see which one you need, you'll have to look that up in the datasheet for the particular chip you are using, as opposed to the family reference manual which you linked above)
The more general form is
mode << (pin * 4)
for AFRL and
mode << ((pin - 8) * 4)
for AFRH.

Resources