以一个I2c EEPROM为例子
int main(void)
{
/* configure systick */
systick_config();
/* configure LEDs */
led_config();
/* configure USART */
gd_eval_com_init(EVAL_COM1);
printf("I2C-24C02 configured....\n\r");
/* configure GPIO */
gpio_config();
/* configure I2C */
i2c_config();
/* initialize EEPROM */
i2c_eeprom_init();
printf("\r\nThe I2C0 is hardware interface ");
printf("\r\nThe speed is %d", I2C0_SPEED);
if(I2C_OK == i2c_24c02_test()){
while(1){
/* turn off all LEDs */
gd_eval_led_off(LED1);
gd_eval_led_off(LED2);
gd_eval_led_off(LED3);
gd_eval_led_off(LED4);
/* turn on a LED */
led_turn_on(count%4);
count++;
if(count >= 4){
count = 0;
}
delay_1ms(500);
}
}
/* turn on all LEDs */
gd_eval_led_on(LED1);
gd_eval_led_on(LED2);
gd_eval_led_on(LED3);
gd_eval_led_on(LED4);
while(1);
}
gpio_config();
void gpio_config(void)
{
/* enable GPIOB clock */
rcu_periph_clock_enable(RCU_GPIOB);
/* enable I2C0 clock */
rcu_periph_clock_enable(RCU_I2C0);
/* connect PB6 to I2C0_SCL */
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_6);
/* connect PB7 to I2C0_SDA */
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_7);
/* configure GPIO pins of I2C0 */
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_6);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_7);
}
void rcu_periph_clock_enable(rcu_periph_enum periph)
{
RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));
}
涉及到三个部分,1是 RCU_REG_VAL(periph),2是 BIT(RCU_BIT_POS(periph)),3是 参数 periph
下面第一部分RCU_REG_VAL
#define RCU_REG_VAL(periph) (REG32(RCU + ((uint32_t)(periph)>>6)))
/* bit operations */ #define REG32(addr) (*(volatile uint32_t *)(uint32_t)(addr))
/* RCU definitions */ #define RCU RCU_BASE
#define RCU_BASE (AHB1_BUS_BASE + 0x00001000U) /*!< RCU base address */
#define AHB1_BUS_BASE ((uint32_t)0x40020000U) /*!< ahb1 base address */
从Memory map中可以看到,AHB1_BUS_BASE的内存地址定义为了0x4002 0000,也合SEPC中介绍的一致;
RUC的内存地址是RCU_BASE 也和SPEC上写的0x4002 1000对应
这里说一下,GD厂商给的SPEC中memory map是用内存地址来映射到硬件地址,
ARM是统一编址的,也就是外设和内存进行统一的编址,共同形成了4G物理地址空间(32位为例子)。
大家知道操作外设时,实际上操作的是读写设备相关的寄存器,这些与外设相关的寄存器与不同操作模式下R0-R15那些寄存器是不同的,这些寄存器并不是所谓的物理上的寄存器,实际上是所谓的IO端口,通常会有控制、状态、数据的分类。他们被连续地编址,对于其编址的方式有两种一种是IO映射、一种是内存映射。IO映射是对x86为例的复杂指令集来说的,需要专门的IO控制指令,不详谈。
内存映射是对于统一编址的精简指令集计算机ARM等来说的。具体的方法就是将IO端口映射成和内存一样的物理地址,然后与内存一起进行统一编址,或者说成为了内存的一部分,当然还可以理解内存映射的意思是可以用访问内存的方式进行IO地址的访问,内存和IO地址一起编码为cpu识别的地址哦。然后内存+IO端口地址=4GB的寻址空间。
那么接下来还有个问题就是这种编址的硬件实现?这就要详见arm的AMBA(Advanced Microcontroller BusArchitecture)了,这是目前芯片总线的主流标准。共定义了3组总线:高性能总线(Advanced High Performance Bus,AHB)、系统总线(Advanced System Bus,ASB)和外设总线(Advanced Peripheral Bus,APB)。不同的总线上挂接着不同的外设和存储器,大部分由三态门控制。比如,当AHB总线上的主设备读写从设备时,发出的地址经过AHB总线的译码器产生该地址所对应从设备的选择信号,选中从设备;这样就可以对从设备进行读写啦。或者可以这样想AHB总线上的译码器根据地址产生相应的片选信号,选中对应的设备。对于硬件的实现我们这是简单地理解以便更好地体会上述编址。涉及到具体的硬件读写操作,地址的硬件实现会提到很多的译码器、三态门、总线、还有时序等等具体情况具体分析,在此不做详述。
<
第二部分:BIT(RCU_BIT_POS(periph))
#define BIT(x) ((uint32_t)((uint32_t)0x01U<<(x)))
很简单,不说
#define RCU_BIT_POS(val) ((uint32_t)(val) & 0x1FU)
这里把val先与上0x1Fu,表示给对应寄存器设置的最大值就0x1F,你 超过这个值肯定是不对的,所以把它做个限制
第三部分 3是 参数 periph 也就是 RCU_GPIOB
/* peripheral clock enable */
typedef enum
{
/* AHB peripherals */
RCU_DMA = RCU_REGIDX_BIT(IDX_AHBEN, 0U), /*!< DMA clock */
RCU_CRC = RCU_REGIDX_BIT(IDX_AHBEN, 6U), /*!< CRC clock */
RCU_GPIOA = RCU_REGIDX_BIT(IDX_AHBEN, 17U), /*!< GPIOA clock */
RCU_GPIOB = RCU_REGIDX_BIT(IDX_AHBEN, 18U), /*!< GPIOB clock */
RCU_GPIOC = RCU_REGIDX_BIT(IDX_AHBEN, 19U), /*!< GPIOC clock */
RCU_GPIOD = RCU_REGIDX_BIT(IDX_AHBEN, 20U), /*!< GPIOD clock */
RCU_GPIOF = RCU_REGIDX_BIT(IDX_AHBEN, 22U), /*!< GPIOF clock */
RCU_TSI = RCU_REGIDX_BIT(IDX_AHBEN, 24U), /*!< TSI clock */
看这两个宏RCU_REGIDX_BIT 和 IDX_AHBEN
/* constants definitions */
/* define the peripheral clock enable bit position and its register index offset */
#define RCU_REGIDX_BIT(regidx, bitpos) (((uint32_t)(regidx)<<6) | (uint32_t)(bitpos))
/* register index */
typedef enum
{
/* peripherals enable */
IDX_AHBEN = 0x14U,
IDX_APB2EN = 0x18U,
IDX_APB1EN = 0x1CU,
IDX_ADDAPB1EN = 0xF8U,
/* peripherals reset */
IDX_AHBRST = 0x28U,
IDX_APB2RST = 0x0CU,
IDX_APB1RST = 0x10U,
RCU_REGIDX_BIT(IDX_AHBEN, 18U) => (((uint32_t)(0x14)<<6) | (uint32_t)(0x18))
很明显就是根据参数来通过一些操作处理成需要的值。
具体怎么处理刚才找了下没有找到对应的具体的寄存器表,就不说了。
回到
RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));
这里通过periph参数,1是找到需要设置的具体的内存地址,另外就是通过这参数来确定需要给对应地址设置什么值。一举两得
回到这个函数的作用上来,这个函数是 /* enable GPIOB clock */”
有人问,为什么要enable gpio clock?
因为在当前处理器上对每类外设(Cortex-M3之外的部分)都有clock的设置,你用GPIO,我只用打开GPIO的clock就可以了,其他的模块的clock我不用开,可以省功耗。
这里用到GPIOB,就只开GPIOB的clock,用到I2C 就用I2C的clock就可以了。
有人又要问,你说enable I2C的Clock我理解,因为I2C有clk线,但是你说GPIO 的clk也要enable ,我就不理解了。
以下是在网上找的的一些答案,但是我觉的说的还不是特别具体。
所有寄存器都需要时钟才能配置吧,寄存器是由D触发器组成的,只有送来了时钟,触发器才能被改写值。
任何MCU的任何外设都需要有时钟,8051也是如此(只不过8051使用统一的时钟);GD为了让用户更好地掌握功耗,对每个外设的时钟都设置了开关,让用户可以精确地控制,关闭不需要的设备,达到节省供电的目的