Drivers for the STM32F446xx Family of Microcontrollers. This family from ST Microelectronics uses an ARM Cortex M4 processor with a floating point unit (ARM Cortex M4F). The drivers include interrupt and polling methods for:
- I2C
- GPIO
- SPI
- UART
- Dynamic IRQ System with Support for Opaque Data
This is a bare-metal layer and everything is self-contained in this repository.
- Enable the clock to the peripheral with
USART_PeriClockControl()
- Declare a
USART_Handle_t
structure and fill with initialization information, and initialize with a call toUSART_Init()
- If using interrupts, use
USART_IRQPriorityConfig()
to configure priority for this UART. ThenUSART_IRQInterruptConfig()
to enable or disable the interrupt line. - Call
USART_PeripheralControl()
to enable or disable the peripheral. CallUSART_IRQHandling()
as soon as possible in the ISR - Send data with
USART_SendData()
orUSART_SendDataIT()
. The former polls and the latter uses interrupts - Receive data with
USART_ReceiveData()
orUSART_ReceiveDataIT()
, the former polls and the latter uses interrupts - If using interrupts, you might want to implement the callback
USART_ApplicationEventCallback()
- You can call
USART_DeInit()
to reset the peripheral - Calls to
USART_GetFlagStatus()
andUSART_ClearFlag()
allow you to read and clear flags, respectively
- Enable the clock to the peripheral with
SPI_PeriClockControl()
- Declare a
SPI_Handle_t
structure and fill with initialization information, then initialize with a call toSPI_Init()
SPI_PeripheralControl()
can be used to enable or disable the peripheral- Send data with
SPI_SendData()
orSPI_SendDataWithIT()
. The former polls and the latter uses interrupts - Receive data with
SPI_ReadData()
orSPI_ReadDataWithIT()
. The former polls and the latter uses interrupts - If using interrupts, call
SPI_IRQPriorityConfig()
to configure priority. Then callSPI_IRQInterruptConfig()
to enable or disable the interrupt line. The functionSPI_IRQHandling()
must be called as soon as possible in the ISR. - If using interrupts, you might want to implement the callback
SPI_ApplicationEventCallback()
- You can call
SPI_DeInit()
to reset the peripheral - Calls to
SPI_GetFlagStatus()
allow you to read flags - SSI and SSOE are supported and enabled/disabled through
SPI_SSIConfig()
andSPI_SSOEConfig()
- Enable the clock to the peripheral with
GPIO_PeriClockControl()
- Declare a
GPIO_Handle_t
structure and fill with initialization information, then initialize with a call toGPIO_Init()
- Read a pin with
GPIO_ReadFromInputPin()
or a whole port withGPIO_ReadFromInputPort()
- Write to a pin with
GPIO_WriteToOutputPin()
or a port withGPIO_WriteToOutputPort()
. You can also toggle an output pin withGPIO_ToggleOutputPin()
- If using interrupts, call
GPIO_IRQPriorityConfig()
to configure priority. Then callGPIO_IRQInterruptConfig()
to enable or disable the interrupt line. The functionGPIO_IRQHandling()
must be called as soon as possible within the ISR - You can call
GPIO_DeInit()
to reset the peripheral
- Enable the clock to the peripheral with
I2C_PeripheralClockControl()
- Declare an
I2C_Handle_t
structure and fill with initialization information, then initialize with a call toI2C_Init()
- Use
I2C_ManageAcking()
to configure acknowledge control - As a master device, you can send data with
I2C_MasterSendData()
orI2C_MasterSendDataIT()
. The former polls while the latter uses interrupts - Use
I2C_PeripheralControl()
to enable or disable the peripheral - You can receive data with
I2C_MasterReceiveDataIT()
orI2C_SlaveReceiveData()
. The former polls while the latter uses interrupts. - As a slave device, you can respond with
I2C_SlaveSendData()
orI2C_SlaveReceiveData()
. - If using interrupts, call
I2C_IRQPriorityConfig()
to configure priority. Then callI2C_IRQInterruptConfig()
to enable or disable the interrupt line. The funcitonI2C_EV_IRQhandling()
must be called as soon as possible within the ISR. Optionally, callI2C_ERR_IRQHandling()
to get a callback when an error is detected. You might want to implement the callbackI2C_ApplicationEventCallback()
- You can call
I2C_DeInit()
to reset the peripheral
- Always call
IRQ_Init()
to initialize the dynamic IRQ system - An ISR must have the following type (
irq
is the IRQ Number anddata
is the opaque data):typedef void (*IRQ_Handler)(IRQn_Type irq, void *data);
- Register the ISR with
IRQ_Register()
and unregister withIRQ_Unregister()
- Configure the priority with
IRQ_SetPriority()
. The current priority can be retrieved withIRQ_GetPriority()
. - Enable the interrupt line with
IRQ_Enable()
and disable withIRQ_Disable()
- Change the opaque data with
IRQ_SetData()
- Retrieve the opaque data outside of the ISR with
IRQ_GetData()
Let's say you want to make a simple application where you'd like to turn on an LED whenever a button is pressed, but you want the LED off whenever the button is not being pressed. Let's use the on-board LED and user button for this example.
The first thing is to design the circuitry. This is done for us since we are using on-board components. Second, we need to select which pins to use. Since we are using on-board components, we need to look at the schematics. The schematics can be found in the user manual which can be downloaded from ST's website click here to go to ST's website. From the schematics, we know that PA5 (Port A, Pin 5) is connected to the LED, and PC13 (Port C, Pin 13) is connected to the push-button. From the schematics, we can also see that PC13 is pulled to high by a pull-up resistor. PC 13 is driven to low when the button is pressed. When PC13 is Low (pressed), we will turn on the LED (PA5). When PC13 is High (not pressed), we will turn off the LED (PA5).
Now let's dive into the code. The general procedure for these drivers is to:
- Create a handler. The handler keeps track of configurations and it keeps a pointer to the base address of the peripheral. Pointers to the base address of the peripherals have already been defined for you.
- Fill the configurations.
- Enable the clock, and initialize the desired configuration.
- If using interrupts, configure the priority, register the handler, and then enable the interrupt.
// 1. Create Handles
GPIO_Handle_t GPIOHandle_C13;
GPIO_Handle_t GPIOHandle_A5;
// 1. Point to GPIO Port C and A
GPIOHandle_C13.pGPIO = GPIOC;
GPIOHandle_A5.pGPIO = GPIOA;
// 2. Configurations
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_IN;
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_NO_13;
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_OUT;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_NO_5;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinOPType = GPIO_OP_TYPE_PP;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_NO_PUPD;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
// 3. Enable Clock
GPIO_PeriClockControl(GPIOHandle_C13.pGPIO, ENABLE);
GPIO_PeriClockControl(GPIOHandle_A5.pGPIO, ENABLE);
// 3. Initialize
GPIO_Init(&GPIOHandle_C13);
GPIO_Init(&GPIOHandle_A5);
Now, let's add the logic:
while(1) {
// if !High -> if Low -> If pressed -> turn LED on
if(!GPIO_ReadFromInputPin(GPIOHandle_C13.pGPIO, GPIOHandle_C13.GPIO_PinConfig.GPIO_PinNumber)) {
GPIO_WriteToOutputPin(GPIOHandle_A5.pGPIO, GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber, SET);
} else {
GPIO_WriteToOutputPin(GPIOHandle_A5.pGPIO, GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber, RESET);
}
}
Thus the whole program can be written like this:
#include "stm32f446xx.h"
int main(void) {
// Create Handles
GPIO_Handle_t GPIOHandle_C13;
GPIO_Handle_t GPIOHandle_A5;
// Point to GPIO Port C and A
GPIOHandle_C13.pGPIO = GPIOC;
GPIOHandle_A5.pGPIO = GPIOA;
// Configurations
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_IN;
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_NO_13;
GPIOHandle_C13.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_OUT;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_NO_5;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinOPType = GPIO_OP_TYPE_PP;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_NO_PUPD;
GPIOHandle_A5.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
// Enable Clock
GPIO_PeriClockControl(GPIOHandle_C13.pGPIO, ENABLE);
GPIO_PeriClockControl(GPIOHandle_A5.pGPIO, ENABLE);
// Initialize
GPIO_Init(&GPIOHandle_C13);
GPIO_Init(&GPIOHandle_A5);
while(1) {
// if !High -> if Low -> If pressed -> turn LED on
if(!GPIO_ReadFromInputPin(GPIOHandle_C13.pGPIO, GPIOHandle_C13.GPIO_PinConfig.GPIO_PinNumber)) {
GPIO_WriteToOutputPin(GPIOHandle_A5.pGPIO, GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber, SET);
} else {
GPIO_WriteToOutputPin(GPIOHandle_A5.pGPIO, GPIOHandle_A5.GPIO_PinConfig.GPIO_PinNumber, RESET);
}
}
}