目录
前言
本文是适合单片机新手的,因此借助c51使用寄存器形式驱动芯片的方式下,stm32同样采用寄存器方式,最终形成对比,从而能够有更深刻的体验
一、基于c51芯片的流水灯
1.借助Proteus软件绘制电路图
本文采用低电平有效的方式,因此在lLED灯的另外一边接入的是电源,也就是,当I/O口为低电平的时候,电路接通,LED灯亮,另外c51的I/O口很多,例如P0、P1等,因此选用一个即可,最终绘制出的电路图如下所示:
2.编写代码,生成.hex文件
引入头文件
由于c51和c52功能性质相似,c52只是增强了部分功能,因此本文直接是在c52的环境下编写的
#include <regx52.h>
创建LED数组
根据低电平有效,因此将需要亮的LED灯赋值为0,其余均为1,创建数组如下:
//创建流水灯数组
unsigned char led[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
建立延时函数
因为单片机在运行的时候速度很快,因次插入延时函数可以让程序按照我们所设定的时间进行运行, 本文由于对于流水灯闪烁的间隔没有要求,因此时间可以随意设定,想要具体准确的使劲按,可以使用软件生成一个准确时间的延时函数代码,本文代码如下:
void delay()
{
int i,j;
for(i=0;i<1000;i++)
for(j=0;j<30;j++);//其中的i和j可以根据直接需求设定
}
编写主函数
本文引入一个变量k,方便实现八个流水灯持续交替闪烁,也就是当第八个灯亮完之后,恢复程序,继续使第一个灯继续亮,在每一个灯亮完之后加一个延时,更加方便观察,减缓闪烁频次
//主函数
void main()
{
int k=0;
while(1)
{
P1=led[k];
delay();
k++;
if(k==8)
k=0;//建立循环,当最后一个灯亮后,使第一个灯继续亮
}
}
最后所有代码整合起来,如下:
#include <regx52.h>
//创建流水灯数组
unsigned char led[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
//延时
void delay()
{
int i,j;
for(i=0;i<1000;i++)
for(j=0;j<30;j++);
}
//主函数
void main()
{
int k=0;
while(1)
{
P1=led[k];
delay();
k++;
if(k==8)
k=0;//建立循环,当最后一个灯亮后,使第一个灯继续亮
}
}
运行文件,生成.hex文件
点击魔法棒在output一栏下,按如下选择
点击运行,随后将生成一个.hex文件
3.烧录.hex文件,实现仿真
双击仿真软 件中的芯片,浏览栏中选中上诉生产的.hex的文件,开始仿真,运行结果如下:
4.程序优化
上文将LED灯以数组的形式进行点灯,但是通常情况下,是使用移位的方式,代码如下:
#include <regx52.h>
//延时
void delay()
{
int i,j;
for(i=0;i<1000;i++)
for(j=0;j<30;j++);
}
void main()
{
int i;
while(1)
{
for (i=0;i<8;i++)
{
P1=~(0x01<<i);
//每次向左移i位,空出来的补0;由于本文是低电平有效,所以最终取反
delay();
}
if(i==8)i=0;
}
}
以上运行效果与之前相同。
二、基于stm32芯片的流水灯
本文的寄存器方式主要是为了能够和C51芯片进行对比。
(1)绘制电路图
本文使用GPIOA的PA0引脚对LED灯进行控制
(2)编写代码
本文选用的是APB2外设使能寄存器(RCC_APB2ENR),同时输出时选用的是PA0引脚
#include "stm32f10x.h" // Device header
int main(void)
{
while(1)
{
//配置时钟RCC 0100
*(unsigned int *)0x40021018|=(1<<2);
//配置位寄存器CRL 0001
// 配置PA0为推挽输出模式
*((unsigned int *)0x40010800) &= ~(0x3 << 0); // 清除PA0的模式和配置位
*((unsigned int *)0x40010800) |= (0x2 << 0); // 设置PA0为推挽输出模式
//配置数据寄存器ODR
(*(unsigned int *)0x4001080C) &=~(1);//寄存器
//40010800[GPIOA的首位地址]+0Ch[ODR的地址偏移]=GPIOA_ODR0[PA0的地址]
//1的二进制为0000 0001
}
}
配置时钟
RCC的偏移地址:0x18
RCC的初始地址:0x40021000
RCC_APB2ENR地址:0x40021018
配置数据寄存器
GPIOA初始地址:0x40010800
偏移地址:0x0Ch
GPIOA_ODR0地址:0x4001080C
配置位寄存器
GPIOA初始地址:0x40010800
偏移地址:0x00
GPIOA_CRL0地址:0x40010800
(3)程序仿真结果
经过Proteus仿真结果如图所示
(2)实物烧录结果
三、问题的思考
嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?
嵌入式C程序代码对内存中的变量的修改操作与对外部设备(寄存器)的操作有一些相同和一些差异。相同之处在于它们都涉及到对内存和寄存器的读写操作。无论是修改内存中的变量还是配置外部设备的寄存器,都需要通过读取和写入操作来实现。
然而,它们的差异在于目标和访问方式。对内存中的变量进行修改主要是为了在程序中存储和处理数据。在嵌入式系统中,使用RAM(随机存取存储器)来存储变量数据是常见的做法。通过C语言中的变量赋值语句,可以直接将新值写入变量所在的内存地址。
对外部设备的操作通常涉及对寄存器的访问。外部设备(例如芯片、传感器或接口芯片)通常会包含一组寄存器,用于配置设备的功能和状态。通过向寄存器写入特定的值,可以控制设备的行为。在嵌入式C程序中,通过给寄存器赋值,可以实现对外部设备的操作。
为什么51单片机的LED点灯编程要比STM32的简单?
51单片机和STM32是两种常见的嵌入式系统开发平台。在51单片机上进行LED点灯编程相对比较简单的原因有以下几点:
51单片机的架构相对较简单,指令集和寄存器设计也较为简洁。因此,编写代码并操作寄存器来控制LED的点灯相对容易理解和实现。
在51单片机上,通常使用汇编语言来编写嵌入式代码,与C语言相比,汇编语言更直接地操作寄存器和设备。这使得对51单片机进行底层设备编程更加简单。
在开发过程中,51单片机的资源(例如GPIO引脚)通常较为有限,这使得其编程相对简单。对于简单的操作,可以直接使用特定的寄存器位来控制LED点亮或熄灭。
相比之下,STM32是一种更强大且复杂的嵌入式系统开发平台,具有更多的功能和资源。STM32系列芯片通常配备了更多的外设和功能模块,这要求程序员在编程时需要更深入地理解与配置这些功能。此外,STM32开发通常使用C语言编写,这为开发者提供了更高级的抽象和方便的开发环境,但同时也存在更多的库函数和API需要学习和使用。综合而言,STM32上的开发相对复杂,但也能提供更多更灵活的功能和扩展性。
register和volatile 关键字
在嵌入式C程序中,经常会使用`register`和`volatile`这两个变量修饰符来对变量进行特殊的声明和使用。
1)`register`关键字:
`register`关键字用于向编译器建议将变量存储在寄存器中,以提高访问速度和效率。但是,`register`关键字只是对编译器的建议,并不能强制将变量存储在寄存器中,因为寄存器的数量和可用性是由编译器决定的。
示例代码:
register int x; // 声明一个建议存储在寄存器中的变量x
需要注意的是,`register`关键字不能用于全局变量或函数参数,因为全局变量和函数参数已经有它们自己的存储约定。
2)`volatile`关键字:
`volatile`关键字用于告诉编译器该变量可以被意外地修改,这样编译器就不会进行某些优化,以确保对该变量的访问具有可见性。在嵌入式系统中,`volatile`通常用于访问外部设备的寄存器,或在多线程/中断环境中访问共享数据。
示例代码:
volatile int flag; // 声明一个可变的标志变量flag
在使用`volatile`关键字修饰的变量时,编译器不会进行某些优化,例如对变量的缓存读取和编译器重排等。这是因为`volatile`变量的值可能会在未经程序代码直接操作的情况下发生变化,例如由硬件中断修改。
总结来说,`register`关键字建议把变量存储在寄存器中以提高访问效率,而`volatile`关键字用于声明可变的变量,并告诉编译器不要做某些优化,以确保访问的可见性。在嵌入式C程序中,使用这些关键字可以更好地管理和访问硬件资源。
四、总结
在寄存器方式下,c51会比stm32简单,因为c51的端口、功能没有stm32复杂,因此是stm32通常采用库函数或者HAL的形式。