Lecture 2 - (cont.) GPIO, Reset and Clock Control (RCC)

(for slides, see obsidian://open?vault=MySchoolNotes&file=CPE%20316%20Intro%20GPIO%20RCC%20STM32.pptx)

Today is very much a exercise-heavy day. We'll be primarily flashing a lot of software to verify how it work.

GPIO Revisited

The microcontroller we use is called the STM32_L476RGT6:

Recall the GPIO Registers (see [[rm0351-stm32l47xxx-stm32l48xxx-stm32l49xxx-and-stm32l4axxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf#page=303]], up to pp. 311).

So how do we turn a pin (say 15/14) into an input? We do:

// Pin A in GPIO struct ptr -> MODER (see above) (need to clear bits 15/14 via pg. 304 of the reference manual)
GPIOA->MODER &= ~(0x3 << 14); //equivalent to 0b0b1111111111111111001111111111111111
// A port is set to an input
GPIOA->PUPDR = (0x01) << 14; // set A to the same value

Notice here that &= maintains the previous values, while the strict = just clears all the data and writes what we want. We could rewrite line 2 as:

GPIOA->PUPDR &= ~(0x3 << 14); // clear the two bits in 14/15 (no overwrite)
GPIOA->PUPDR |= ~(0x1 << 15); // then set the values in bits 14/15 to our wants

Note that a lot of the #include macros should do this logic for you. We'll see more of this type of implementation in a bit.

Next thing we'd want is to try to probably read the input data register (IDR, see [[rm0351-stm32l47xxx-stm32l48xxx-stm32l49xxx-and-stm32l4axxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf#page=306]]):

if (GPIOA->IDR & (1 << 7))  // if the input is high (like a button)
{
	// pressed
}
else 
{
	// not pressed
}

You could image we just poll our button presses and respond when that happens:

while(1)
{
	if(buttonPressed())
	{
		doPressedEvent();
	}
	else 
	{
		doNonPressedEvent();
	}
}

See the example below:

#include "main.h"

int main(void)
{
	// turns on clock to GPIO bankks A and C
	RCC->AHB2ENR |= (RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOCEN);

	// bank A as GPIO mode
	GPIOA->MODER &= ~(GPIO_MODER_MODE5);
	GPIOA->MODER |= (GPIO_MODER_MODE5_0);

	// bank C as GPIO mode
	GPIOC->MODER &= ~(GPIO_MODER_MODE13);
	GPIOC->PUPDR &= ~(GPIO_PUPDR_PUPD13);

	while(1) // poll
	{
		if (GPIOC->IDR & GPIO_PIN_13) // if pin 13 of port C != 0
		{
		GPIOA->ODR &= ~GPIO_PIN_5; // set pin 5 of PORT A to 0
		}
		else
		{
		GPIOA->ODR |= GPIO_PIN_5; // set pin 5 of PORT A to 1
		}
	}
}

Worksheet / Board Examples 1

  1. Set pin PA5 as an output
  2. Make PA5 go high
  3. Wait some amount of time (HAL_Delay(1000);)

#include "main.h"
int main(void)
{
	// 1) set pin PA5 as an output
	GPIOA->MODER &= ~(0x03 << 10); // clear just our PA5_MODER data
	GPIOA->MODER |= 0x01 << 10; // output mode is 0x01, so input that

	// 2) Make PA4 go high
	GPIOA->ODR &= ~(0x1 << 5); // clear just our PA5_ODR data (is optional)
	GPIOA->ODR |= 0x1 << 5; // input our data.
	// we could, alternatively, do the following:
	GPIOA->BSRR = (0x1 << 5);
	
	// 3) Wait some time
	HAL_Delay(1000); // arguments of ms
}

Reset and Clock Control (RCC)

Often in embedded systems, we really want to have our power usage be lower, since:

Thus, we may actually want to lower the clock frequency when possible, as Pf2.

The STM32L476 has lots of different clocks. In the chip of the microcontroller there's:

One to be familiar with is the PCLK (peripheral clock). This goes to all GPIO elements (hence the name). Default state is MSI = 4MHz for your SYSCLK, and all peripherals (GPIO, SPI, ...) have their clocks turned off.

One thing we've already seen is the ability to turn on the clock to GPIOA and GPIOC:

// turns on clock to GPIO Banks A and C
RCC->AHB2ENR |= (RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOCEN);