Discovery: Let's Get Started
It’s taken a while to get to this point. We’ve looked at the STM32F407G-DISC1 circuit board that I will be targeting. We’ve looked at CubeMX, a tool to generate the horrible code to get our processors started, and the compiler options. Hopefully, you’ve gotten a tool chain set up and have stopped the LEDs from blinking by overwriting the demo program that is installed in the board at manufacture time. In this installment, we’re finally going to be looking at controlling the LEDs and slowing everything down.
Let There Be Light!
Our processor board comes with four LEDs connected to port D pins 12-15.
ST used the names LD3-6 in the Cube project to match the silkscreen labels on the circuit board. If we look at the appropriate page in the schematics we see:
In this topology, with the LED having one connection to the processor and the other to ground, we have to turn the processor pin on to turn the LED on. If the board had been laid out so that the LEDs were connected between the processor pin and +3 volts, we would have to turn the processor pin off to turn the LED on.
The processor drives the LEDs with a 3V signal. The resistors between the processor pins and the LEDs are used as current limiters (R42, R41, R36, and R40). The processor can only supply up to 25 milliamps (mA, that is 0.025 amps) of current to the LED without causing damage. But the processor does not have the ability to limit the amount of current provided to devices connected to it, not having built in current limiters, because the current limiting resistors are made of carbon and cannot be directly incorporated into the silicon of the processor. The processor manufacturers expect you to provide the current limiting resistors externally.
An LED without the limiting resistor will draw enough current to boil the silicon inside of the LED and the processor. From figure 2, the amount of current to the orange, red, and blue LEDs is calculated by dividing the 3 volts that the processor will provide by the 680 ohms of the resistor giving:
current = voltage/resistance = 3 volts / 680 ohms = 0.0044 amperes(4.4mA)
4mA is well within what the processor can provide (parameter IIO in the datasheet).
The green LED draws 3 volts/510 ohms = 5.8mA. I expect that the green LED is given a little more current to give it a similar brightness to the other three LEDs.
Enough electrical engineering, how do we turn on the pins?
When I gave an overview of the code generated by Cube, there is a file called main.h that contains a bunch of #define statements that give us a convenient set of symbols for the pins that we can use in our program. For the pins connected to the LEDs we have:
#define LD4_Pin GPIO_PIN_12
#define LD4_GPIO_Port GPIOD
#define LD3_Pin GPIO_PIN_13
#define LD3_GPIO_Port GPIOD
#define LD5_Pin GPIO_PIN_14
#define LD5_GPIO_Port GPIOD
#define LD6_Pin GPIO_PIN_15
#define LD6_GPIO_Port GPIOD
The hardware abstraction layer (HAL) gives us routines to turn the pins on and off using those definitions:
HAL_GPIO_WritePin( LD4_GPIO_Port, LD4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin( LD4_GPIO_Port, LD4_Pin, GPIO_PIN_RESET);
It is common for ARM processors have three registers to control the voltage levels on GPIO pins; one that you can use to set all of the pins at once, one that is used to turn on particular pins, and another to turn off particular pins.
On other processors you have to use a sequence called “read/modify/write” to set and clear pins. You do not have the ability to work on a single port pin, so you read the port to get its current state, turn on the bits that you want, then write the pattern back to the port register.
tempBits = PORTA;
tempBits |= 0x04; /* Turn on the third pin */
PORTA = tempBits;
tempBits = PORTA;
tempBits &= ~(0x04); /* Turn off the third pin */
PORTA = tempBits;
On an ARM, you can do this, but a much better way is to use the port set and reset registers.
Without using the HAL routines, turning on the green LED on our processor would look like this:
GPIOD->BSRR = LD4_Pin;
To turn it off:
GPIOD->BSRR = LD4_Pin << 16; /* Reset register is in the top 16 */
BAM! One instruction with really crappy syntax and a whole bunch of magic. Unless you really MUST understand how everything works and need to save every cycle, just use HAL_GPIO_WritePin.
Let’s take a look at the actual Cube code. It is about as simple as you can get:
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16;
}
}
One thing that the code does is sanity check the parameters. The function assert_param takes a condition that must be true. If the condition is false, assert_param will generate an error message telling you what went wrong, in which file, and at which line number. So, if you supply it with a parameter that isn’t a pin or set/reset, it will generate an error message.
The critics will say that this is stupid because that check happens every time the function is called which can be thousands of times per second. BUT WAIT, one of the options is to insert a statement into your program that looks like
#define assert_param(expr) ((void)0U)
What this does is make every call to assert_param disappear. When you are first getting your program working you let assert_param check all of your parameters. When your program gets going properly, we turn off assert_param and all of the calls get compiled out.
Wait Just One Moment!
Programs in embedded systems are basically infinite loops. All of your processing is done within that infinite loop. So if we wanted to blink our LEDs, we could do this:
while (1) {
HAL_GPIO_WritePin( LD4_GPIO_Port, LD4_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin( LD4_GPIO_Port, LD4_Pin, GPIO_PIN_RESET);
}
The problem with this is, by default, our processor will be running at 25MHz. and our LEDs will be blinking so fast that they will appear to just be on. We have to slow things down.
Every ARM Cortex M4 processor, like ours, has a special timer that is used to generate a periodic interrupt. This interrupt is usually used to generate a “tick” once per millisecond (1/1000 seconds). That tick interrupt increments a counter that the HAL uses to give us a very useful function HAL_Delay. You can use HAL_Delay to delay execution for a specified number of milliseconds.
while (1) {
HAL_GPIO_TogglePin( LD4_GPIO_Port, LD4_Pin); /* another useful HAL function */
HAL_Delay( 500); /* Stop for 1/2 second */
}
This function goes into a tight loop, holding your program, until the delay expires. That can be a problem if you need to do other stuff. Another method is to do some time calculations:
#define HALF_SECOND 500 // in millisecond ticks
uint32_t lastRun = 0U;
uint32_t nowTime;
while (1) {
nowTime = HAL_GetTick();
if ((nowTime - lastRun) >= HALF_SECOND) {
HAL_GPIO_TogglePin( LD4_GPIO_Port, LD4_Pin);
lastRun = nowTime;
}
...
// Other processing happens here
}
This code loops around doing the “Other processing”, but only toggles the LED pin if it has been at least ½ second since the last time it has toggled.
Okay, now we know how to turn LEDs on and off, and how to slow down processing so that we can bring things down to human scale. Your job is to write a program to blink your LEDs.
This post is part of a series. Please see the other posts here.