目录
-
一. 使用Proteus 完成一个C51程序设计和仿真
-
1.在keil uVision软件上编写程序
-
2.在proteus上创建新工程,并设计连接电路,绘制原理图,如下所示。
-
3.仿真结果测试\n\n
-
二.
-
1.新建工程,创建main.c文件,编写代码
-
2.在source group下添加main.c文件,进行编译编辑
-
3.STM32程序仿真调试
-
三. 理论概念题目1
-
四. 理论概念题目2
-
五. 总结与反思
一. 使用Proteus 完成一个C51程序设计和仿真
1.在keil uVision软件上编写程序
代码如下:
#include <reg51.h>
#include <intrins.h>
void delay_ms(int a)
{ int i,j;
for(i=0;i<a;i++)
{ for(j=0;j<1000;j++) _nop_(); } }
void main(void)
{ while(1)
{ P0=0xfe;
delay_ms(50);
P0=0xfd;
delay_ms(50);
P0=0xfb;
delay_ms(50);
P0=0xf7;
delay_ms(50);
P0=0xef;
delay_ms(50);
P0=0xdf;
delay_ms(50);
P0=0xbf;
delay_ms(50);P0=0x7f;
delay_ms(50); }
}
2.在proteus上创建新工程,并设计连接电路,绘制原理图,如下所示。
3.运行结果:
二.
1.新建工程编写代码,创建main.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.在source group添加main文件之后编译,如图所示:
3.STM32程序仿真调试
a.设置Options选项中Debug的Use Simulator为勾选状态
b.在Debug的setting选项中确定Port是JTAG,Reset设置为Autodetect
c.仿真结果如下:
三. 理论概念题目1
(1)嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?
解答:
数据指针的相同和差别:
在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。
以指针直接操作内存多发生在如下几种情况:某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元,称为mail box。书写内容以在对方CPU产生中断;读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。函数指针的相同和差别:C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;调用函数实际上等同于“调转指令+参数传递处理+回归位置入栈”,本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以“调用”一个根本就不存在的函数实体。
(2)为什么51单片机的LED点灯编程要比STM32的简单?
答:
51单片机开发,通常是直接操作寄存器,比如P1_0对应LED的IO口,如:
#includesbit LED = P1^0;
void main(){ LED = 0; while(1);}
STM32点灯难度系数要大一点,因为STM32外设资源更多,启动文件更复杂。51单片机的任何器件只需要配置寄存器打开就可以进行编程,而STM32系列单片机则需要先打开对应的时钟,包括开启后打开外部时钟(晶振)才开始工作。STM32的内部资源(寄存器和外设功能)较普通的51单片机都要多,基本上接近于计算机的CPU。STM32基本不会选择汇编语言了,因为工程量巨大,寄存器太多了,位数也多,而51单片机则多使用汇编语言。
四. 理论概念题目2
题目:与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。
答:
1.register关键字作用:
如果一个变量用register来修饰,则意味着该变量会作为一个寄存器变量,让该变量的访问速度达到最快。例如,一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。
register int a; 限制变量定义在寄存器上的修饰符 。
可定义快速访问的变量,如置于寄存器内,会进一步加快计算速度。
编译器会尽量的安排CPU的寄存器去寄存这个变量a,如果寄存器不足时,变量a还是会被放在存储器中。其中,register只能修饰局部变量,不能修饰全局变量和函数;register修饰的变量不能通过取地址来获取寄存器变量;register修饰的变量一定是CPU能接受的数据类型。
2.volatile关键字作用:是告知编译器编译方法的关键字,不会优化编译。
即具体会体现在以下方面
(1)不会在两个操作之间把volatile变量缓存在寄存器当中,在多任务中,甚至stejmp环境下变量可能被其他程序改变,编译器无法知道,volatile就是告诉编译器这种情况的。
(2)不做常量合并,常量传播等优化。
(3)对volatile变量的读写不会被优化掉,如果你对一个变量赋值,但后面没用到,编译器常常可以忽略掉那个赋值操作,然而对Memory Mapped IO处理是不能这样优化的。
3.C代码示例说明
(1)register:
代码如下:
#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;
}
输出结果为:5050
(2)volatile:
代码如下:
int tmp, a1, a2;
tmp = (unsigned int *)0x4004;
a1 = *tmp;
a2 = *tmp;
在特殊情况下可能引起一些错误,如第一次读取(a1=*tmp)后,*tmp的内容可能被更新,所以第二次读取内容会发生变化,就需要使用volatile关键字:
volatile unsigned int *tmp;
int a1, a2;
tmp = (volatile unsigned int *)0x4004;
a1 = *tmp;
a2 = *tmp;
五. 总结与反思
通过本次实验,我学会了使用Proteus创建工程,熟悉了Keil软件的编译原理及方式,通过阅读ARM、STM32技术手册,了解到了STM32F103系列芯片的地址映射和寄存器映射原理及GPIO端口的初始化设置的一般步骤。这个过程中还学习了嵌入式C程序对内存和对外部设备操作的同异之处及51单片机和STM32LED点灯编程中的区别与联系。
在本次实验过程中,我和同学遇到很多困难,在完成Proteus电路仿真的实验中,keil软件生成的hex文件出现了很多次的无法运行情况,在查找keil代码错误原因后,终于成功运行仿真。
其次,作为一名新人小白,我在此时此刻已经感受到这个领域的复杂性和挑战性,已经给自己留下压力。在学习过程中,我更加扎实地掌握了有关嵌入式方面的知识,也通过解决一些实际问题发现了我在学习过程中的知识欠缺与经验不足,践行过而能改,善莫大焉的知行观。实践对于本课程是十分重要的,只有尝试和改正才能助我们修成一门学问。以后无论学习过程有多苦,我都将变苦为乐,在实践中学习更多知识,面对即将到来的挑战。