1.Keil5软件介绍
keil是一款广泛用于嵌入式系统开发的软件工具。它支持多种常见的微型控制器架构和编程语言,并提供了丰富的调试辅助功能,可以帮助开发人员在嵌入式系统开发过程中提高效率,缩短开发周期,是嵌入式系统开发领域的重要工具之一。
Keil软件的主要功能和作用
1.提供集成化的开发环境
2.支持多种芯片体系结构,例如ARM、8051、C251等
3.提供强大的调试功能
4.内置丰富的库函数和示例代码
5.可以连线多种仿真器和调试器
Keil软件的优点
1.跨平台支持,可在Window、Linux等操作系统上运行
2.易于使用
3.支持多种编程语言,如C、C++、ASM等
4.丰富的API和库函数
5.高效的编译器
6.强大的调试功能
2.Protues软件介绍
Proteus是英国著名的EDA工具(仿真软件),从原理图布图、代码调试到单片机与外围电路协同仿真,一键切换到PCB设计,真正实现了从概念到产品的完整设计。是世界上唯一将电路仿真软件、PCB设计软件和虚拟模型仿真软件三合一的设计平台,其处理器模型支持8051、HC11、PIC10/12/16/18/24/30/DSPIC33、AVR、ARM、8086和MSP430等,2010年又增加了Cortex和DSP系列处理器,并持续增加其他系列处理器模型。在编译方面,它也支持IAR、Keil和MATLAB等多种编译器。
Protues软件功能
1.原理布图
2.PCB自动或人工布线
3.SPICE电路仿真
Protues软件特点
1.互动的电路仿真
用户甚至可以实时采用诸如RAM,ROM,键盘,马达,LED,LCD,AD/DA,部分SPI器件,部分IIC器件。
2.仿真处理器及其外围电路
可以仿真51系列、AVR、PIC、ARM、等常用主流单片机。还可以直接在基于原理图的虚拟原型上编程,再配合显示及输出,能看到运行后输入输出的效果。配合系统配置的虚拟逻辑分析仪、示波器等,Proteus建立了完备的电子设计开发环境。
(一)利用Keil和protues完成51单片机的简单仿真(流水灯)
1.在Keil软件中编写51程序
打开Keil软件,选择创建新工程
输入文件名
在Atmel中找到AT89C51,然后确定
新建一个文档
然后把代码写进去,参考如下
#include <reg51.h>
#include <intrins.h>
void delay(int a)
{
int i,j;
for(i=0;i<a;i++)
{
for(j=0;j<1000;j++) _nop_();
}
}
void main(void)
{
char st[8]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
int t=0;
while(1)
{
P0=st[t];
delay(50);
t++;
if(t==8){
t=0;
}
}
}
然后将文档进行保存,记得以.c为后缀命名
然后双击Source Group 1
找到刚刚创建的LED2.c加入进去
打开Source Group 1可以看见LED2.c加进去了
然后打开魔法棒
选择Output,打开Creat HEX file
最后进行编译,可以看见没有错误
2.在protues中进行仿真
打开protues,选择新建工程
选择下一步
选择原理图DAFAULT,然后下一步
不用创建PCB布板设计,直接下一步
也不用创建固件项目,直接下一步
然后点完成
进入原理图绘制界面,找到左上的P
搜索AT89C51
再搜索LED
然后返回之前的界面,找到电源
然后将上述找到的元件链接起来
然后右键点击51单片机,选择编辑属性,点击文件夹,将刚刚生成的LED2.hex文件添加然后点完成。
3.仿真
上述步骤完成后,点击左下角的仿真
查看仿真结果
交通灯 - Proteus 8 Professional - 原理图绘制 2023-09-24 17-26-19
(二)ARM开发,使用MDK编译简单STM32程序(LED灯闪烁)
1.使用Keil编写程序
创建工程部份和上述51单片机一致,在选择stm32型号时使用STM32F103R6
按照下图选择序号
代码如下(参考ARM开发:使用MDK编译stm32简单程序(闪烁LED))
#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( 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(500);//延时时间
LED0=1;//LED亮
delay(500);//延时时间
}
}
2. 电路链接以及烧录
这里我使用的时STM32最小开发板和USB转TTL串口模块
电路连接如下
然后打开我们的烧录程序
然后查看结果
VID_20230924_184345
(三)嵌入式C程序中的register和volatile
1.register
寄存器比内存访问要快,因此可以使用register关键字将C程序中最常用的变量放入寄存器中。关键字register会向编译器提示可以将给定变量放入寄存器中。编译器可以选择是否将其保存在寄存器中。通常,编译器自己进行优化,然后将变量放入寄存器中。
示例
#include<stdio.h>
int main() { int i = 10;
register int* a = &i;
printf("%d", *a);
getchar();
return 0; }
2.volatile
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
示例
long square(volatile int*ptr)
{
int a;
a = *ptr;
return a * a;
}
(四)总结
这次实验中我学到了有关51单片机和STM32有关的知识,学会了使用Keil和Protues来尽心仿真。在实验中我能明显的感受到51单片机的编程要比STM32简单不少,究其原因我觉得首先51单片机的结构比STM32简单,其次就是51单片机的开发环境和工具比较成熟,有很多支持它的软件和硬件工具,而STM32这没有这么丰富的资源,很多东西都需要自己去琢磨和学习,然后就是51单片机的输入输出比较简单,而STM32的GPIO就需要借助复杂的库函数和配置文件。
比较嵌入式C程序代码对内存中的各变量的修改操作对比外部对设备的操作。它们的相同点是都需要使用相关的寄存器或地址来访问和修改数据,需要考虑数据的精确性和正确性,要避免出错的情况。它们的不同点是对内存变量进行修改时,C代码可以直接通过变量名来进行更改,而外部设备需要特定的寄存器进行读写,而且对内存中的变量的修改操作主要受限于内存容量,而外部设施的操作受限于设备的特性和功能。