2. volatile关键字:在嵌入式编程中,volatile关键字是非常重要的。它告诉编译器,该变量可能会被意外(即编译器无法预知的)地改变。这通常发生在硬件寄存器映射到内存的情况中,硬件的状态可能会在任何时候改变,而编译器可能认为这些变量的值没有改变,从而进行优化,导致程序出错。
例如:
————————————————
版权声明:本文为CSDN博主「Kmugi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_74112520/article/details/133235860
2. volatile关键字:在嵌入式编程中,volatile关键字是非常重要的。它告诉编译器,该变量可能会被意外(即编译器无法预知的)地改变。这通常发生在硬件寄存器映射到内存的情况中,硬件的状态可能会在任何时候改变,而编译器可能认为这些变量的值没有改变,从而进行优化,导致程序出错。
例如:
volatile int *p = (volatile int*) 0x8000; // 假设0x8000是某个硬件寄存器的地址
*p = 1; // 对硬件进行写操作
int x = *p; // 从硬件读取数据
1
2
3
在这个例子中,p是一个指向volatile变量的指针,这意味着编译器不会对其进行优化,每次访问*p都会直接从硬件读取数据,而不是从缓存中读取。
以上是在嵌入式C程序中经常看到register和volatile关键字的原因和用法。
————————————————
版权声明:本文为CSDN博主「Kmugi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_74112520/article/details/133235860
d1.3 编写程序
接下来我们打开keil软件,创建一个新的工程
1.使用Proteus仿真软件创建51单片机工程并运行
1.1安装Proteus仿真软件
首先我们下载Proteus仿真软件 点击下载
提取码:zmcd
下载完成之后,我们将压缩包解压得到以下文件我们点击下面的.exe文件运行,出现如下界面
这里选择合适的安装路经进行安装,点击Next点击Next开始安装
安装完成以后桌面出现以下图标
点击打开,Proteus安装完成
点击进入汉化补丁
复制上面的文件,进入Proteus安装目录
将安装目录中的此文件夹替换成我们复制的文件夹重启软件,汉化完成
(2)如何创建工程
在Proteus中创建一个工程(包含三个窗口:源代码窗口、PCB窗口、原理图窗口)
在原理图窗口中用到如下原件:
AT89C51
LED-YELLOW(8个)
RES(8个)
并用一条总线和多条结点连接起来,接入一个电源,在连线标号模式下给线路命名(P0、P1、P3......)。
1.3 编写程序
2.创建工程
打开 Keil 软件,点击 Project → New uVision Project …
在搜索框内输入 AT89C51 ,再选中 AT89C51 芯片,然后点击 OK
点击“是”
代码编写
打开Keil5,创建新工程,并创建test.c文件,编写下列代码:
//宏定义,用于存放stm32寄存器映射
#define PERIPH_BASE ((unsigned int)0x40000000)//AHB
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
//GPIOA_BASE=0x40000000+0x10000+0x0800=0x40010800,该地址为GPIOA的基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
//GPIOB_BASE=0x40000000+0x10000+0x0C00=0x40010C00,该地址为GPIOB的基地址
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
//GPIOC_BASE=0x40000000+0x10000+0x1000=0x40011000,该地址为GPIOC的基地址
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
//GPIOD_BASE=0x40000000+0x10000+0x1400=0x40011400,该地址为GPIOD的基地址
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
//GPIOE_BASE=0x40000000+0x10000+0x0800=0x40011800,该地址为GPIOE的基地址
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
//GPIOF_BASE=0x40000000+0x10000+0x0800=0x40011C00,该地址为GPIOF的基地址
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
//GPIOG_BASE=0x40000000+0x10000+0x0800=0x40012000,该地址为GPIOG的基地址
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define LED0 MEM_ADDR(BITBAND(GPIOA_ODR_Addr,8))
//#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8
//定义typedef类型别名
typedef struct
{
volatile unsigned int CR;
volatile unsigned int CFGR;
volatile unsigned int CIR;
volatile unsigned int APB2RSTR;
volatile unsigned int APB1RSTR;
volatile unsigned int AHBENR;
volatile unsigned int APB2ENR;
volatile unsigned int APB1ENR;
volatile unsigned int BDCR;
volatile unsigned int CSR;
} RCC_TypeDef;
#define RCC ((RCC_TypeDef *)0x40021000)
//定义typedef类型别名
typedef struct
{
volatile unsigned int CRL;
volatile unsigned int CRH;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
volatile unsigned int BRR;
volatile unsigned int LCKR;
} GPIO_TypeDef;
//GPIOA指向地址GPIOA_BASE,GPIOA_BASE地址存放的数据类型为GPIO_TypeDef
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
void LEDInit(void)
{
RCC->APB2ENR|=1<<2; //GPIOA 时钟开启
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;
}
//粗略延时
void Delay_ms(volatile unsigned int t)
{
unsigned int i,n;
for (n=0;n<t;n++)
for (i=0;i<800;i++);
}
int main(void)
{
LEDInit();
while (1)
{
LED0=0;//LED熄灭
Delay_ms(500);//延时时间
LED0=1;//LED亮
Delay_ms(500);//延时时间
}
}
2.4编译烧录代码并查看结果
点击编译,0报错,0警告,点击烧录,开始烧录程序
3.嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器—>对应相关管脚)的操作有哪些相同与差别?
嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器—>对应相关管脚)的操作有以下相同和差别:
相同点:
两者都是通过对存储单元进行读写操作来实现数据的修改。
在C语言中,无论是对内存变量还是对外部设备寄存器,都可以使用类似的语法进行操作,例如“*”、“&”等指针运算符。
不同点:
存储空间不同:内存变量存储在RAM中,而外部设备寄存器通常存储在特定的地址空间中,如I/O空间或内存映射I/O空间。
存取速度不同:对内存变量的访问速度通常比对外部设备寄存器的访问速度快。
操作方式不同:对内存变量的操作可以通过指针或数组等方式进行,而对外部设备寄存器的操作通常需要使用特定的函数或指令。
中断和异常处理:对外部设备的操作可能会引起中断或异常,需要特殊的处理机制来处理这些情况,而对内存变量的操作通常不会。
物理实现不同:内存变量是存储在半导体材料上的电荷,而外部设备寄存器是通过电路中的电子流动来实现数据的存储和传输。
2、为什么51单片机的LED点灯编程要比STM32的简单?
(1)用51单片机开发通常是直接操作寄存器,指令集更简单,指令执行速度更快,操作寄存器的方式直接,因此编程易于上手;
(2)51单片机的资源占用更少,所需要的内存、存储空间和低级硬件资源更少,因此开发者可以更加专注于程序状态的处理,编写更加高效、简洁的代码;
(3)51单片机系统功能较为简单,从内部硬件到软件有一套完整的按位操作系统,处理对象是位,功能完备上手方便;
(4)STM32系列单片机内容丰富功能强大,但开发环境和编程语言相对复杂,并且STM32的寄存器数量和种类更多,需要对硬件有深入的了解和学习。
四、问题二
与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。
register关键字告诉编译器,这个变量需要存储在寄存器中,以便能够更快地访问。通常,register关键字只适用于较小的变量,例如循环计数器或临时变量。如果编译器无法将变量存储在寄存器中,那么它会像普通变量一样存储在RAM中。
例如:
register int i;
for(i=0; i<100; i++) {
// do something...
}
2. volatile关键字:在嵌入式编程中,volatile关键字是非常重要的。它告诉编译器,该变量可能会被意外(即编译器无法预知的)地改变。这通常发生在硬件寄存器映射到内存的情况中,硬件的状态可能会在任何时候改变,而编译器可能认为这些变量的值没有改变,从而进行优化,导致程序出错。
例如:
#include <stdio.h>
int main()
{
volatile int a = 10; //使用volatile来告诉编译器这个变量的值可能会被硬件自动改变。
printf("a = %d\n", a);
return 0;
}
1
2
3
4
5
6
7
a=10 //输出