Keil4与Proteus联合仿真实现流水灯实验&&STM32实现通过寄存器实现流水灯

目录

一、Keil4与Proteus联合仿真实现流水灯实验

1.  安装 Proteus&&Keil4

2.  在Proteus创建工程,完成原理图的绘制

      ​编辑

3. 在keil4创建新工程,并且编写一个流水灯程序

4. 将Keil4生成的hex文件导入Proteus,进行仿真

5.  仿真结果

二、STM32流水灯实验(程序解释在第三大点)

         1. 安装Keil5软件

         2.  新建工程文件,编写、编译STM32流水灯程序

         3. Jlink烧录程序

         4. 实验效果

三、STM32F103系列芯片的地址映射和寄存器映射原理,GPIO初始化设置的一般步骤及流水灯程序解释

         1. 寄存器映射简介

2. 操作GPIO端口的一般步骤

3. 程序编写

       1. GPIOA端口时钟使能

2. 端口配置

3. 端口操作,具体程序实现 

  4. 思考

    1. 嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?

    2. 为什么51单片机的LED点灯编程要比STM32的简单?

四、嵌入式register、volatile 关键字的作用

1. register关键字

2. volatile关键字

五、总结


 

一、Keil4与Proteus联合仿真实现流水灯实验

1.  安装 Proteus&&Keil4

       proteus安装可参考 Protues 8.9 仿真(安装包和安装步骤) - 知乎 (zhihu.com)

       keil4安装可参考         Keil uVision4保姆级安装教程 - 知乎 (zhihu.com)

2.  在Proteus创建工程,完成原理图的绘制

        1. 打开Proteus,点击新建工程

          c38d87b65bac4fc9bdbb4f492059cc60.png

          8ef27ce2646a4a4a8833d785fd2adccf.png

         2. 名称处输入工程文件名称,路径选择工程保存的路径,

          点击下一步。

      b790b7e67ba745c5a85bcce9587d1d6a.png

       3.  选择从选中的模板中创建原理图,选择默认:DEFAULT

         点击下一步。

       427d07e7d2884fda8b6b3ce6b28b6e55.png

      4. 由于此仿真实验不需要绘制PCB,所以不需要创建PCB布板设计,

       点击下一步。

      91dc75ac36f440bca0e1cae3976e3bc6.png

     5. 选择创建固件项目,在第二个选项栏的下拉选项中选择AT89C51,其他的默认都是51,

     点击下一步。

     a005bb4dda054b9b9fda96431754b78e.png

    点击完成。

    6. 进入原理图绘制界面:

   0afc468d984241b3b40736dcf8522bbb.png

    7. 在界面中左上角有标识符P,P表示放置元器件,里面有很多仿真电子器件,点击P

   c51653c30b9c4f3cb82d18f5b8f03967.png

  8. 在关键词处搜索“LED-”,找到发光二极管,可以看到有很多型号,选择绿色发光二极管

 75631d1466274c39809318afd20d3343.png

  9. 开始放置LED灯,旋转角度

  8a67cb817c4342bd91a050c9ed0ebed7.png

 10. 一端连接到P2-0接口,另一端准备接地,接地在左边选择栏中的终端处,选择选择ground

   acfb683f25254f158a46fe20e1042dc1.png

   808dedc750c14ceb8a1c9732c8bbf528.png

   6f6348c5e5a44a8895b19c072e812ebc.png

  11 .放置8个led灯,分别连接到P2-0~7端口采用共阴极接法

   a72e93764df549a9b7af4d5fc0980a0c.png

   至此,原理图创建完成。

3. 在keil4创建新工程,并且编写一个流水灯程序

      1. 打开keil4,点击工程project,选择创建一个新的工程,New uVision Project

       046a2a9e3dfa4600b8ff5831a82cf358.png 

       2. 在新弹出的窗口中输入工程文件名称和选择工程的保存路径

      ed2ceb2e71a74e0aa9bfc5a53514fc2d.png

      3. 在弹出的窗口中选择数据库,选择Atmel,在下拉选项中选择AT89C51

       513778e5254242a48e23150a22f693c8.png

        5198939d4cd94bbd9f51d2371b8e65ed.png

       4. 在弹出的窗口中选择  是

       ed5b3f0f203548bfa609f4f112d6c718.png

       5. 进入主页面,创建一个新文件,点击保存,命名为led.c

        f8173941286147809bedec5bc02094df.png

        9ce247cdb8044e8493045d9191b14d9a.png

       5cce3840d4d7488c96633602067ef76c.png

       6. 在里面编写程序

       75ef039737264c86b249597a79491770.png

       7. 在文件夹下添加led.c文件

       右键点击文件夹,点击Add Files to Group ……

       8c7609959a094580bd0453c77008c9ac.png

      8. 点击魔术棒,在output中勾选输出hex文件

       18be97d0412d41c59a36aa36df0170a1.png

      b3dba40dc3f444e59354777a21ae64b7.png

     9. 编译文件,生成hex文件

    依次点击  translate  build rebuild   

    26f984a13b514a1bb24d5e47a0ccc3a6.png

   查看编译输出文件,build output:

   68581d4708a7466f88b947ca337623fe.png

    无警告和错误,且生成了hex文件,编译通过。

4. 将Keil4生成的hex文件导入Proteus,进行仿真

     1. 双击51单片机

      2955dc08b15e4dba9badfeac2c6b0c53.png

     2. 在Programe File 中选择刚才Keil4生成的hex文件

     8e8fe2fefefe4e0c90954a8bc5632f9c.png

    3.  点击右下角仿真按钮,进行仿真

    2ce3ac0fb5da4e75844c2b9fb389bbbb.png

5.  仿真结果

     bd0a2b6b740b4296bb3d91ecc1732e96.gif

 

二、STM32流水灯实验(程序解释在第三大点)

  1. 安装Keil5软件

        Keil5安装参考:keil5安装教程_Matcha_ice_cream的博客-CSDN博客

  2.  新建工程文件,编写、编译STM32流水灯程序

          1. 创建新的工程,点击project ,new uvision project

               5db866d1e9d442109030d8715183c9a5.png

           2. 输入工程文件名,选择工程路径

              73637e357b4a46c29706303bfaa69ea4.png

            3. 选择stm32f103zet6对应的库文件,搜索stm32f103ze,点击ok

               e4ebfe12cbcf418eaba9b32d8960a4dd.png

            4. 选择ok

             18a308bbe64f401189857c3c9ae9baf6.png

            5. 创建led.c文件,并且进行编译,和keil4操作基本一样,不再赘述

             在led.c中输入如下代码:

              

#define PERIPH_BASE           ((unsigned int)0x40000000)//AHB的地址
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000) //APB2地址
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800) //GPIOA地址
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C   //GPIOA_ODR地址
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
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  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;
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
void delay(unsigned int  time)
{
 unsigned int i=0;
	while(time--)
	{
   i=12000;
   while(i--);		
	}
}

int main(void)
{
	RCC->APB2ENR|=1<<2;      	  	 
	GPIOA->CRL&=0x00000000;
	GPIOA->CRL|=0x33333333;	
  GPIOA->ODR=0x01;      
  while(1)
	{
	delay(1000);
	GPIOA->ODR=GPIOA->ODR<<1;
	if(GPIOA->ODR==0x0100)
	{
		GPIOA->ODR=0x01;
}
}
}

         6. 进行编译

            3bbd5875ca914a0a9d42579442c6048c.png

3. Jlink烧录程序

          1. 安装Jlink驱动

                参考网站:jlink驱动安装_洛殿的博客-CSDN博客

           2. 配置Jlink烧录环境

              点击魔法棒

            cf58a922256442489f4f751a6910ec90.png

           点击debug

          ca397d846b1a4b349f74c1c5a0fabc27.png

          选择ST-LIN/v2 端口选择sw

         aea2eb0ec4aa4f7b87efd686a0b130dc.png

       3. 烧录程序

         点击下载程序

         2a40016d31284b4491f92e5e660359ce.png

4. 实验效果

        ​​​:连接图4e5d9bbf80b84db4ae6dd581c2759f1a.png

         实验效果: 

STM32流水灯实验效果

 

 

三、STM32F103系列芯片的地址映射和寄存器映射原理,GPIO初始化设置的一般步骤及流水灯程序解释

  1. 寄存器映射简介

       stm32采用的是Cortex-M3处理器,32位操作系统,具有:

       32位寄存器、32位内部数据通路以及32位总线接口。那么其寻址能力就是2^32bit=4G

       Cortex-M3采用的是哈佛计算机结构,即数据、指令分开存储,具有数据存储器和指令存储器

      其最大4G的地址分配如下:

    45067700205649a7a3914b1f9fa00ace.png

 

24e3dc78d11243ea9236cfa84f820966.png

       将4GB的大小分为代码区0.5GB、SRAM0.5GB、外设0.5GB、外部存储器RAM1G、外部设备   1G和私有外设0.5GB

       一个典型的CPU是由运算器、控制器、寄存器等器件构成,这些器件都通过内部总线相连,CPU中,运算器进行信息处理、寄存器进行信息存储、控制器控制各种器件进行工作、内部总线连接各种器件,在它们之间进行数据的传输。

     寄存器的作用是信息存储,通常存储的类型有数据、指令和地址。从本质上来讲,寄存器都是存放的01二进制编码,我们可以把其当作数据、指令和地址进行处理,根据不同的情况和需求,做对应的变换。

    简单来说:

    寄存器映射就是通过给绝对地址取一个别名,然后通过寄存器,修改该绝对地址的状态。

      我们知道,一个单片机是通过CPU、存储器和许多外设组成

      如下是stm32的系统架构

dc2619ee4b4e4c1cb19df3c43e177588.png

  从图中可以看出,CPU连接有ICode总线、Dcode总线、系统总线和DMA总线

  ICode总线连接着Flash接口,通过FLASH可以烧录程序

  DCode,System和DMA通过总线矩阵连接着各种外设,如SRAM、FSMC

  通过总线矩阵,又引出了AHB总线,AHB总线通过桥接的方式分为APB1和APB2总线

  APB1和APB2连接着不同的外设,我们需要操作对应的外设,就要通过总线连接,找到对应地址

 stm32参考手册中给出了寄存器映像,即外设的地址分布:

924cecf055b64355bc20646ede98ebc0.png

  这里没有给完整,详情可以在参考手册中查看。

  GPIO也是外设,其地址分布在0x40000000~0x5fffffff之内,和上面从Cortex的存储映像符合

  那么如果我们要操作GPIOA的某一个端口,就需要修改对应寄存器的值,从图中可以找到寄存器    的范围在0x40010800-0x400100BFF

2. 操作GPIO端口的一般步骤

     1. 时钟使能

        为什么要时钟使能?因为外设的状态都是保存在寄存器中的,如果我们要修改外设的状态,那么就要修改对应寄存器的值,stm32内部寄存器都是采用的D触发寄存器,因此需要时钟使能才能使能D触发,才能修改寄存器中的值,从而改变外设的状态

     2. IO口初始化(端口配置)

        stm32的输出口有很多种模式,IO口初始化是用配置系统最开始的模式

     3. 操作IO口

       操作IO口一般是在循环中要完成的操作

3. 程序编写

    1. GPIOA端口时钟使能

    根据上面的操作步骤首先是时钟使能

    通过查看stm系统架构,知道了GPIOA是连接在APB2这条总线上,因此我们使能GPIOA口需要      操作APB2的外设时钟使能寄存器,查看APB2的外设时钟使能寄存器:
   63700235d72744a5aa6b19df7afff746.png

  因此我们修改APB2时钟使能寄存器的第二位为1,就成功使能GPIOA端口了。

  那么APB2的使能寄存器在哪里呢?

98fdd29b50d0481eb058f9aee521cef0.png

 通过查表发现在AHB总线 复位和时钟控制 RCC的起始地址为0x4002100,

 为什么APB2的时钟使能控制寄存器在AHB1上呢?

 因为系统架构是通过AHB桥接成APB1和APB2的,换句话说就是通过AHB上面的时钟频率分频给   APB1和APB2

 RCC总共有8个寄存器:CR 、CF、GR、CIR、APB2RSTR、APB1RSTR、AHBENR、   APB2ENR、APB1ENR、BDCR、CSR,我们需要使能GPIOA时钟,用到的是APB2ENR寄存器

7b2cb0ab55a647e3bc81b85037b1b557.png

其相对于RCC的偏移地址为0x18,那么使能寄存器映射地址就为:

0x40021000+0x18=0x40021018

在上面的程序中是通过volatile关键字声明了8个寄存器的RCC结构体完成的,其作用和直接声明寄存器映射地址效果是一样的:

1b2276d529514c679bd7ef38bc6a27fa.png

可以简化为:#define APB2ENR  ((unsigned int)0x40021018)

下一步就是使能时钟了,就是将该寄存器第二位设置为1:

e53b95f258e94333ba74285bafeb8146.png

  现在就已经完成了GPIOA端口时钟使能了。

2. 端口配置

 stm32的GPIO口总共分为8种模式

 输入:上拉输入、下拉输入、浮空输入、模拟输入

 输出: 复用推挽输出、普通推挽输出、复用开漏输出、普通开漏输出

 在此次实验中所需要配置的类型为普通推挽输出,那么如何配置呢?还是通过操作寄存器配置

 需要用到的两个寄存器分别为端口配置低寄存器(端口配置高寄存器),端口输出数据寄存器

 那么首先应该找到这两个寄存器的映射地址:

 这两个寄存器都在GPIOA寄存器组里面,因此首先要找到GPIOA寄存器的映射地址:

c3272d29bba642d88d8a47b3fd18cf24.png

 在表中APB2的起始地址为0x4001 0000,GPIOA寄存器组的起始地址为0x40010800

 其次找到对应的两个寄存器偏移地址:

  c0ef16307fc24585acdfea2d9a08a6bd.png

   859db2485c114997b0f1638fdd073061.png

  所以端口配置低寄存器映射地址为0x40010800+0x00=0x40010800

  端口输出数据寄存器映射地址为:0x40010800+0x0c=0x4001080c

  同样也可以通过直接声明寄存器的映射地址来完成

  在代码中也是通过创建volatile关键词的GPIO结构体完成的:

472a102894384817aa4e82965844c018.png

ad4a128ac292446e86d248c229056af1.png

前面说过,stm32是32位操作系统,端口配置低寄存器控制32个bit,每4位为一个端口,从小到大分别为PA0~PA8

3571253df77e44319338053a6e663f03.png

每个4位的高两位CNF选择配置上面说的GPIO的8种的一种模式

第两位选择输入模式或者输出模式的输出频率

7224b69279ec4a208d176ac2e2070f85.png

 我们需要配置的是通用推挽输出模式,速度选择50Mhz,因此为00 11 在16进制里面为3

  因为需要用到8个端口,所以可以将这8个端口全部设置为推挽输出,速度为50MHz:

c9cba19fd3aa4bfe88c31d63af6df64d.png

这时端口输出模式选择完成了,但是不知道输出是0,还是1,因此需要端口输出数据寄存器

1347a7bbf5cf406aada9ba1781630782.png

  一个bit对应一个端口,最小系统板只有8个端口,则其有效位位0-8,当配置为0的时候,PA0则输出低电平,配置为1的时候,则输出高电平

初始化的时候,将PA0设置为高电平,其他为低电平,即该寄存器的值可以设置为:0x0001

     38fc53a579a3493cae1f56df75a597c2.png

到这里,初始化就完成了

3. 端口操作,具体程序实现 

     完成上述程序配置后,PA的8个端口全部为推挽输出模式,并且输出速度为50Mhz

    流水灯程序还需要一个延时程序

    在程序中采用的是软件延时的方法:

     8328712dedf24b8ca920d5e8ef2da858.png

     在此次实验中,采用的是共阴极接法,8个led的阴极全部接地,阳极接PA的8个端口,当PA端       口输出高电平的时候灯亮,输出高电平的时候灯灭。

    程序思路:初始化PA0为高电平,在循环中,先延时,然后左移,PA1为高电平,再判断是否0      ~8端口是否全为0,如果是的话,则又赋值为0x0001

   程序流程图:

   10e82e336aac4bad9e71e42c1ae12170.png

  4. 思考

    1. 嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?

      对外部的设备操作是通过找到寄存器地址,然后修改寄存器的值,从而修改外部设备的状态,一般可以通过宏定义一个指针,指向寄存器的地址:

     793392ec22e84844ab932cb2e3edf099.png

     对内存的修改操作也可以通过指针变量的方式进行修改。

     寄存器是CPU内部的组成部分,通过内部总线与CPU直接相连,内存是通过外部地址总线与C        PU相连,通过外部总线寻址,通过寄存器修改变量的值比内存更快。

     

     2. 为什么51单片机的LED点灯编程要比STM32的简单?

       因此在GPIO端口配置的时候,需要时钟信号来使能D触发器(寄存器),从而修改端口状态

       51单片机的时钟只有一个,且没有进行分频等操作

       将STM32虽然也只有一个系统时钟,但是可以通过分频,将APB1或者APB2的时钟时钟频率           更改,比51单片机多一个分频的操作,所以在开始修改寄存器时,51单片机直接能对寄存器           进行操作,而stm32则需要进行时钟配置使能。

       此外,stm32是32位单片机,而51是位8单片机,stm32能够实现的功能能多,也就意味着其更         复杂。

四、嵌入式register、volatile 关键字的作用

1. register关键字

     register声明的变量直接存在在CPU的寄存器中,程序可以直接对该变量进行读写。访问内存的变量比访问寄存器的变量更慢,因此将需要快速访问的变量放入寄存器能提高程序的效率。

 比较如下了两个c语言程序:

for(int i=0;i<=100000000;i++)
{




}

for(register int i=0;i<100000000;i++)
{



}

 

这里访问了变量i很多次,如果把i定义在内存中,那么CPU每次都会从内存中访问i,这样相比在寄存器中就会很慢;如果把i用 register关键字修饰,那么访问CPU就会通过寄存器的方式访问它,从而能提高程序运行的效率。

2. volatile关键字

  volatile声明的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程,    遇到这个关键字声明的变量,编译器对访问改变量的代码不在进行优化,从而可以提供对特殊地    址的访问。每次使用它的时候必须从内存中取出它的值,不会从寄存器中使用,从而保证了对某    特殊地址的稳定访问。

 在嵌入式开发中,往往在宏定义的时候会看到这样的代码:

#define GPIOB_BASE ((unsigned int)0x40010800)

这样的好处便是在嵌入式编程中,硬件寄存器和内存映射设备常常需要使用volatile关键字,以确保编译器不会对其进行优化,从而保证与硬件的交互是准确的。

五、总结

 1. C51不需要分频,且为8位单片机,STM32需要时钟分频且为32位单片机,功能更复杂,C51         编写流水灯程序时比STM32更简单。

 2. stm32存储器映射是给绝对地址命名个别名,通过寄存器更改绝对地址里面的值。

 3.  寄存器是CPU内部的组成部分,通过内部总线与CPU直接相连,内存是通过外部地址总线与C        PU相连,通过外部总线寻址,通过寄存器修改变量的值比内存更快。

 4. register声明的变量尽可能直接放在寄存器中存储,volatile声明的变量只能从绝对地址中访问。

 

PS:参考网站:

     proteus安装可参考 Protues 8.9 仿真(安装包和安装步骤) - 知乎 (zhihu.com)

     Keil5安装参考:keil5安装教程_Matcha_ice_cream的博客-CSDN博客       

     keil4安装可参考   Keil uVision4保姆级安装教程 - 知乎 (zhihu.com)​​​​​

     4个案例代码告诉你,C语言中volatile关键字的高级玩法 (baidu.com)

     参考网站:jlink驱动安装_洛殿的博客-CSDN博客 

 

 

  • 13
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值