STM32/51单片机编程入门(Proteus仿真模拟)
Proteus是一款广泛用于电子设计自动化(EDA)的软件工具,主要用于模拟和设计电子电路以及微控制器和嵌入式系统。电子电路设计和模拟:Proteus允许电子工程师和设计师创建和模拟各种电子电路,包括模拟电路和数字电路。用户可以绘制电路图,并通过模拟功能来验证其设计的正确性。微控制器仿真:Proteus支持各种微控制器和微处理器的仿真,包括常见的型号如8051、PIC、AVR等。这使得用户能够在软
目录
一.Proteus简介
Proteus是一款广泛用于电子设计自动化(EDA)的软件工具,主要用于模拟和设计电子电路以及微控制器和嵌入式系统。
电子电路设计和模拟:Proteus允许电子工程师和设计师创建和模拟各种电子电路,包括模拟电路和数字电路。用户可以绘制电路图,并通过模拟功能来验证其设计的正确性。
微控制器仿真:Proteus支持各种微控制器和微处理器的仿真,包括常见的型号如8051、PIC、AVR等。这使得用户能够在软件中模拟他们的嵌入式系统,以进行调试和验证。
PCB设计:Proteus还提供了用于印刷电路板(PCB)设计的功能。用户可以将他们的电路设计转换为PCB 布局,并生成制造所需的文件。这包括布线、引脚分配、元件库等功能。
虚拟示波器和逻辑分析仪:软件还包括虚拟示波器和逻辑分析仪,使用户能够查看电路中的信号波形和逻辑分析结果,有助于调试和分析电子系统的性能。
多种元件库:Proteus包括大量的元件库,包括各种电子元件、传感器、开关、电阻、电容等,以及各种常见的集成电路(IC)。
实时交互:Proteus的实时交互性使得用户能够在设计过程中动态修改电路参数,并立即查看结果,有助于快速迭代和优化设计。
二.c51程序设计和仿真
1.绘制原理图
(1)点击绘制原理图按钮,左键单击元件,然后再点击P按钮,进入元件选择界面,在 Keywords 处输入 AT89C51 ,然后在中间的窗口内双击AT89C51芯片,即可添加到元件列表中,而后依次添加LED-RED、RES
(2)左击元件列表窗内的 AT89C51 芯片,然后再原理图编辑窗口内左击摆放
(3)左击元件列表内的 LED-RED ,再点击 旋转按钮 ,可以在预览窗内看见元件顺时针旋转了下,再在原理图编辑框内一次摆放LED灯共8个
(4)再依次摆放8个电阻,然后左键双击“10K”,弹出修改值的对话框,将10K修改为300,以至于让LED更亮
(5)拉一条主线(左击一下起点,然后移动鼠标,然后双击一下终点,即可拉一条主线),连接管脚
(6)点击终端接口→选择POWER,放置电源,然后左键双击电源图标,修改为VCC,使用 LBL 为支线标记编号,连接到主线的支线
2.编写c51程序
1.使用Keil C51来编写程序,创建一个工程,打开点击 Project → New uVision Project,给工程命名
2.在搜索框内输入 AT89C51 ,选中 AT89C51 芯片
3.编写main.c文件,点击新建文件,输入以下51程序代码
#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);
}
}
4.右键点击 Source Group 1 ,再点击 Add Existing Files to Group “Source Group 1”…选中刚刚创建的 main.c 文件,并点击 添加
5.生成 .hex 文件点击魔法棒,在弹出的窗口内选择 Output ,再勾选 Create HEX File ,然后点击 OK
6.开始仿真,回到Proteus软件内,双击 AT89C51 芯片后,在弹出的窗口的 Program File 一栏从刚才 keil 软件编译后的路径中添加 .hex 文件
7.点击调试按钮,结果:
三.MDK编译简单stm32程序
1.新建工程
1.新建一个工程,在左侧的窗口内选择STM32芯片,这里选择STM32F103RB
2.勾选相应的选项,工程创建完毕
2.编写main.c文件
1.新建文件,编写如下代码
#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))
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;
//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;
Delay_ms(500);
LED0=1;
Delay_ms(500);
}
}
2.首先点击 魔法棒,然后在弹出的窗口内,点击 Debug,勾选 Use Simulator ,再选择 ULINK2/ME Cortex Debugger,确定一下Port是JTAG,Reset可以设置为Autodetect或SYSRESEETREQ,然后点击OK返回上一级窗口,再点击OK
3.保存main.c
文件,如c51实验步骤,然后选中带有红色d的放大镜开始调试
四.概念理论
1.嵌入式C程序代码对内存中的各变量的修改操作与对外部设备的操作相同与差别
1.相似之处
数据存储和访问:无论是修改内存中的变量还是与外部设备通信,都涉及到数据的存储和访问。在内存中,变量的值存储在特定的内存地址中,而与外部设备的通信通常需要将数据发送到或从特定的寄存器或管脚中读取数据。
数据操作:在内存中的变量和外部设备通信中,都可以进行各种数据操作,例如读取、写入、更改和计算。这些操作可以是基本的算术运算、逻辑运算或其他自定义操作。
数据同步:为了确保数据的一致性和可靠性,需要采取适当的同步措施,以防止多个部分同时访问和修改数据。这适用于内存中的变量和外部设备通信。
2.差异之处
物理位置:最显著的差异是内存中的变量和外部设备位于不同的物理位置。内存中的变量存储在RAM中,而外部设备通常通过特定的寄存器或管脚与嵌入式系统相连。
访问方式:访问内存中的变量通常是通过指针或变量名来实现的,而访问外部设备通常需要使用特定的寄存器操作或管脚控制指令。这些操作通常需要通过硬件抽象层(如外设寄存器映射)来执行。
时序要求:与内存访问相比,与外部设备的通信通常对时序要求更为敏感。外部设备可能需要特定的时序、时钟信号或协议来正确地接收和处理数据。
硬件依赖性:操作内存中的变量通常更加硬件无关,因为它们依赖于CPU和内存的抽象层。与外部设备通信通常更加硬件相关,需要考虑特定硬件的功能和约束。
中断处理:与外部设备通信通常需要考虑中断处理,因为外部事件可能需要立即处理,而内存中的变量修改通常不会引发中断。
2.为什么51单片机的LED点灯编程要比STM32的简单
51单片机的LED点灯编程相对于STM32的LED点灯编程可能会更简单,这主要是由于以下一些因素:
1.体系结构的复杂性:51系列单片机的体系结构相对简单。它通常具有较小的指令集和较少的寄存器,这意味着在编写程序时需要考虑的细节较少。STM32系列微控制器则通常具有更复杂的体系结构,包括更多的寄存器和功能单元,因此编写程序可能需要更多的配置和设置。
2.开发环境和工具:51系列单片机有许多成熟的开发环境和工具,如Keil C51等,这些工具提供了易于使用的集成开发环境(IDE)和丰富的代码库,可以大大简化编程任务。STM32也有强大的开发工具,但初学者可能需要一些时间来熟悉它们。
3.资源限制:51系列单片机通常用于相对简单的嵌入式系统,因此可能不需要处理太多复杂的功能或外设。相比之下,STM32系列微控制器通常用于更复杂的应用,可能需要更多的配置和设置来管理多个外设和功能单元。
4.学习曲线:由于51的相对简单性,初学者可能更容易理解和掌握它。STM32可能需要更多的学习和实践,尤其是对于初学者来说。
3.嵌入式C程序关键字register和volatile两个变量修饰符的作用
1.register
1.register
关键字建议编译器将变量存储在寄存器中,以提高访问速度。寄存器是CPU内部的存储区域,访问速度比RAM更快。
2.register
关键字是一个建议,而不是强制要求。编译器可以选择忽略它,特别是当寄存器不足以容纳所有标记为 register
的变量时。
3.使用 register
关键字通常适用于需要快速访问的变量,如循环中的计数器或需要频繁访问的状态变量。
示例代码:
#include <stdio.h>
int main() {
register int counter; // 建议将counter存储在寄存器中
int sum = 0;
for (counter = 1; counter <= 10; ++counter) {
sum += counter;
}
printf("Sum of numbers from 1 to 10: %d\n", sum);
return 0;
}
在这个例子中,将 counter 变量标记为 register,这是因为它在循环中频繁使用,并且希望尽量提高它的访问速度。
2.volatile
1.volatile
关键字告诉编译器不要对变量进行优化,因为变量的值可以在程序控制之外发生变化。这对于与外部硬件或中断服务程序交互的变量非常重要,因为编译器通常会尝试优化掉似乎没有被使用的变量。
2.使用 volatile
可以确保每次访问变量时都从内存中读取其最新值,并且每次写入时都将新值写入内存,而不会使用寄存器中的缓存值。
示例代码:
#include <stdio.h>
volatile int sensorValue; // 使用volatile关键字声明sensorValue
int main() {
// 模拟传感器值的变化
for (int i = 1; i <= 10; ++i) {
sensorValue = i;
printf("Sensor Value: %d\n", sensorValue);
}
return 0;
}
在这个例子中,声明了一个 volatile 整数变量 sensorValue。模拟了传感器值的变化,将 sensorValue 设置为1到10的连续值,并打印出来。sensorValue 变量被标记为 volatile,这意味着编译器不会将它的读取或写入操作进行优化。这就确保了每次访问 sensorValue 时都会从内存中读取最新的值,而不会使用任何缓存值。
五.总结
通过本次实验,我学习到了使用Proteus创建仿真工程、掌握简单的c51原理知识、使用Keil编写c51项目。
六.参考资料
1.https://blog.csdn.net/ssj925319/article/details/108919862
2.https://blog.csdn.net/ssj925319/article/details/108929227
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)