嵌入式——关于STM32的一些理解

文章详细讲解了地址映射和寄存器原理,GPIO初始化步骤,包括工作模式和示例,以及嵌入式C中对内存和外部设备操作的区别。还对比了51单片机与STM32的编程差异,并阐述register与volatile的关键字用法。
摘要由CSDN通过智能技术生成

一、地址映射和寄存器映射原理

1. 寄存器

首先,寄存器存在于CPU当中,在各种存储器当中是离CPU最近的,传输速度最快的。当然,速度比其他存储器快,所以价格最高,存储的数据最少。

2. 地址映射

指的是,寄存器地址映射,为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射。

3. 寄存器映射

存储器本身不具备地址,所以把芯片内核所预先设定好的地址分配给寄存器,就是存储器映射,具体见下图
在这里插入图片描述

二、GPIO初始化的一般步骤

1. GPIO接口

首先,STM32F103C8T6开发板引脚如图所示
在这里插入图片描述
可知C8T6有48个IO口在这里插入图片描述

2. GPIO的工作模式

主要有八种:4种输入方式,4种输出方式,分别为输入浮空,输入上拉,输入下拉,模拟输入;输出方式为开漏输出,开漏复用输出,推挽输出,推挽复用输出。
(1)GPIO_Mode_AIN 模拟输入 (应用ADC模拟输入,或者低功耗下省电)
(2)GPIO_Mode_IN_FLOATING 浮空输入 (浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
(3)GPIO_Mode_IPD 下拉输入 (IO内部下拉电阻输入)
(4)GPIO_Mode_IPU 上拉输入 (IO内部上拉电阻输入)
(5)GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
(6)GPIO_Mode_Out_PP 推挽输出 (推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
(7)GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
(8)GPIO_Mode_AF_PP 复用推挽输出 (片内外设功能(TX1,MOSI,MISO.SCK.SS))

3.GPIO的初始化

第一步:使能GPIOx口的时钟
第二步:指明GPIOx口的哪一位,这一位的速度大小以及模式
第三步:调用GPIOx初始化函数进行初始化
第四步:调用GPIO-SetBits函数,进行相应位的置位

4.实例
对单个GPIO口的初始化:
GPIO_InitTypeDef GPIO_InitStructure;

第一步:使能GPIOA的时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

第二步:设置GPIOA参数:输出OR输入,工作模式,端口翻转速率

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_6| GPIO_Pin_7| GPIO_Pin_8; //设定要操作的管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz

第三步:调用GPIOA口初始化函数,进行初始化。

GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA

第四步:调用GPIO-SetBits函数,进行相应为的置位。

GPIO_SetBits(GPIOA,GPIO_Pin_0); //输出高
对于多个GPIO口的初始化如下
GPIO_InitTypeDef GPIO_InitStructure;

第一步:使能GPIOA,GPIOE的时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

第二步:设置GPIOA,GPIOE参数:输出OR输入,工作模式,端口翻转速率
第三步:调用GPIOA口初始化函数,进行初始化。
第四步:调用GPIO-SetBits函数,进行相应为的置位。
把第二、三、四步合并分别设置GPIOA和GPIOE
先设置GPIOA

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 第四个口,PA4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOA,&GPIO-InitST); //根据设定参数初始化GPIOA
GPIO_SetBits(GPIOA,GPIO_Pin_4); //输出高

再设置GPIOE

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 第三个口,PE3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOE,&GPIO-InitST); //根据设定参数初始化GPIOE
GPIO_SetBits(GPIOE,GPIO_Pin_3); //输出高
4. 总结

所有寄存器都需要时钟才能配置,寄存器是由D触发器组成的,只有送来了时钟,触发器才能被改写值。

任何MCU的任何外设都需要有时钟,STM32为了让用户更好地掌握功耗,对每个外设的时钟都设置了开关,让用户可以精确地控制,关闭不需要的设备,达到节省供电的目的。

三、嵌入式C程序对内存的修改操作,与对外部设备的操作的相同与差别

1. 相同之处

不管程序对内存进行操作还是对外部设备进行操作,都是最终改变硬件中物理存储单元的数据。

2.不同之处

寻址方式不同
(1)对于内存中的变量,已知地址,所以在C程序代码中直接操作寄存器改变目标地址指向的值,即寄存器映射
(2)对于外设,不知道具体地址,外设上一般有自己的片内RAM(内存),所以CPU只需要与外设的RAM交互就可以操作外设(CPU分出地址空间与片内RAM物理连接即内存映射的方式)

四、51单片机与STM32的编程区别

对于51单片机的编程来说,不用配置IO时钟,只是默认使用同一个时钟;而在STM32中,有外部和内部时钟之分,并且对每个外设的时钟都设置了开关,可以关闭不需要的设备,节能省电。

五、register与volatile区别

1. register

register声明寄存器变量。register这个关键字就是变量要求写入寄存器中。这种方式特别适用于不需要经常修改,但是会被频繁访问的变量,这样可以一定程度上提高访问效率。在ANSI C语言中,由于用了register,变量需要写入寄存器而不在内存中,所以利用&取址运算符是无法获取地址的,即,&取址运算符不能用于register修饰的变量。
注意事项:
1.register只是请求寄存器变量,不一定能够成功,
我们知道寄存器是有限的(各个部门),如果定义了很多register变量,可能会超过CPU的寄存器个数,超过容量,(各个部门超负载了)
这时候就没有办法都变为寄存器变量了,这个数量主要看机器性能决定

2.register变量必须是能被CPU所接受的类型。
这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。 (这个我们很容易理解,不是什么事都能作为大事的)

3.因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。

4.用register修饰的变量只能是局部变量,不能是全局变量。CPU的寄存器资源有限,因此不可能让一个变量一直占着CPU寄存器 (人不可能一直只处理一件事情)

5.register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,所以请注意,.register仅仅是暗示而不是命令。 这就好比寄存器可以说明这件事比较重要,但是不能直接命令CPU去做一件事

6.局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
虽然它被称作最近快的变量,但是也是比较不安全的变量,现在使用不多

#include <stdio.h>
int main()
{
    register int i;
    int tmp=0;
    for(i=1;i<=100;i++)
        tmp+=i;
    printf("总和为 %d\n",tmp);
    return 0;
}

经测试同样的代码使用register int比int快

2. volatile

volatile说明变量在程序执行中可被隐含地改变。就针对编译器而言,volatile所做的就是要求变量必须保存在内存中,而不允许被编译器进行优化放入缓存或者寄存器等等。变量如果没有加入volatile修饰,可能会被编译器优化后写入缓存或者寄存器等等。在多线程情况下,并且多个线程对这个变量会进行频繁读写的话,那就会出现脏数据。即,如果一个线程修改了这个变量的值,但是变量新值还未及时更新到缓存或者寄存器时,另外一个线程一旦访问缓存或者寄存器,就会获取没有及时更新的脏数据。就有可能出现严重的错误。

例如:

#include <stdio.h>
int pass = 1;
int main()
{
	while(pass)
	{
		;
	} 	
	return 0;
}

由于pass = 1,为真,编译器会优化此代码,将循环条件直接改为真,后续不再从内存中读取pass的值来判断循环是否停止。
加了volatile后,每次都会将pass的值导入cpu判断是否循环了。
结论
volatile忽略编译器的优化,保持内存可见性。

#include <stdio.h>
volatile int pass = 1; //加上volatile
int main()
{
	while (pass) {
	;
	} 
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值