This is the 29th day of my participation in the August More Text Challenge
STM32F103 chip We see the STM32 chip is a packaged product, mainly composed of core and peripherals on the chip. If with the computer analogy, the core and peripherals are like the CPU on the computer and the motherboard, memory, graphics card, hard disk relationship. The STM32F103 uses the Cortex-M3 kernel, which is the CPU, designed by ARM. ARM does not make chips, but licenses its chip technology. Chip manufacturers (soCs) such as ST, TI, and Freescale are responsible for designing components outside of the core and producing the entire chip. These components outside the core are called extranuclear peripherals or on-chip peripherals. Such as GPIO, USART (serial port), I2C, SPI and so on are all called on-chip peripherals. (Use wildfire official introduction).
The chip (here referred to as the core, or CPU) and peripherals are connected by various buses, in which there are four driver units and four passive units. For ease of understanding, we can think of the drive unit as the CPU part, and the passive unit as peripherals. Let’s briefly introduce the various components of the drive unit and the passive unit.
ICode Bus THE I in ICode stands for Instruction. The compiled programs are stored in FLASH as instructions. To read these instructions and execute the program, the kernel must pass through the ICode bus, which needs to be used almost all the time. It is specially used for pointing. Driver unit DCode bus THE D in DCode stands for Data, that is, Data, that means this bus is used to fetch numbers. When we write a program, there are two types of data: constants and variables. Constants are fixed, and are modified with the const keyword in C language. They are placed in internal FLASH. Because the data can be accessed by both the Dcode bus and the DMA bus, to avoid access collisions, the fetch needs to be mediated by a bus matrix to determine which bus is fetching. System bus System bus is to access the peripheral registers, we usually say the register programming, that is, read and write registers are completed through this system bus. The DMA bus is also mainly used to transfer data, which can be in a data register of some peripheral, can be in SRAM, can be in internal FLASH. Because the data can be accessed by both the Dcode bus and the DMA bus, to avoid access collisions, the fetch needs to be mediated by a bus matrix to determine which bus is fetching. The FLASH memory inside the passive unit and the internal FLASH memory, the FLASH memory, that’s where we write the program. The kernel fetches its instructions through the ICode bus. Internal SRAM The internal SRAM, commonly known as RAM, is based on the overhead of program variables, stack, etc. The kernel accesses it through the DCode bus. FSMC Flexible Static Memory Controller FSMC Flexible Static Memory Controller FSMC Flexible Static Memory Controller FSMC Flexible Static Memory Controller is a very characteristic of STM32F10xx peripheral, through FSMC, we can expand memory, such as external SRAM, NANDFLASH and NORFLASH. Note that FSMC can only expand static memory, that is, the name of the S: static, not dynamic memory, such as SDRAM can not expand. AHB to APB Bridge The two APB2 and APB1 buses that extend from the AHB bus mount various featured peripherals of STM32. We often say GPIO, serial port, I2C, SPI these peripherals are mounted on these two buses, this is the focus of our study of STM32, is to learn how to program these peripherals to drive various external devices.
Memory maps the controlled unit’s FLASH, RAM, FSMC and AHB to APB bridge (i.e. peripherals on a chip), which are arranged together in a 4GB address space. When we program, we can find them by their address, and then manipulate them (read and write data to them through C language). Memory itself does not have address information, its address is assigned by the chip manufacturer or user, the process of assigning address to memory is called memory mapping. Reassigning an address to memory is called memory remapping.
Memory area function division
In the 4GB address space, ARM has evenly divided it into 8 blocks in broad lines, each 512MB, and each block has a specified purpose. The size of each block is 512MB, which is obviously very large, and chip manufacturers may not always run out of peripherals with their own characteristics in the design of each block. I’m just using a fraction of it.
Of the eight blocks, there are three that are very important and the three that we care about most. Block0 is used to design internal FLASH, Block1 is used to design internal RAM, and Block2 is used to design peripherals on the chip. Let’s briefly introduce the functional division of specific areas in these three blocks.
Block0 is mainly used to design FLASH devices on the chip. The FLASH devices of STM32F103ZET6 and STM32F103VET6 are 512KB, which is large capacity. To integrate larger FLASH or SRAM inside the chip means that the cost of the chip increases, often the integrated FLASH chip is not too large, ST can pursue the cost performance at the same time to achieve 512KB, it is a move of conscience. Memory Block1 internal area function division Block1 is used to design the SRAM within the chip. The SRAM of both STM32F103ZET6 and STM32F103VET6 is 64KB. Memory Block2 internal area function division Block1 is used to design the SRAM within the chip. The SRAM of both STM32F103ZET6 and STM32F103VET6 is 64KB.
Register mapping As we know, memory itself has no address, the process of assigning an address to memory is called memory mapping. In the memory Block2 area, on-chip peripherals are designed. They take four bytes as a unit, a total of 32 bits, and each unit corresponds to different functions. When we control these units, we can drive peripherals to work. We can find the starting address of the each unit, and then through the operation of the C language pointer way to access these units, if every time is through the access to this address, is not only bad memories also error-prone, then we can according to different function of each unit, the function name for the memory unit take an alias, This alias is often referred to as a register. The process of aliasing a memory location with a specific function that has been assigned an address is called register mapping. For example, the address of the ODR for the GPIOB port is 0x4001 0C0C. The ODR register is 32bit. The lower 16bit is valid and corresponds to 16 external IO. Write IO of 0/1 to output low/high level. Now we make all 16 IO of GPIOB output high level through the operation mode of C language pointer.
// GPIOB Port full output high level *(unsigned int*)(0x4001 0C0C) = 0xFFFF;Copy the code
0x4001 0C0C looks to us like the address of the GPIOB port ODR, but from the compiler’s point of view, this is just an ordinary variable, an immediate number, and to make the compiler think of it as a pointer, we have to cast it to a pointer. That is, (unsigned int *)0x4001 0C0C, and then the * operation on the pointer. As we said, accessing memory via absolute addresses is hard to remember and error-prone, so we can do it via registers.
#define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C) * GPIOB_ODR = 0xFF;Copy the code
For ease of operation, we simply define the pointer operation “*” inside the register alias.
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C) GPIOB_ODR = 0xFF;Copy the code
The peripheral area on the peripheral address mapping chip of STM32 is divided into three buses. According to the different speed of peripherals, different buses mount different peripherals. APB1 mounts low-speed peripherals, and APB2 and AHB mount high-speed peripherals. The lowest address of the corresponding bus is called the base address of the bus, and the base address of the bus is also the address of the first peripheral mounted on the bus. The APB1 bus has the lowest address, from which the on-chip peripherals start, also known as the peripheral base address.
Bus base address
Peripheral base address The bus carries various peripherals, which also have their own address range. The first address of a particular peripheral is called “XX peripheral base address,” also known as the boundary address of XX peripherals. For the boundary addresses of STM32F10xx peripherals, please refer to Table 1 of memory mapping in section 2.3 of STM32F10xx Reference Manual: Boundary addresses of STM32F10xx registers. Here we use the GPIO peripheral to explain the base address of the peripheral, GPIO is a high-speed peripheral, mounted to the APB2 bus.
Peripheral register The registers of the XX peripheral are distributed in the address range of the peripheral. Take GPIO peripherals for example. GPIO is short for general I/O port. Simply speaking, IT is the controllable pin of STM32. The simplest application is to connect the GPIO pin to the cathode of the LED lamp, the anode of the LED lamp is connected to the power supply, and then control the level of the pin through STM32, so as to realize the control of the LED lamp on and off. GPIO has many registers, each with a specific function. Each register is 32bit, four bytes, and is arranged in order at the base address of the peripheral. The location of the registers is described by an offset address relative to the base address of the peripheral. Here we take GPIOB port as an example to illustrate what registers GPIO has.
The register description of the peripherals can refer to the register description section of the specific chapter in the STM32F10xx Reference Manual. We need to refer to the register description of the peripherals repeatedly during programming. Here we take “GPIO port set/reset register” as an example to teach you how to understand the description of the register.
(GPIOx_BSRR)(x=A… E) “where” X “can be a-E, that is, this register description applies to GPIOA, GPIOB and GPIOE. These GPIO ports all have such A register. The offset address is the offset of the register relative to the base address of the peripheral. The offset address of this register is 0x18. From the reference manual, we can find that the base address of GPIOA peripheral is 0x4001 0800, so we can calculate the address of GPIOA_BSRR register of GPIOA is 0x4001 0800+0x18. Similarly, since the peripheral base address of GPIOB is 0x4001 0C00, the address of GPIOB_BSRR register can be calculated as 0x4001 0C00+0x18. The same can be done for other GPIO ports. The register bit table is followed by the bit table of the register, which lists the names and permissions of its 0-31 bits. The numbers in the upper part of the table are bit numbers, the middle part is bit names, and the bottom part is read/write permission. W indicates write only, R indicates read-only, and Rw indicates read/write. The bit permissions in this register are W, so only write, if read this register, is not guaranteed to read its real contents. And some register bits read-only, is generally used to express the STM32 peripherals of some working state, by STM32 hardware automatic change, the program by reading those register bits to judge the working state of the peripherals. The bit function is the most important part of the register description. It describes the function of each bit of the register in detail. For example, there are two register bits in this register, namely BRy and BSy. The y value can be 0-15, where 0-15 represents the pin number of the port. For example, BR0 and BS0 are used to control the 0th pin of GPIOx, and x represents GPIOA. That is the 0 pin that controls GPIOA, and BR1 and BS1 are the 1 pin that controls GPIOA. Where the BRy pin is stated as “0: no operation is performed on the corresponding ODRx bit; 1: reset the corresponding ODRx bit “. Here “reset” means to set the bit to 0, and “set” means to set the bit to 1; ODRx in the description is the register bit of another register. We only need to know that when ODRx bit is 1, the corresponding pin X outputs high level, and when ODRx bit is 0, the corresponding pin X outputs low level (interested readers can refer to the description of this register GPIOx_ODR for understanding). Therefore, if a “1” is written to BR0, the 0th pin of GPIOx will output “low”, but if a “0” is written to BR0, it will not affect the ODR0 bit, so the pin level will not change. For this pin to output “high level”, it is necessary to write “1” to the “BS0” bit. The register bit BSy is the opposite operation to BRy.
All of the above content about memory mapping is to prepare for a better understanding of how to use C language to control reading and writing peripheral registers, which is the focus of this chapter. Encapsulating bus and peripheral base addresses Programmatically, we define both bus base addresses and peripheral base addresses with corresponding macros, with the bus or peripheral using their names as macro names.
Firstly, the base address of “on-chip peripherals” is defined PERIPH_BASE, and then the address offsets of each bus are added to PERIPH_BASE to obtain the addresses of APB1 and APB2 buses APB1PERIPH_BASE and APB2PERIPH_BASE. The peripheral address of GPIOA-G is obtained by adding the offset of peripheral address. Finally, the address offset of each register is added to the peripheral address to obtain the address of a specific register. Once you have a specific address, you can read and write using Pointers.
*/ *(unsigned int *)GPIOB_BSRR = (0x01<<(16+0)); /* GPIOB_BSRR = (0x01<<(16+0)); */ *(unsigned int *)GPIOB_BSRR = 0x01<<0; */ *(unsigned int *)GPIOB_BSRR = 0x01<<0; unsigned int temp; */ temp = *(unsigned int *)GPIOB_IDR;Copy the code
This code uses (unsigned int *) to force the value of the GPIOB_BSRR macro into an address, and then uses the “*” pointer to assign the address, thus implementing the function of writing a register. Similarly, reading a register also uses a pointer operation to fetch data from the register into a variable, thus obtaining the state of STM32 peripherals. For example, GPIoa-GPIoe has a set of registers with the same function, such as GPIOA_ODR/GPIOB_ODR/GPIOC_ODR and so on. They are just not the same address, but it is necessary to define its address for each register. In order to access registers more conveniently, we introduce structure syntax in C language to encapsulate registers.
typedef unsigned int uint32_t; /* unsigned 32-bit variable */ typedef unsigned short int uint16_t; /* unsigned 16 bit variable */ /* GPIO register list */ /* Uint32_t CRH; /*GPIO port configuration high register address offset: 0x04 */ uint32_t IDR; /* Uint32_t ODR; /* Uint32_t BSRR; /* Uint32_t BRR; /* Uint16_t LCKR; /*GPIO port configuration lock register address offset: 0x18 */} GPIO_TypeDef;Copy the code
This code uses the typedef keyword to declare a structure type named GPIO_TypeDef. The structure has seven member variables whose names correspond to the register names. The syntax of C language stipulates that the storage space of variables in the structure body is continuous, in which 32-bit variables occupy 4 bytes and 16-bit variables occupy 2 bytes.
In other words, the GPIO_TypeDef we define, if the initial address of the structure is 0x4001 0C00 (which is also the address of the first member variable CRL), then the address of the second member variable CRH in the structure is 0x4001 0C00 +0x04. The added 0x04 is the offset representing the 4-byte address occupied by the CRL. The offset of other member variables relative to the first address of the structure is commented on the right side of the above code. This address offset corresponds to the register address offset defined by the STM32 GPIO peripheral. As long as the initial address of the structure is set, the address of the member in the structure can be determined, and then the register can be accessed as the structure.
GPIO_TypeDef * GPIOx; // define a GPIO_TypeDef structure pointer GPIOx GPIOx = GPIOB_BASE; GPIOx->IDR = 0xFFFF; GPIOx->ODR = 0xFFFF; int32_t temp; temp = GPIOx->IDR; // Read the GPIOB_IDR register into the temp variableCopy the code
This code defines a structure pointer GPIOx using a GPIO_TypeDef type and makes the pointer point to the address GPIOB_BASE(0x4001 0C00). Use GPIOx->ODR and GPIOx->IDR to read and write registers. Finally, we go one step further and define Pointers of type GPIO_TypeDef directly with macros that point to the beginning address of each GPIO port. When using this macro, we can access registers directly.
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE) #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE) #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE) #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE) #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE) #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE) use the defined macro to access the register of the GPIOB port directly */ /* / GPIOB->BSRR = 0xFFFF; Gpiob ->CRL = 0xFFFF; // Modify GPIOB_CRL register GPIOB->ODR =0xFFFF; // Modify GPIOB_ODR int32_t temp; temp = GPIOB->IDR; GPIOA->BSRR = 0xFFFF; GPIOA->BSRR = 0xFFFF; GPIOA->CRL = 0xFFFF; GPIOA->ODR =0xFFFF; uint32_t temp; temp = GPIOA->IDR; // Read the GPIOA_IDR register into the temp variableCopy the code
Here we just take the GPIO peripheral as an example to explain the C language to register encapsulation. Similarly, other peripherals can be encapsulated in the same way. The good news is that the firmware library does all the work for us, so we’ll just take a look at the encapsulation process to give you an idea of what it is and why. Turn on the LED lights
1. Turn on pin PC13 clock 2. Configure the output and determine the output mode 3. Output low level
The program
int main() { *(unsigned int*)0x40021018 |=(1<<4); / / open the GPIO clock * (unsigned int *) 0 x40011004 & = ~ (0 x0f < < (4 * 5)); / / configuration output mode * (unsigned int *) 0 x40011004 | = (1 < < (4 * 5)); *(unsigned int*)0x4001100C &=~(1<<13); // set output low level}Copy the code