STM32/51单片机编程入门(点亮LED)
- 一、安装并熟悉Proteus电路仿真软件,完成一个C51程序设计和仿真
- 二、安装mdk5软件和stm32包,熟悉mdk开发环境,完成一个stm32的简单的通过寄存器方式,用某一个GPIO端口点亮LED等程序。
- 三、通过以上实践,结合阅读ARM、STM32技术手册,深入思考STM32F103系列芯片的地址映射和寄存器映射原理,GPIO端口的初始化设置的一般步骤。回答:1)嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?2)为什么51单片机的LED点灯编程要比STM32的简单?
- 四、与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。
一、安装并熟悉Proteus电路仿真软件,完成一个C51程序设计和仿真
1.绘制原理图
用Proteus电路仿真软件,将电路仿真模拟出来。原理图如下:
2、编写51程序
我是用keil4来编写的,就不需要添加C51的pack了。
程序如下:
#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);
}
}
生成.hex文件,然后回到Proteus软件的原理图中,将.hex文件添加进 AT89C51 芯片中,点击调试按钮,开始仿真,仿真结果如下:
二、安装mdk5软件和stm32包,熟悉mdk开发环境,完成一个stm32的简单的通过寄存器方式,用某一个GPIO端口点亮LED等程序。
安装好了keil 和 stm32 包,就来开始一个 stm32 的简单程序的编译。
代码如下:
//宏定义,用于存放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);//延时时间
}
}
编译完成后,显示0错误,0警告:
然后生成hex文件
三、通过以上实践,结合阅读ARM、STM32技术手册,深入思考STM32F103系列芯片的地址映射和寄存器映射原理,GPIO端口的初始化设置的一般步骤。回答:1)嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器—>对应相关管脚)的操作有哪些相同与差别?2)为什么51单片机的LED点灯编程要比STM32的简单?
在嵌入式C程序中,对内存(RAM)中的变量进行修改操作与对外部设备(寄存器—>对应相关管脚)的操作有一些相同和一些差别。
相同之处:
1、都需要使用相关的寄存器或地址来访问和修改数据。
2、都需要按照特定的操作顺序和时序进行操作。
3、都需要考虑数据的精确性和正确性,避免出现错误的结果。
差别之处:
1、对内存中的变量进行修改操作时,可以直接使用变量名来进行读取和修改,而对外部设备的操作通常需要通过特定的寄存器或地址进行读写。
2、内存中的变量修改操作通常是在代码中进行的,而对外部设备的操作通常是通过特定的指令或函数调用来完成的。
3、对内存中的变量的修改操作主要受限于内存容量,而对外部设备的操作则受限于设备的特性和功能。
51单片机的LED点灯编程比STM32的简单主要有以下几个原因:
1、51单片机的操作相对简单,它直接操作寄存器和I/O口,只需要将口线设置为输出状态并输出高电平即可点亮LED。而STM32需要通过外设驱动LED,需要进行外设时钟配置和初始化设置,这增加了编程的复杂性。
2、51单片机的资源较少,I/O地址简单,而STM32的外设资源更多,启动文件更加复杂。在51单片机的编程中,点亮LED只需要几行简单的代码就可以实现,易于理解和上手。而STM32的代码相对复杂且麻烦,需要更多的配置和初始化,不容易读懂和编写。
3、51单片机的寄存器编程相对简单直观,初学者容易理解和掌握。而STM32的寄存器编程需要更深入的理解和掌握,对初学者来说有一定的难度。
四、与PC平台上的一般程序不同,嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。
register关键字用于告诉编译器将变量存储在寄存器中,以便于快速访问。寄存器是位于CPU内部的一种高速存储器,可以更快地读写数据。使用register关键字可以提高程序的执行效率。然而,由于嵌入式系统的资源有限,通常只有少量的寄存器可用,所以register关键字不一定能够被完全实现。
volatile关键字用于告诉编译器该变量可能在任何时刻都会被改变,因此编译器不会对该变量进行优化。在嵌入式系统中,由于硬件的特殊性,某些变量可能会被外部设备或者其他中断程序修改,因此需要使用volatile关键字来确保程序正确地读取和写入这些变量的值。
下面是一个示例代码,演示了register和volatile关键字的用法:
register int counter; // 使用register关键字声明一个计数器变量
void delay(volatile int num) {
while(num > 0) {
num--;
}
}
int main() {
volatile int flag = 0; // 使用volatile关键字声明一个标志变量
counter = 10; // 将计数器初始化为10
while(counter > 0) {
if(flag == 1) {
delay(counter);
counter--;
flag = 0;
}
}
return 0;
}