文章目录
一、使用C51程序设计点亮LED
1.1 仿真软件Proteus电路建立
在工具栏中搜索AT89C51,选择对应芯片;
选择合适的器件,诸如LED、电阻等,进行电路连接;
电路连接完成。
1.2 C51程序的编写与仿真
打开软件Keil,编写LED点亮程序;
选择对应芯片AT89C51:
创建新项目与新文件,在新文件中编写程序:
代码如下:
//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);
}
}
点击保存,后缀为.c文件:
生成Hex文件,并导入Proteus芯片中:
点击调试按钮,进行仿真:
仿真成功。
二、使用STM32点亮LED
2.1 在软件Keil中安装Keil.STM32F1xx包
2.2 在Keil中选择芯片STM32F103C8
2.3 创建新项目与新文件
编写程序,代码如下:
//宏定义,用于存放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);//延时时间
}
}
点击保存,后缀为.c文件;进行编译;
首先生成Hex文件:
进行仿真调试:
三、思考
3.1 嵌入式C程序代码对内存(RAM)中各变量与对外部设备(寄存器—>对应相关管脚)的操作的相同与差别
嵌入式C程序代码可以通过对内存中的变量进行操作来控制外部设备。这可以通过直接操作内存地址或使用特定的寄存器来实现。以下是一个示例代码,展示了如何使用嵌入式C程序代码对外部设备进行操作:
#include <stdint.h>
// 定义外部设备寄存器的地址
volatile uint32_t* const GPIO_BASE = (uint32_t*)0x40000000;
volatile uint32_t* const GPIO_DIR = GPIO_BASE + 0x00;
volatile uint32_t* const GPIO_DATA = GPIO_BASE + 0x04;
// 设置指定管脚为输出
void set_pin_as_output(uint8_t pin) {
*GPIO_DIR |= (1 << pin);
}
// 设置指定管脚为高电平
void set_pin_high(uint8_t pin) {
*GPIO_DATA |= (1 << pin);
}
// 设置指定管脚为低电平
void set_pin_low(uint8_t pin) {
*GPIO_DATA &= ~(1 << pin);
}
int main() {
// 设置管脚1为输出
set_pin_as_output(1);
// 将管脚1设置为高电平
set_pin_high(1);
// 程序延时
for (int i = 0; i < 1000000; i++);
// 将管脚1设置为低电平
set_pin_low(1);
return 0;
}
在这个示例代码中,我们使用了指针来访问特定的内存地址,这些地址对应于外部设备的寄存器。通过设置相应的位或清除相应的位,我们可以控制外部设备的状态。
3.2 为什么51单片机的LED点灯编程要比STM32的简单?
51单片机的LED点灯编程相对于STM32来说更简单,主要有以下几个原因:
1.
51单片机的架构相对较简单:51单片机采用的是8位架构,指令集相对较小,功能相对简单。相比之下,STM32是基于ARM Cortex-M系列的32位微控制器,具有更复杂的架构和更丰富的功能。
2.
51单片机的编程模型相对简单:51单片机使用的是汇编语言或者C语言进行编程,编程模型相对简单直观。而STM32则需要使用更复杂的编程语言和开发环境,例如C语言、C++语言或者使用STM32CubeMX等开发工具。
3.
51单片机的开发工具相对简单:51单片机的开发工具相对简单,常用的有Keil C51等,使用起来相对容易上手。而STM32的开发工具相对复杂,例如Keil MDK、IAR Embedded Workbench等,需要更多的学习和配置。
3.3 与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用
在嵌入式C程序中,register和volatile关键字具有特殊的用途。
register关键字:在嵌入式系统中,通常会有一些寄存器用于存储临时变量或者频繁使用的变量。使用register关键字可以提示编译器将变量存储在寄存器中,以提高程序的执行效率。然而,现代编译器通常会自动进行寄存器分配,因此register关键字的使用已经不再常见。
下面是一个使用register关键字的示例:
register int x; // 声明一个整型变量x,希望将其存储在寄存器中
volatile关键字:在嵌入式系统中,由于硬件和外部环境的干扰,某些变量的值可能会发生变化,但编译器无法感知这种变化。使用volatile关键字可以告诉编译器该变量是易变的,需要每次都从内存中读取,而不是使用寄存器中的缓存值。
下面是一个使用volatile关键字的示例:
volatile int sensorValue; // 声明一个整型变量sensorValue,告诉编译器该变量是易变的
四、总结
在此次实验中,由于是第一次进行STM32相关的程序编译,因此遇到了许多困难。软件方面,添加文件等等较为繁琐,编译调试时也会出现其他问题,无法调试等等。总之,我也学到了许多知识,包括项目创建,下载调试代码,以及芯片中各个管脚等等的连接。