STM32/51单片机编程入门(点亮LED)

STM32/51单片机编程入门(点亮LED)

实验任务

  1. 安装并熟悉Proteus电路仿真软件,完成一个C51程序设计和仿真:实现流水灯
  2. 安装MDK5开发环境和STM32包,通过寄存器方式用某一个GPIO端口点亮LED。

实验准备

  • Proteus
  • Keil5
  • STM32最小系统板
  • 面包板
  • LED灯
  • 杜邦线和跳线若干

实验过程

1. C51实现流水灯

  1. 打开Proteus软件,创建新工程后,选择需要的元器件:51单片机AT89C51、发光二极管LED-BIBY、电阻RES
  1. 将电路连接成如下形式(选择共阳极接法,P2口控制流水灯)。
  1. 打开Keil4,创建新工程,编写代码。
#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main() {
	while(1) {
		P2 = 0xFE; //1111 1110
		Delay500ms();
		P2 = 0xFD; //1111 1101
		Delay500ms();
		P2 = 0xFB; //1111 1011
		Delay500ms();
		P2 = 0xF7; //1111 0111
		Delay500ms();
		P2 = 0xEF; //1110 1111
		Delay500ms();
		P2 = 0xDF; //1101 1111
		Delay500ms();
		P2 = 0xBF; //1011 1111
		Delay500ms();
		P2 = 0x7F; //0111 1111
		Delay500ms();
	}
}
  1. 仿真效果

2. STM32点亮LED(寄存器方式)

  1. 代码编写

    打开Keil5,创建新工程,并创建test.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. 电路连接

​ 这里使用面包板和STM32最小系统板进行电路连接。其中LED的正极与PA8引脚连接,负极接地。

  1. 实际效果图

    将代码烧入到STM32中,编译运行,可以看到如下效果。

思考题

  1. (1) 嵌入式C程序代码对内存(RAM)中的各变量的修改操作,与对外部设备(寄存器--->对应相关管脚)的操作有哪些相同与差别?

    相同点:

    1. 数据访问方式: 无论是内存中的变量还是外部设备的寄存器,它们都可以通过C程序中的变量来访问。可以使用变量名来读取和写入数据。
    2. 数据类型: 在C中,可以使用相同的数据类型(例如int、char、float等)来定义内存中的变量和表示寄存器中的数据。这意味着可以使用相同的数据类型来处理它们。
    3. 赋值操作: 无论是内存中的变量还是寄存器中的值,都可以通过赋值操作进行修改。可以使用赋值运算符(=)来将新值分配给变量或寄存器。

    差异:

    1. 物理位置: 内存中的变量存储在RAM(随机存储器)中,而外部设备的寄存器通常与处理器的I/O端口相连,用于与外部设备进行通信。因此,内存和寄存器的物理位置不同。
    2. 访问速度: 通常情况下,内存的访问速度较慢,而寄存器的访问速度较快。因此,对寄存器的操作可能比对内存的操作更快。
    3. 特定硬件操作: 对寄存器的操作通常需要特定的硬件寄存器映射,而对内存中的变量的操作是标准的C变量操作。因此,与内存中的变量不同,对寄存器的操作可能需要使用特殊的寄存器地址和指令。
    4. 中断和外部事件: 外部设备通常通过中断或外部事件来触发操作,而内存中的变量不会具有这种行为。因此,对外部设备的操作可能需要与中断处理程序或外部事件处理相关联。
    5. 硬件限制: 外部设备的寄存器操作受到硬件限制和规范的约束,而内存中的变量受到更少的限制。在对外部设备进行操作时,需要遵循相关的硬件规范和文档。

    (2) 为什么51单片机的LED点灯编程要比STM32的简单?

    1. 架构简单: 51单片机是基于较早的8位架构,拥有相对简单的指令集和寄存器结构。这种简单性使得编写程序相对容易,特别是对于初学者来说。

    2. 资源有限: 51单片机通常具有有限的存储器(ROM和RAM)和少量的I/O引脚,因此只需要处理少量的寄存器和少量的内存,这使得编程工作相对简单。

    3. 直接寻址: 51单片机的内存和I/O寄存器通常可以通过直接寻址访问,这意味着你可以直接读写特定的内存地址或寄存器,而不需要复杂的外设库或驱动程序。

    4. 较低的复杂性: STM32系列等高性能嵌入式处理器通常配备了丰富的外设和复杂的架构,这为处理更复杂的任务提供了更多的灵活性,但也增加了编程的复杂性。相比之下,51单片机的应用通常相对简单,例如LED点灯。

  2. 嵌入式C程序经常会看见 register和volatile 关键字,请解释这两个变量修饰符的作用,并用C代码示例进行说明。

    答:

  • register

在嵌入式C语言程序中,register关键字用于建议编译器将某个变量存储在CPU寄存器中,而不是存储在内存中。这可以减少内存访问的开销,以提高变量的访问速度。

#include <stdio.h>

int main() {
    // 定义一个普通的整数变量
    int a = 10;
    // 定义一个带有 register 关键字的整数变量
    register int b = 20;

    // 访问并修改变量 a
    a = a + 5;

    // 访问并修改变量 b
    b = b + 5;

    // 打印变量 a 和 b 的值
    printf("a = %d\n", a);
    printf("b = %d\n", b);

    return 0;
}

在上述代码中,我们定义了两个整数变量 aba 是普通的整数变量,而 b 带有 register 关键字。接下来,我们对这两个变量分别执行加法操作,然后打印它们的值。

使用 register 关键字可以向编译器发出信号,告诉它某个变量在代码中频繁使用,建议将其存储在寄存器中,以提高访问速度。不过,我们不能对 register 变量执行取地址操作,因为寄存器没有内存地址。

  • volatile

volatile 关键字在嵌入式C语言中的作用是告诉编译器不要对标记为 volatile 的变量进行优化或缓存。它的主要用途是告诉编译器,这个变量的值可能会在程序的控制之外被改变,因此编译器不应该对其进行任何优化,以确保在需要时可以正确访问该变量的最新值。

#include <stdio.h>

int main() {
    // 声明一个 volatile 变量
    volatile int sensorValue = 0;

    // 模拟传感器读取操作,该值可能会在程序控制之外更改
    sensorValue = 42;

    // 使用 volatile 变量
    printf("Sensor Value: %d\n", sensorValue);

    return 0;
}

在上述代码中,sensorValue 被标记为 volatile,因为它表示一个传感器的值,该值可能会在程序的控制之外由传感器硬件更改。因此,编译器不会对对 sensorValue 的读取进行优化,以确保在需要时可以正确读取传感器的最新值。

总结&体会

通过本次实验,我了解了Keil和Proteus等软件的基本使用,并且能够分别使用C51和STM32来实现点灯,也对51单片机和STM32的区别有了一定的认识。其中比较大的困难,就是理解STM32代码中的大量的寄存器映射和类型定义。

参考

Proteus电路仿真及应用(51单片机系列)

江科大-51单片机教程

江科大-STM32入门教程

ARM开发:使用MDK编译stm32简单程序(闪烁LED)

posted @ 2023-09-17 17:07  回文串多放孜然  阅读(237)  评论(0编辑  收藏  举报