Write STM32F103C8T6 driver library from scratch -- write GPIO driver

Catalogue of series articles

Github open source address
Write STM32F103C8T6 driver library from scratch (I) -- STM32CubeMX create and adjust engineering structure
Write STM32F103C8T6 driver library from scratch (II) -- write system initialization program and configure clock tree
Write STM32F103C8T6 driver library from scratch (III) -- write GPIO driver

preface

Basic structure of I/O port bit

1. Create a file


Click the New icon in the upper left corner to create two New files, one as c file as a h file

Press ctrl+s to save the file, save it to the Drivers/Src path, and name it GPIO c
Similarly, save another file to Drivers/Inc and name it GPIO h

2. Add to project


Click file management to open GPIO C added to the project and system_stm32f1xx.c moves to the Core folder, and the adjusted path results are as follows.

3. Add document notes


You can right-click the blank space in Templates to configure your own notes

Just fill in the note name in 2 places and the note information in 3 places to write notes quickly

Let's add it c documents and Note information of h file

4. Add h file to prevent repeated compilation


We use a macro definition to prevent GPIO h file is compiled repeatedly, which roughly means that if there is no definition_ GPIO_H macro, then define the macro and start compiling h file. If the macro has been defined when compiling the file for the second time, the second compilation will not be carried out.

5. Create a new common file common h

In the same way, we save it in the Core/Inc folder. The main codes are as follows.

/**
  ******************************************************************************
  * @file           : common.h
  * @brief          : General header file
	* @author					: Full of joy
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/01/21
  ******************************************************************************
  * @attention
  *
  * This program is only for learning and can not be used for any other purpose without the permission of the author.
  *
  ******************************************************************************
  */
#ifndef _COMMON_H
#define _COMMON_H

//Data type declaration
typedef unsigned char							uint8;													//  8 bits 
typedef unsigned short int						uint16;													// 16 bits 
typedef unsigned long int						uint32;													// 32 bits 
typedef unsigned long long						uint64;													// 64 bits 

typedef char									int8;														//  8 bits 
typedef short int								int16;													// 16 bits 
typedef long  int								int32;													// 32 bits 
typedef long  long								int64;													// 64 bits 

typedef volatile int8							vint8;													//  8 bits 
typedef volatile int16							vint16;													// 16 bits 
typedef volatile int32							vint32;													// 32 bits 
typedef volatile int64							vint64;													// 64 bits 

typedef volatile uint8							vuint8;													//  8 bits 
typedef volatile uint16							vuint16;												// 16 bits 
typedef volatile uint32							vuint32;												// 32 bits 
typedef volatile uint64							vuint64;												// 64 bits 


#endif



6.gpio.h documentation

gpio.h file mainly stores some header files, enumeration types, function declarations and macro definitions

  1. First, we need to introduce two header files
#include "stm32f1xx.h"
#include "common.h"

stm32f1xx.h is the stm32 register macro definition file
common.h is the general header file we just created. Its main content is the alias of data type.

  1. Define MCU pin enumeration type
/*Set pin enumeration type*/
typedef enum
{
    PA0,  PA1,  PA2,  PA3,  PA4,  PA5,  PA6,  PA7,  PA8,  PA9,  PA10, PA11, PA12, PA13, PA14, PA15,
                 
    PB0,  PB1,  PB2,  PB3,  PB4,  PB5,  PB6,  PB7,  PB8,  PB9,  PB10, PB11, PB12, PB13, PB14, PB15,
                 
    PC0,  PC1,  PC2,  PC3,  PC4,  PC5,  PC6,  PC7,  PC8,  PC9,  PC10, PC11, PC12, PC13, PC14, PC15,
}GPIO_Num;
  1. Define IO port direction enumeration type
/*Set IO port direction enumeration type*/
typedef enum
{
	GPI,										// Define pin input
	GPO,										// Define pin output
}GPIO_Dir;

  1. Define the IO port mode enumeration type. The value of the enumeration type is the value to be written to the register in the future
/*Set IO port mode enumeration type*/
typedef enum
{
	GPI_ANAOG_IN 			=	0x00,						// Define pin analog input
	GPI_FLOATING_IN			=	0x04,						// Define pin float input
	GPI_PULL_UD				=	0x08,						// Define pin pull-down input

	GPO_PUSH_PULL			=	0x00,						// Define pin push-pull output
	GPO_OPEN_DTAIN			=	0x04,						// Define pin open drain output
	GPO_AF_PUSH_PULL		=	0x08,						// Define pin reuse push-pull output
	GPO_AF_OPEN_DTAIN		=	0x0C,						// Define pin multiplex open drain output
	
}GPIO_Mode;

  1. Defines the IO port speed enumeration type. The value of the enumeration type is the value to be written to the register in the future
/*Set IO port speed enumeration type*/
typedef enum
{
		GPIO_SPEED_2MHZ		= 	0x02,
		GPIO_SPEED_10MHZ	=	0x01,
		GPIO_SPEED_50MHZ	=	0x03,
}GPIO_Speed;
  1. Define the IO port pin module number
    We know that the enumeration type in C language is also a number and can also participate in the operation. That is to say, PA0 is 0, PA1 is 1 and PB0 is 16.
    Because each pin module has sixteen pins (0-15), the module number of the pin can be obtained by moving the pin 4 bits to the right
/*Get pin module number (A,B,C)*/
#define Get_Region(pin)		(pin>>4)
  1. Get pin number
    The lower four digits of the pin are the serial number of the pin, so we only need to match the pin and 0x0F to get the pin number
/*Get pin serial number*/
#define Get_Pin(pin)			(pin&0x0f)

7.GPIO initialization function

  1. First, we need to define a GPIO register array, so that we can configure the registers through the array serial number
GPIO_TypeDef *gpio_group[4] = {GPIOA, GPIOB, GPIOC, GPIOD};
  1. Define function
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)

I define the function name as GPIO_ Of course, you can use the name init as you like. Then input the parameters required for GPIO initialization: pin, direction, initial level, GPIO mode and pin speed.

  1. Get pin module number and pin number
uint8 Reg = Get_Region(pin);
uint8 Pin = Get_Pin(pin);
  1. Enable corresponding GPIO clock
    Because of the consideration of system energy saving, the chip reset is to turn off all peripheral clocks by default. The user needs to use the clock and turn on the clock by himself

    Since all GPIO clocks are mounted on the APB2 peripheral bridge, we only need to obtain the pin number and offset it on the GPIOA
RCC->APB2ENR |= 0x01<<(2+Reg);
  1. GPIO mode configuration

    The configuration of GPIO mode is controlled by CRL and CRH registers
    CRL is a low eight bit register that controls GPIO0-GPIO7
    CRH is the high eight bit register control GPIO8-GPIO15
    If we want to configure GPIO as the up-down input mode, we write the two bits of mode to 00 and configure the two bits of CNFy to 10
    If we want to configure GPIO to push-pull output mode, we write two bits of mode to 11 and two bits of CNFy to 00
    Clear the corresponding register bit before writing to the register to prevent errors.
    So the code is as follows:
if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
  1. GPIO output level

    ODR register is the port output data register. If it is push-pull output mode, it is the output level. If it is input mode, it is up and down.

  2. In GPIO H declared in the document

void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed);

This is the end of GPIO initialization. The complete code is as follows:

/**
	* @name		gpio_init
 	* @brief  	GPIO initialization
	* @param  	pin	Pin number 		 (P(A,B,C)0-15)
	* @param  	dir	Pin direction 		 GPO output 	 GPI input
	* @param  	dat	Initialization level 	 0 is low level 	 1 is high level
	* @param  	mode	Pin mode 		 In GPIO H files can be selected
	* @param  	speed	Output rate 		 In GPIO H files can be selected
	* @return 	void
	* @Sample 	gpio_init(PC13, GPO, 0, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample 	gpio_init(PC13, GPI, 1, GPI_PULL_UD	 , GPIO_SPEED_50MHZ)
  */
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	RCC->APB2ENR |= 0x01<<(2+Reg);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
	if(dat)	gpio_group[Reg]->ODR |= 0x01<<Pin;
	else	gpio_group[Reg]->ODR &= ~(0x01<<Pin);
}

8. Set pin level function

This function has nothing to say but to set the value in the corresponding ODR register. The code is as follows:

/**
	* @name		gpio_set
  	* @brief  	GPIO Set pin level
	* @param  	pin		Pin number 		 (P(A,B,C)0-15)
	* @param  	dat		Initialization level 	 0 is low level 	 1 is high level
	* @return 	void
	* @Sample 	gpio_set(PC13, 0)
  */
void gpio_set(GPIO_Num pin, uint8 dat)
{
	if(dat)	gpio_group[Get_Region(pin)]->ODR |= 0x01<<Get_Pin(pin);
	else	gpio_group[Get_Region(pin)]->ODR &= ~(0x01<<Get_Pin(pin));
}

9. Read pin level function


Just read the value in the IDR register of the corresponding pin. The code is as follows:

/**
	* @name		gpio_get
  	* @brief  	GPIO Get pin level
	* @param  	pin		Pin number 		 (P(A,B,C)0-15)
	* @return 	Pin level 	 0 is low level and 1 is high level
	* @Sample 	gpio_get(PA0)
  */
uint8 gpio_get(GPIO_Num pin)
{
	if(gpio_group[Get_Region(pin)]->IDR & 0x01<<Get_Pin(pin))		return 1;
	else															return 0;
}

10. Set the pin direction function

This function is often used in software analog communication timing. In fact, it is an initialization function. On the basis of removing the enable clock and configuring the pin level, the code is as follows:

/**
	* @name		gpio_dir
	* @brief  	GPIO Set pin direction
	* @param  	pin		Pin number 		 (P(A,B,C)0-15)
	* @param  	dir		Pin direction 		 GPO output 	 GPI input
	* @param  	mode	Pin mode 		 In GPIO H optional in file
	* @param  	speed	Output rate 		 In GPIO H files can be selected
	* @return 	void
	* @Sample 	gpio_dir(PC13, GPO, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample	gpio_dir(PC13, GPI, GPI_PULL_UD  , GPIO_SPEED_50MHZ)
  */
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
}

11. Pin flip level function

This function reverses the corresponding bit in the ODR register
Here I use an XOR operation to XOR 0x01 with the corresponding bit. The XOR operation is the same as 0 but different as 1
The truth table is as follows:

Register corresponding bit0x01result
110
011

The code is as follows:

/**
	* @name		gpio_reverse
  	* @brief  	GPIO Pin flip level
	* @param  	pin		Pin number 		 (P(A,B,C)0-15)
	* @return 	void
	* @Sample 	gpio_reverse(PC13)
  */
void gpio_reverse(GPIO_Num pin)
{
	gpio_group[Get_Region(pin)]->ODR ^= 0x01<<Get_Pin(pin);
}

12. Complete procedure

Full GPIO C Documents

/**
  ******************************************************************************
  * @file           : gpio.c
  * @brief          : GPIO drive
	* @author					: Full of joy
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/12/30
  ******************************************************************************
  * @attention
  *
  * This program is only for learning and can not be used for any other purpose without the permission of the author.
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "gpio.h"

GPIO_TypeDef *gpio_group[4] = {GPIOA, GPIOB, GPIOC, GPIOD};


/**
	* @name		gpio_init
  * @brief  GPIO initialization
	* @param  pin		Pin number 		 (P(A,B,C)0-15)
	* @param  dir		Pin direction 		 GPO output 	 GPI input
	* @param  dat		Initialization level 	 0 is low level 	 1 is high level
	* @param  mode	Pin mode 		 In GPIO H files can be selected
	* @param  speed	Output rate 		 In GPIO H files can be selected
	* @return void
	* @Sample gpio_init(PC13, GPO, 0, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample gpio_init(PC13, GPI, 1, GPI_PULL_UD	 , GPIO_SPEED_50MHZ)
  */
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	RCC->APB2ENR |= 0x01<<(2+Reg);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
	if(dat)	gpio_group[Reg]->ODR |= 0x01<<Pin;
	else		gpio_group[Reg]->ODR &= ~(0x01<<Pin);
}

/**
	* @name		gpio_set
  * @brief  GPIO Set pin level
	* @param  pin		Pin number 		 (P(A,B,C)0-15)
	* @param  dat		Initialization level 	 0 is low level 	 1 is high level
	* @return void
	* @Sample gpio_set(PC13, 0)
  */
void gpio_set(GPIO_Num pin, uint8 dat)
{
	if(dat)	gpio_group[Get_Region(pin)]->ODR |= 0x01<<Get_Pin(pin);
	else		gpio_group[Get_Region(pin)]->ODR &= ~(0x01<<Get_Pin(pin));
}

/**
	* @name		gpio_get
  * @brief  GPIO Get pin level
	* @param  pin		Pin number 		 (P(A,B,C)0-15)
	* @return Pin level 	 0 is low level and 1 is high level
	* @Sample gpio_get(PA0)
  */
uint8 gpio_get(GPIO_Num pin)
{
	if(gpio_group[Get_Region(pin)]->IDR & 0x01<<Get_Pin(pin))		return 1;
	else																												return 0;
}

/**
	* @name		gpio_dir
  * @brief  GPIO Set pin direction
	* @param  pin		Pin number 		 (P(A,B,C)0-15)
	* @param  dir		Pin direction 		 GPO output 	 GPI input
	* @param  mode	Pin mode 		 In GPIO H files can be selected
	* @param  speed	Output rate 		 In GPIO H files can be selected
	* @return void
	* @Sample gpio_dir(PC13, GPO, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample	gpio_dir(PC13, GPI, GPI_PULL_UD  , GPIO_SPEED_50MHZ)
  */
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
}

/**
	* @name		gpio_reverse
  * @brief  GPIO Pin flip level
	* @param  pin		Pin number 		 (P(A,B,C)0-15)
	* @return void
	* @Sample gpio_reverse(PC13)
  */
void gpio_reverse(GPIO_Num pin)
{
	gpio_group[Get_Region(pin)]->ODR ^= 0x01<<Get_Pin(pin);
}

Full GPIO H file

/**
  ******************************************************************************
  * @file           : gpio.h
  * @brief          : GPIO drive
	* @author					: Full of joy
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/12/30
  ******************************************************************************
  * @attention
  *
  * This program is only for learning and can not be used for any other purpose without the permission of the author.
  *
  ******************************************************************************
  */
#ifndef _GPIO_H
#define _GPIO_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx.h"
#include "common.h"

/*Set pin enumeration type*/
typedef enum
{
    PA0,  PA1,  PA2,  PA3,  PA4,  PA5,  PA6,  PA7,  PA8,  PA9,  PA10, PA11, PA12, PA13, PA14, PA15,
                 
    PB0,  PB1,  PB2,  PB3,  PB4,  PB5,  PB6,  PB7,  PB8,  PB9,  PB10, PB11, PB12, PB13, PB14, PB15,
                 
    PC0,  PC1,  PC2,  PC3,  PC4,  PC5,  PC6,  PC7,  PC8,  PC9,  PC10, PC11, PC12, PC13, PC14, PC15,
}GPIO_Num;

/*Set IO port direction enumeration type*/
typedef enum
{
	GPI,										// Define pin input
	GPO,										// Define pin output
}GPIO_Dir;

/*Set IO port mode enumeration type*/
typedef enum
{
	GPI_ANAOG_IN 				= 	0x00,						// Define pin analog input
	GPI_FLOATING_IN				= 	0x04,						// Define pin float input
	GPI_PULL_UD					= 	0x08,						// Define pin pull-down input

	GPO_PUSH_PULL				=	0x00,						// Define pin push-pull output
	GPO_OPEN_DTAIN				= 	0x04,						// Define pin open drain output
	GPO_AF_PUSH_PULL			=	0x08,						// Define pin reuse push-pull output
	GPO_AF_OPEN_DTAIN			= 	0x0C,						// Define pin multiplex open drain output
	
}GPIO_Mode;

/*Set IO port speed enumeration type*/
typedef enum
{
		GPIO_SPEED_2MHZ		= 	0x02,
		GPIO_SPEED_10MHZ	=	0x01,
		GPIO_SPEED_50MHZ	=	0x03,
}GPIO_Speed;

/*Get pin module number (A,B,C)*/
#define Get_Region(pin)			(pin>>4)

/*Get pin serial number*/
#define Get_Pin(pin)			(pin&0x0f)


/*Function declaration*/
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed);
void gpio_set(GPIO_Num pin, uint8 dat);
uint8 gpio_get(GPIO_Num pin);
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed);
void gpio_reverse(GPIO_Num pin);


#endif


Keywords: Single-Chip Microcomputer stm32 ARM

Added by Adamthenewbie on Fri, 21 Jan 2022 18:27:14 +0200