C代写-ECE381
时间:2022-01-27
ECE381 – Lab 1 – GPIO
Objectives:
○ Learn how to poll a GPIO pin
○ Learn the structure of GPIO pins
○ Learn how to debounce a switch
○ Learn how microcontrollers interface with peripherals though memory mapping
Introduction:
In Lab00, you learned a bit about how to set up projects in CubeIDE, as well as the very basics of
output on the STM32 using the standard, blink LED, microcontroller “Hello World!” program. In this
lab, you will learn how to read a digital input through the process of polling. Polling is continually
looping to check a pin’s (or pins’) value(s) to detect a change. In Part 1, you will have to implement a
true LED toggle using a polling loop. In Part2, you will repeat the lab, but use a lower-level API that
requires understanding bit masking and register interaction and GPIO structure.
Part 1 - Polling a Switch using the Hardware Abstraction Layer
Most microcontroller manufacturers include a set of high-level functions that abstract away a lot of the
low-level, necessary details required to get peripherals (GPIO, Timers, PWMs, ADCs, etc.) on a
microcontroller to function correctly. This set of functions is commonly referred to as a Hardware
Abstraction Layer (or HAL for short), and allows for cleaner, more readable, portable (including being
able to change the entire STM32 part without modifying one line of code!) and reliable code. Our
STM32 projects generated using CubeIDE with the default parameters automatically generate this HAL
for us, and the function HAL_GPIO_Toggle() from Lab00. What this HAL is really doing is modifying
a bunch of bits in registers, as you will see in Part 2.

Thus, for Part 1, we will strictly focus on polling. Polling is a loop that is constantly checking to see
whether a pin (or group of pins) is a certain value. Since pins connect to external devices, when the
value of the pin(s) changes due to external input, the loop will break or enter, and thus, you will know
that a certain event happened, like a button press as we will do here.
Part 1 - CONFIGURATION
● Connect one push button to PD2 and one to PA15.
● Attach an external pull down resistor (10K is fine) or configure the pins to use the built in
pull-down resistors for PD2 and PA15
Part 1 - REQUIREMENTS
● Write a polling loop that detects when either PD2 or PA15 is pressed
● Pressing PD2 should toggle the Red LED on PC6
● Pressing PA15 should toggle the Blue LED on PC7
● Debounce both buttons in code
Updated: Jan 12, 2022 1
Part 2 - Polling a Switch using Memory Mapped Peripherals
In microcontrollers, one of the most commonly used peripherals is General Purpose Input and Output
(or GPIO for short). GPIO allows you to wait for all sorts of user input events (like button presses here)
OR control various external hardware (like LEDs here). GPIO typically works using some kind of
push/pull chain on the output (like a CMOS output chain using PMOS for sourcing current and NMOS
for sinking current) or using some kind of high-impedance buffer on the input. The “drive mode” of the
pins (whether they are inputs or outputs), their current state, what to output, how to do interrupts, etc.
are all controlled through some kind of memory-mapped configuration register(s). Memory-mapped
registers are very commonly used in microcontrollers to control all kinds of peripherals. Peripherals are
basically any custom hardware, specific to the microcontroller, that would be different from just a CPU
or Memory (ROM or RAM). These could include GPIO (like this lab), Timers, UARTs, I2C, SPI, etc.
No matter the peripheral, it is highly likely that controlling them requires the utilization of one or more
registers that are mapped to specific locations in memory. Bits in these registers will control the
functionality of the peripheral, as they are typically routed to the control lines in the peripheral
hardware. They give you the chance to modify them in your program to change the behavior as
necessary.

For GPIO on the STM32, the key memory mapped registers are the GPIOx_MODER (Mode Register)
register to control the drive mode, the GPIOx_ODR (Output Data Register) register to change the
output pins, and the GPIOx_IDR (Input Data Register) register to read the current digital value of the
pins. There are a few other registers which control some esoteric things like drive strength
(GPIOx_OTYPER), speed (GPIOx_OSPEEDR), internal pull-up/down resistors (GPIOx_PUPDR), but
for pure input/output aren’t required. Now, for example, let’s say we wanted to blink the LED like we
did in Lab 0, Part 1. We would need to programmatically make the pin an output, what was
automatically done in the STM32 Configuration Window of CubeIDE, and we would also need have to
have a way to write 1s and 0s to the pin, with each “1” corresponding to a voltage output of VDD and
each “0” corresponding to a voltage output of roughly GND.

First, we would need to know the locations of the drive-mode bits in the GPIOx_MODER registers.
The STM32F0x2 Reference Manual tells us on pg. 163 shows the GPIO register map. Since the red
LED is on PC6, we would need to know the location of the GPIOC_MODER. The GPIO pins are on
the AHB2 bus. All peripherals are mapped starting at address range 0x40000000. Peripherals on the
AHB2 bus have an additional offset of 0x08000000, so the first peripheral register address on AHB2
would be 0x48000000, and is actually GPIOA. Since we want GPIOC, the reference manual tells us
that the GPIOC offset is 0x00000800, so the first register in the GPIOC bank is located at 0x48000800
(0x40000000 + 0x08000000+0x00000800). The register map on pg 163 says the GPIOx_MODER
registers have an offset of 0x00, so this register at 0x48000800 is the GPIOC_MODER register!
Going to pg. 157 tells us the details for the GPIOx_MODER, and they are as follows. Each of the 16
pins on PortC has 4 modes, and thus every 2 bits in the MODER register controls the drive mode for
each pin (16 pins on the port, 2 bits per mode, gives 32 bits!). The four drive modes using these two
bits are 00 for input, 01 for output, 10 for Alternate Function (ie, the pin is mapped to other peripheral
inputs/outputs), or 11 for Analog (pin gets routed to analog system). Since we would want to toggle
PC6 for the red LED, we need to find the two-bits for pin 6, and these are bits 13 and 12 in the
Updated: Jan 12, 2022 2
GPIOx_MODER, corresponding to MODER6[1:0]. To make it an output, we need to set bit 12 in the
GPIOC_MODER at memory address 0x48000800 to 1, and bit 13 of the same register to be 0.
To accomplish this in code, you would use another common feature in low-level programming called a
“bitmask”, which is a constant value defined to be 1 at the position(s) of the bit(s) you are controlling,
and 0 for all other bits. If you want to “set” a bit, you would do a bitwise OR (the | operator in C) of
the register with this mask. Since anything OR with 0 stays the same, but anything OR 1 is always 1,
this leaves the other bits alone while setting the one you care about. If you want to clear a bit, you can
simply bitwise AND (the & operator in C) the bitwise inverse (~ in C) of the mask, since anything AND
1 stays the same, but anything AND 0 is always 0! So the bitmask for bit12 of a 32-bit register looks
like this:
uint32_t GPIOC_MODER_MODER6_L_MASK = 0x00001000UL;
The 0x indicates it is a hexadecimal number in C, the 1 is in the 12th bit position, and the UL tells the
compiler that it is an unsigned long. Similarly, the mask for the 13th bit would be:
uint32_t GPIOC_MODER_MODER6_H_MASK = 0x00002000UL;
The last piece of the puzzle would be how to access the specific memory address 0x48000800 in your
C code. C allows you to do this though pointers. First, you have to know the width of the
memory-mapped register you are accessing. On the STM32F0, the GPIOx registers are all 32-bit (4
byte) registers, so we would use a uint32_t * type (NOTE: The actual length of unsigned long
varies among different CPU architectures. The uint32_t is defined to always be 32-bits on any
architecture. In the case of the STM32F0, this is a typedef for unsigned long, since unsigned
long IS 32-bits on the ARM Cortex-M0, but using uint32_t is more portable in case the code would
have to be ported to a different CPU). This now is a pointer to a 4 byte (32-bit) data element. So, you
just need to define where the 4 byte data element is in memory, and you do so with the following:
volatile uint32_t * GPIOC_MODER = (uint32_t *)0x48000800UL;
The 0x48000800 is the correct address of the register, and the (uint32_t *) in front tells the
compiler it is the location of a 32-bit data element. The same uint32_t * in front of the variable
name (GPIOC_MODER) matches the type, and thus all interpretations of that variable are assumed to
be 32-bit unsigned, including dereference operations to modify the value. Thus, to modify the register,
we would just dereference this pointer like this example below, which sets PC6 to output by directly
setting bits 13:12 to 01 (and all others to 0).
*GPIOC_MODER = 0x00001000UL;
THIS IS REALLY BAD PRACTICE THOUGH!!! DON’T DO THIS FOR REAL!!! YOU REALLY
SHOULD USE A MASK FOR THIS!!! Why? Because it sets ALL OTHER PINS TO INPUTS,
regardless of whether that’s what you wanted to do. The correct way is to mask, so that looks like this:
Updated: Jan 12, 2022 3
*GPIOC_MODER |= GPIOC_MODER_MODER6_L_MASK;
*GPIOC_MODER &= ~GPIOC_MODER_MODER6_H_MASK;
The first line sets bit 12 to 1 by ORing it with 1, while leaving all other bits alone as they are 0 in the
mask, and anything OR 0 stays the same. The |= syntax is just a shorter way of writing
*GPIOC_MODER = *GPIOC_MODER | GPIOC_MODER_MODER6_L_MASK. The second line clears bit
13, since, by inverting the mask, all the 0s become 1s, and anything AND 1 stays the same, but bit 13
was 1 in the original mask, so inverting makes it 0, and anything AND 0 is 0. The syntax is a little
strange, but in C, the * in front of a pointer name is the dereference operator, and gives you access to
the content where the pointer points. What about that volatile? What does that mean? Well, it basically
exists to tell the compiler not to mess with that variable. Compilers often do things to optimize your
code (which is usually a good thing), but in the case of registers like these, optimizations can be bad
and lead to unintended behavior. For example, suppose you wanted to make a pulse on your LED. You
would write a 1 to bit 6 of the GPIOC_ODR register, followed by a 0 to bit 6 of the same GPIOC_ODR
register. The compiler might look at that, decide that since the final value is 0, it wouldn’t need to
compile in the instructions to change it to 1 in first place as an optimization! Your code would be faster
(and smaller), but you wouldn’t get your pulse! The volatile keyword tells the compiler to just forget it,
your intent is to do both writes, so don’t optimize!
We are almost done with replicating Lab0, Part1 using only registers. The last bit would be to toggle
the output LED. One usual way would be to read the value of the pin, and check if the current value is
1 or 0. Doing so requires masking the correct bits in the GPIOC_IDR (Input Data Register), as that
register contains the current value on the pins, regardless of if the pin is an input or output (or AF or
analog). So, we need another pointer to that register, which is at offset 0x10 so:
volatile uint32_t * GPIOC_IDR = (uint32_t *)0x48000810UL;
gets us there. Next, to check the value of the pin 6, we need a mask which is 1 for pin 6 and zero
elsewhere:
uint32_t P6_MASK = 0x00000040UL;
Finally, to read the register is simply a matter of dereferencing it again, but since we only want to
check pin 6, we use our mask to do so:
*GPIOC_IDR & P6_MASK
Now, if the result of the bitwise AND of the mask and the reister is 0, that means pin 6 currently has the
digital value of 0 (the red LED is off). If the current value of pin 6 is 1 (red LED on), then the result of
the bitwise AND would be the same as the mask itself! So the toggle logic looks like this:
if (*GPIOC_IDR & P6_MASK){
// LED is ON
}
else {
Updated: Jan 12, 2022 4
// LED is OFF
}
If the LED is currently on, the result of the AND is non-zero (the mask itself actually) and therefore
you enter that block. If the mask is off, the result of the AND will be 0, and thus you enter the second
block. An alternate way to write this with the logic flipped would be:
if ((*GPIOC_IDR & P6_MASK) == 0){
// LED is off
}
else {
// LED is on
}
In either case, for a toggle, you want to turn the LED off if it was on, and on if it was off. This requires
using PC6 as an output, and thus using the GPIOC_ODR (Output Data Register). Just like we did with
the GPIOC_MODER, we’ll use bitmasking of that register so, make sure it is defined (Ref. Man pg.
159 says the offset is 0x14):
volatile uint32_t * GPIOC_ODR = (uint32_t *)0x48000814UL;
and now finally the toggle code:
if (*GPIOC_IDR & P6_MASK){
// LED is ON, so turn it off
*GPIOC_ODR &= ~P6_MASK;
}
else {
// LED is OFF, so turn it on
*GPIOC_ODR |= P6_MASK;
}
Throw in a delay loop, and you are done! Well, almost. All of these modifications are on registers, and
registers are synchronous on a clock. For reasons of power efficiency, the clock that gets routed to all
the peripherals is turned off by default. Therefore, we need to enable the clock to GPIOC before we
even begin to modify anything, or else we can change all we want in those GPIOC registers, but
nothing will happen! This is controlled in the Reset and Clock Controller (RCC) peripheral. Chapter 6
in the Reference Manual details all the registers for this peripheral. Since all GPIO is on the AHB bus,
all the on/off switches for those clocks are in the RCC_AHBENR (AHB Enable Register), located at
offset 0x14. But, since the RCC is on a separate bus, the base address is different from the GPIO one.
It’s still considered a peripheral, so it starts at 0x4000000, but since it is on the AHB1 bus, that base
address is 0x0002000, and the RCC peripheral offset address is 0x00001000. So, The RCC base
address is 0x40021000, and with an offset of 0x14, the RCC_AHBENR is defined as:
Updated: Jan 12, 2022 5
volatile uint32_t * RCC_AHBENR = (uitn32_t *)0x40021014UL;
Checking pg. 120 of the Reference Manual tells us that bit 19 is the enable for GPIOC’s clock, so:
*RCC_AHBENR |= 0x00080000UL;
will turn it on.
Since we are doing this as bare-bones as possible, we’ll create the project as bare bones as possible.
Create a new STM32 project, selecting the STM32F072B-DISCO board as before. But now, when you
click Next to set the project name, choose to create an Empty Project instead of an STM32Cube project
before clicking Finish:
Paste the code from the Appendix below into main, click “Run” to flash, and you should have the same
Blinky as Lab0! One thing to note here, is that since there is no auto-generated HAL, it doesn’t really
matter where you put your code, apart from the usual C scoping rules, as there are no #BEGIN USER
CODE / #END USER CODE sections.
Updated: Jan 12, 2022 6
To put all of this together, for Part 2 here, we are going to explore both polling and memory mapping.
The objective will be to use the same two pushbuttons, but now you won’t be allowed to use the HAL.
Part 2 - CONFIGURATION:
● Use the same push-buttons on PD2 and PA15 as above
● Create a new STM32Cube project, but set the “Targeted Project Type” to Empty as above
Part 2 - REQUIREMENTS:
● Use Memory-Mapping to configure PD2 and PA15 as inputs. Read the STM32F0x2 Reference
Manual to find the correct addresses for the registers.
○ Don’t assume the Reset value, make explicitly set it using bitmasking
○ If you were using the internal pull-ups, you will have to also use the GPIOx_PUPDR,
see pg. 158 of the STM32F0x2 Reference Manual (RM00091)
● Use Memory-Mapping to configure PC6-PC9 as outputs. Read the STM32F0x2 Reference
Manual to find the correct addresses for the registers.
● Implement a polling loop using memory mapping.
○ Pressing the button on PD2 should toggle the Red/Blue LEDs (if Red was on, turn it off
and turn Blue on; if Blue is on, turn it off and turn Red on)
○ Pressing the button on PA15 should toggle the Green/Orange LEDs
○ Debounce your button presses in software
● You can’t use the HAL_ functions for any of Part 2, as it won’t be generated when you create
the project
● While you don’t have to demo the Memory Mapped Blinky in the Appendix as it is just an
example, in the Lab Report, show and compare the size of the RAM & FLASH used for the
Blinky program using the HAL from Lab0 to the one using Memory Mapping from the
Appendix.
Updated: Jan 12, 2022 7
APPENDIX: Memory Mapped Blinky Code
/**
******************************************************************************
* @file : main.c
* @author : Timothy York
* @brief : Memory Mapped Blinky
******************************************************************************
* @attention
*
******************************************************************************
*/
#include
#if !defined(__SOFT_FP__) && defined(__ARM_FP)
#warning "FPU is not initialized, but the project is compiling for an FPU. Please
initialize the FPU before use."
#endif
#define DELAY 200000 //Delay loop iterations
//Memory Mapped Registers for GPIO, PortC
volatile uint32_t * GPIOC_MODER = (uint32_t *)0x48000800UL;
volatile uint32_t * GPIOC_IDR = (uint32_t *)0x48000810UL;
volatile uint32_t * GPIOC_ODR = (uint32_t *)0x48000814UL;
//Memory Mapped Register for the RCC to enable PortC
volatile uint32_t * RCC_AHBENR = (uint32_t *)0x40021014UL;
Updated: Jan 12, 2022 8
//Bitmask Definitions for bits 13 & 12
uint32_t GPIOC_MODER_MODER6_L_MASK = 0x00001000UL;
uint32_t GPIOC_MODER_MODER6_H_MASK = 0x00002000UL;
//Bitmask correspdoning to pin 6
uint32_t P6_MASK = 0x00000040UL;
int main(void)
{
int ix = 0;
//Enables the clock to GPIO PortC using the IOPCEN bit (19)
//See pg. 120 or RM00091 (STM32F0x2 Reference Manual)
*RCC_AHBENR |= 0x00080000UL;
//Sets PC6 to an output through the MODER register
//See pg. 157 or RM00091 (STM32F0x2 Reference Manual)
*GPIOC_MODER |= GPIOC_MODER_MODER6_L_MASK;
*GPIOC_MODER &= ~GPIOC_MODER_MODER6_H_MASK;
for(;;){
//Check the current value of the PC6
if (*GPIOC_IDR & P6_MASK){
// LED is ON, so turn it off
*GPIOC_ODR &= ~P6_MASK;
}
Updated: Jan 12, 2022 9
else {
// LED is OFF, so turn it on
*GPIOC_ODR |= P6_MASK;
}
//Basic delay loop
for (ix = 0; ix < DELAY; ++ix);
}
}
Updated: Jan 12, 2022 10

essay、essay代写