单片机开发中免不了会与寄存器打交道。在51,AVR单片机中,会有一个头文件将寄存器的地址定义成更容易阅读的宏,在C语言中通过宏定义来访问寄存器。
#ifndef _AVR_IOM128_H_
#define _AVR_IOM128_H_ 1
............
/* Input Pins, Port D */
#define PIND _SFR_IO8(0x10)
/* Data Direction Register, Port D */
#define DDRD _SFR_IO8(0x11)
/* Data Register, Port D */
#define PORTD _SFR_IO8(0x12)
/* Input Pins, Port C */
#define PINC _SFR_IO8(0x13)
/* Data Direction Register, Port C */
#define DDRC _SFR_IO8(0x14)
/* Data Register, Port C */
#define PORTC _SFR_IO8(0x15)
...........
// When use GPIO register
PORTD = 0xff;
tmp = PINC;
而在STM32单片机中,寄存器不再是地址转化成宏定义的形式了,变成了一个结构体。对C语言不熟的同学可能会感到困惑。其实这样的定义方式,对于STM32单片机的寄存器设计而言,是更合理的一种方式。
#ifndef __STM32F10x_H
#define __STM32F10x_H
..........
/**
* @brief General Purpose I/O
*/
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
................
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
................
// When use GPIO register
GPIOA->ODR = 0xff;
tmp = GPIOB->IDR;
在C语言中,结构体对应着一种内存布局方式。定义一个结构体变量,表示申请了一块内存空间,这块内存将按照结构体定义的方式来布局。而定义一个结构体指针,表示这个指针指向的区域,会按照结构体定义的方式来布局。
typedef struct
{
uint32_t field_0;
uint16_t field_1;
uint16_t field_2;
uint32_t field_3;
}test_type_t;
test_type_t var;
test_type_t *p1;
test_type_t *p2;
上面的结构体各字段在内存中布局如下:
如果反过来,我们知道一块内存布局,那也可以用一个结构体将其描述出来。在STM32单片机中,外设模块的功能寄存器就是一个特定的内存区域,有着特定的布局方式。根据器件的参考手册,就可以写出功能寄存器对应的结构体。
在STM32单片机中,同类外设的功能寄存器布局是相同的,通过不同的基地址来区分不同的外设。将不同的外设地址,赋值给不同的结构体指针,这些指针就代表了相应的外设。下面的图片是截取自STM32F103的参考手册,是关于GPIO寄存器基地址和寄存器的描述。
根据上面的图片,我们知道了STM32中GPIO寄存器的内存布局是下面这个样子,根据这个布局,我们可以反过来写出他的结构体定义。
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
再根据上面的基地址表格,不同的IO端口有着不同的基地址。将基地址转换成定义好的结构体类型,就得到了相应IO端口的寄存器定义。通过GPIOA->IDR的形式,就能访问各端口的寄存器了。
.......
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
.......
这个时候再回过头来看STM32单片机的寄存器定义。先定义一个结构体,用来描述对应的外设功能寄存器布局,再将外设的基地址赋值给结构体指针。在后续的代码中,如果要使用这个外设的寄存器,通过这个结构体指针就能访问了。
GPIOA->ODR = 0xff;
上面的这段代码,理解起来就是有一个名为GPIOA的变量,他的地址是GPIOA_BASE,类型是GPIO_TypeDef的指针。把GPIOA的ODR设置为0xff,相当于以GPIOA_BASE为基础,加上ODR这个字段的偏移值,把这个地址的值设置为0xff。
C语言STM32寄存器
C语言结构体与寄存器
2017-09-06
ARM, C语言
No Comments
xtoolbox
单片机开发中免不了会与寄存器打交道。在51,AVR单片机中,会有一个头文件将寄存器的地址定义成更容易阅读的宏,在C语言中通过宏定义来访问寄存器。
而在STM32单片机中,寄存器不再是地址转化成宏定义的形式了,变成了一个结构体。对C语言不熟的同学可能会感到困惑。其实这样的定义方式,对于STM32单片机的寄存器设计而言,是更合理的一种方式。
在C语言中,结构体对应着一种内存布局方式。定义一个结构体变量,表示申请了一块内存空间,这块内存将按照结构体定义的方式来布局。而定义一个结构体指针,表示这个指针指向的区域,会按照结构体定义的方式来布局。
上面的结构体各字段在内存中布局如下:
如果反过来,我们知道一块内存布局,那也可以用一个结构体将其描述出来。在STM32单片机中,外设模块的功能寄存器就是一个特定的内存区域,有着特定的布局方式。根据器件的参考手册,就可以写出功能寄存器对应的结构体。
在STM32单片机中,同类外设的功能寄存器布局是相同的,通过不同的基地址来区分不同的外设。将不同的外设地址,赋值给不同的结构体指针,这些指针就代表了相应的外设。下面的图片是截取自STM32F103的参考手册,是关于GPIO寄存器基地址和寄存器的描述。
根据上面的图片,我们知道了STM32中GPIO寄存器的内存布局是下面这个样子,根据这个布局,我们可以反过来写出他的结构体定义。
再根据上面的基地址表格,不同的IO端口有着不同的基地址。将基地址转换成定义好的结构体类型,就得到了相应IO端口的寄存器定义。通过GPIOA->IDR的形式,就能访问各端口的寄存器了。
这个时候再回过头来看STM32单片机的寄存器定义。先定义一个结构体,用来描述对应的外设功能寄存器布局,再将外设的基地址赋值给结构体指针。在后续的代码中,如果要使用这个外设的寄存器,通过这个结构体指针就能访问了。
上面的这段代码,理解起来就是有一个名为GPIOA的变量,他的地址是GPIOA_BASE,类型是GPIO_TypeDef的指针。把GPIOA的ODR设置为0xff,相当于以GPIOA_BASE为基础,加上ODR这个字段的偏移值,把这个地址的值设置为0xff。
C语言STM32寄存器