系列文章目录-认识ARM开发
/ARM开发初学目录:/
第2章:#STM32 2.寄存器位操作方法及GPIO点亮一颗LED
第1章:#STM32 1.从C51驱动一颗LED到认识ARM_GPIO(最浅显入门)
Lip:从中颖8位迁移到Cortex M3东软IC了,也逐步从51转到STM32的学习
文章目录
一.场景
上手一个ARM工程,结合“寄存器思想”,初步了解GPIO设定,“51”与“ARM”牵线搭桥
二.理论与实际结合
1. 代码比较(驱动LED输出为例)
//. 8位单片机
P1_7 = 1; //main()函数用户设置输出高电平
sbit P1_7 = P1^7; //SH79F6481.h文件
sfr P1 = 0x90; //SH79F6481.h文件
以上,sfr寄存器赋值操作,取P1内存地址操作,等效于赋值于变量,便于操作。
//. 32位ARM
GPIO_InitTypeDef GPIO_Init;
RCC->APB2ENR |= ( (1) << 3 );
GPIO_GPIO_Init.GPIO_Pin = GPIO_Pin_0;
GPIO_GPIO_Init.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_GPIO_Init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_GPIO_Init);
GPIO_SetBits(GPIOB,GPIO_Pin_0);
GPIO_ResetBits( GPIOB,GPIO_Pin_0 );
咋一看相当懵逼,从51到ARM脱节的地方就在于找不到对应的I/O引脚,寄存器的地址也不好找。在不破坏程序既有定义的前提下,不能直接写1/0操作
2.引导篇
Tip:常用的几种工程开发模式:汇编语言寄存器操作、c语言寄存器操作、标准库开发、HAL库开发。从51转到ARM最凸出的变化就是库开发,及寄存器思想
2.1 指针地址访问内存地址
对地址强制类型转换为指针变量(unsigned int*),在对指针*解引用操作,赋值
// GPIOB_ODR 端口全部设为逻辑1,ODR:1高 0低
*(unsigned int*) (0x40010c0c) = 0xFFFF;
为什么是GPIOB输出高?
以下为官方STM32F1xxx datasheet
2.1+1 定义寄存器别名访问内存地址
提高代码可读性,重写如下:
// GPIOB_ODR 端口全部设为逻辑1,ODR:1高 0低
#define GPIOB_ODR (unsigned int *)(GPIOB_BASE + 0x0c)
*GPIOB_ODR = 0xFF;
再进一步,指针操作*标准化,修改为:
// GPIOB_ODR 端口全部设为逻辑1,ODR:1高 0低
#define GPIOB_ODR *(unsigned int *)(GPIOB_BASE + 0x0c)
GPIOB_ODR = 0xFF;
2.1+2 封装总线及外设地址
a.寄存器映射概念
片上外设合计3条总线:APB1/APB2/AHB
//
总线名称 | 相对外设的地址偏移 | 总线基地址 |
---|---|---|
APB1 | 0 | 0x4000 0000 |
APB2 | 0x0001 0000 | 0x4001 0000 |
AHB | 0x0001 8000 | 0x4001 8000 |
//
外设名称 | 相对APB2地址偏移 | 外设基地址 |
---|---|---|
GPIOB | 0x0000 0C00 | 0x4001 0C00 |
所以,现在明白了上面实例操作的是:APB2总线上的GPIOB外设的ODR寄存器
b.宏定义总线及外设地址
// 外设基地址
#define PERIPHERAL_BASE (unsigned int)(0x4000 0000)
//总线基地址
#define APB1PERIPHERAL_BASE PERIPHERAL_BASE
#define APB2PERIPHERAL_BASE (PERIPHERAL_BASE + 0x0001 0000)
#define AHBPERIPHERAL_BASE (PERIPHERAL_BASE + 0x0002 0000)
//GPIO外设地址
#define GPIOB_BASE (APB2PERIPHERAL_BASE + 0x0C00)
//寄存器地址GPIOB_
#define GPIOB_ODR (GPIOB_BASE + 0x000C)
#define GPIOB_BSRR (GPIOB_BASE + 0x0010)
以上,已经用指针将立即数强制转换为了外设地址,对具体寄存器可以直接读写操作,如:
// GPIOB_BSRR的BR0置1(PB0引脚输出0)
*(unsigned int *)GPIOB_BSSR = 1 << (16 + 0);
// GPIOB_BSRR的BS0置1(PB0引脚输出1)
*(unsigned int *)GPIOB_BSSR = 1 << 0;
2.1+3 指针访问寄存器
运用C语言结构体定义寄存器。因结构体内变量是连续的内存地址,外设的寄存器也是连续地址,一个寄存器占4个字节地址,与每个寄存器地址相符。此时仅需申明结构体的首地址则每个寄存器地址也会被定义。
typedef unsigned int uint32_t;
typedef unsigned short int uint16_t;
//结构体定义寄存器列表
typedef struct{
uint32_t CRL; //GPIO端口配置低寄存器 地址偏移:0x00
uint32_t CRH; //GPIO端口配置高寄存器 地址偏移:0x04
uint32_t IDR; //GPIO数据输入寄存器 地址偏移:0x08
uint32_t ODR; //GPIO数据输出寄存器 地址偏移:0x0C
uint32_t BSRR; //GPIO位设置/清楚寄存器 地址偏移0x10
uint32_t BRR; //GPIO端口位清楚寄存器 地址偏移:0x14
uint32_t LCKR; //GPIO端口配置锁定寄存器 地址偏移:0x18
}GPIO_InitTypeDef
//定义一个结构体指针变量GPIOx,并将GPIOB基地址给予首地址
GPIO_InitTypeDef *GPIOx;
GPIOx = GPIOB_BASE;
GPIOx -> ODR = 0xFFFF;
2.1+4 指针访问寄存器plus版
宏定义文件下将基地址强制转化为指针变量
仅需修改基地址GPIOx即可
//使用结构体GPIO_InitTypeDef将地址强制转化为指针
#define GPIOB (GPIO_InitTypeDef *) GPIOB_BASE
直接访问寄存器:
GPIO -> BSRR = 0xFFFF;
GPIO -> ODR = 0xFFFF;
总结
- Cortex M3内核与51最明显的区别是对寄存器的操作方式,转化为代码形式则是结构体与指针的结合使用。通过对指针地址*操作,进行内存单元的赋值等处理。
- 附简易的指针表达:
int i = 10; // 声明并定义变量i
int *p = &i; // int型指针指向i对应的内存地址,*操作符无运算意义,仅指针表达,&取地址
//规范使用int *p而不是int* p,否则以下会有误解:int* p1,p2;//int型指针变量p1,int型变量p2
*p = 20; // *表明解引用,改变对应内存地址内的数值
printf("%d\n",i); // 此时i的数值已经是20