STM32入门之点亮LED灯

导语: stm32系列的芯片比起入门级的AT89C51、STC系列芯片,有更强大的性能以及资源。但在编程上也有着或大或小的改变,使得初学者总会面临一些麻烦,下面我来总结下自己初学时的经验。

  1. 首先在keil软件的初始化上,我们需要选择自己对应的型号。
  2. 接下来就是对工程的属性设置,基础的设置网上有很多视频,不知道的童鞋可以去搜索某火的视频,非常的全面。
    但是在这里我要提醒下,在对工程的属性设置时记得将target中的 use Micro LIB 选项勾选住。否则在下载程序后,芯片是不会做出反应的。

STM32入门之点亮LED灯_第1张图片

我翻阅过资料,但是 始终未能寻找到我想要的答案。( 希望知道的大哥能告知下o( ̄▽ ̄)ブ
只能知道他是一个c的缺省库,用于printf这样的函数转换。

  1. 其次是在新的工程当中要添加一个启动文件,这个汇编文件是startup_stm32f429_439xx.s
    对于这个文件的作用不做详细的解释,具体可以翻阅 某火的资料。简单地说他类似于系统的boot文件,用来启动(初始化)内核的设置。其中值得注意的是他复位后调用了初始化系统时钟的函数,这个函数被存放在文件 system_stm32f4xx.c 中,若创建工程时没有添加该文件,需要自行建立一个子函数来防止编译器报错。

4.再就是将所有的外设函数都添加进工程中
STM32入门之点亮LED灯_第2张图片
5.最后添加中断函数的存放文件 stm32f4xx_it.c 以及自己创建的main.c 文件。

进行到这里,就可以开始对寄存器操作赋值来控制芯片的led灯。首先看原理图:
STM32入门之点亮LED灯_第3张图片
这里我们可以看到LED灯属于共阳接法,都挂载再GPIOH(通用输入\输出端口)上,为了能够点亮RLED,需要将PH10置零。值得注意的是,与51等芯片不同的是端口的设置已经被挂载到了AHB1上,所以为了能让数据正常的传输到GPIO上,首先要初始化AHB1总线时钟。
STM32入门之点亮LED灯_第4张图片
而RCC的起始地址为 0X40023800 ,RCC
所以 具体的实现语句是 *(unsigned int *)0X40023830 |=(1<<7) ; 目的是为了打开GPIOH的总线时钟
接着再设置GPIO的模式、输出类型、输出速度、上拉或下拉以及输入输出的数据
由于GPIO默认为输入模式,推挽输出、低速运行、不进行上拉下拉操作以及输出低电平
所以这里只需要设置模式
具体实现语句是 *(unsigned int * )0X40021C00 |=(1<<20);目的是将模式调整为输出。
至此,我们只需要编译并下载程序,RLED灯就能被点亮。这是对寄存器直接进行操作,下面介绍利用库函数来操作寄存器。
关于RCC时钟方面,后面会有专门的一章,所以先不进行详细的讲解,主要是注释GPIO的库函数。

#define GPIOH_LED 0X00000080
#define RLED	0X0200

int mai(void)
{
GPIO_InitTypeDef* GPIO_InitStruct
//打开AHB1时钟   
RCC_AHB1PeriphClockCmd(GPIOH_LED, ENABLE);   

//定义初始化结构体,由于我们只需要初始化模式和引脚且初始化函数中并没有对其他设置进行了防错检查,所以可以不设置。
GPIO_InitStruct->GPIO_Pin = RLED;
GPIO_InitStruct->GPIO_Mode = GPIO_Mode_OUT;


//设置GPIOH的模式为输出
GPIO_Init(GPIOH,GPIO_InitStruct);
}

以上仅仅用到了GPIO的初始化函数,在最后我们对GPIO的库函数中挑选常用的函数进行分析:

/*  Function used to set the GPIO configuration to the default reset state ****/
void GPIO_DeInit(GPIO_TypeDef* GPIOx);  //设置GPIOx的AHB1时钟总线为默认状态(不使能) 由于内部程序较为简单不做详细分析

/* Initialization and Configuration functions *********************************/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//GPIO初始化函数,需要提供初始化结构体和对应GPIO
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);//设置所有GPIO为默认状态(输入、20MHz、推挽输出、无上拉下拉	)
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//配置锁定函数

/* GPIO Read and Write functions **********************************************/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读取指定GPIO中的引脚的当前输入状态
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//读取指定GPIO的所有引脚输入状态
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读取指定GPIO中的引脚的当前输出状态
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);//读取指定GPIO的所有引脚输出状态
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//对指定的GPIO引脚的输出值进行置位
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//对指定的GPIO引脚的输出值进行复位
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//对指定的GPIO进行直接复位或置位
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//对指定的GPIO的ODR进行写入数据
void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//翻转指定GPIO引脚

/* GPIO Alternate functions configuration function ****************************/
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);//复位功能配置

这里挑出值得学习的代码段进行分析

#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))

#define GPIO_PinSource0            ((uint8_t)0x00)
//这里省略其余的赋值
#define GPIO_PinSource15           ((uint8_t)0x0F)

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
                                    ((PERIPH) == GPIOB) || \
                                    ((PERIPH) == GPIOC) || \
                                    ((PERIPH) == GPIOD) || \
                                    ((PERIPH) == GPIOE) || \
                                    ((PERIPH) == GPIOF) || \
                                    ((PERIPH) == GPIOG) || \
                                    ((PERIPH) == GPIOH) || \
                                    ((PERIPH) == GPIOI) || \
                                    ((PERIPH) == GPIOJ) || \
                                    ((PERIPH) == GPIOK))

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;

  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));  //注释1		
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));

  /* ------------------------- Configure the port pins ---------------- */
  /*-- GPIO Mode Configuration --*/
  for (pinpos = 0x00; pinpos < 0x10; pinpos++)	//注释2
  {
    pos = ((uint32_t)0x01) << pinpos;
    /* Get the port pins position */
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    if (currentpin == pos)
    {
      GPIOx->MODER  &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
      GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));

      if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
      {
        /* Check Speed mode parameters */
        assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));

        /* Speed mode configuration */
        GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
        GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));

        /* Check Output mode parameters */
        assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));

        /* Output mode configuration*/
        GPIOx->OTYPER  &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos)) ;
        GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
      }

      /* Pull-up Pull down resistor configuration*/
      GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
      GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
    }
  }
}

注释1: 库函数中常用IS_XXXX_XXX函数来防止函数调用时参数出现错误,来自宏定义,起到判断参数是否在规定范围内的作用,返回布尔值。返回的参数将被函数assert_param()调用,实际上是想要实现当参数错误时,需要进行报错提示,但是库函数中并没有实现报错的功能,所以需要自行建立。即建立 函数 assert_failed(),若使用该功能,需要在魔术棒的c/c++中定义USE_FULL_ASSERT
STM32入门之点亮LED灯_第5张图片
注释2: 从函数体的结构来看,该函数支持或运算,即可以一次性对多个引脚进行操作。为了能够统一化操作,将GPIOx_Pinsource 定义为16位的数值,对应的引脚x的数值对应2^x,因此再调用函数时需要配套的算法进行判断。
这里需要和复用功能配置函数区别, 复用功能函数不支持多引脚同时开启功能。接下来对代码进行分析:

void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)
{
  uint32_t temp = 0x00;
  uint32_t temp_2 = 0x00;
  
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
  assert_param(IS_GPIO_AF(GPIO_AF));
  
  temp = ((uint32_t)(GPIO_AF) << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;
  GPIOx->AFR[GPIO_PinSource >> 0x03] &= ~((uint32_t)0xF << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;
  temp_2 = GPIOx->AFR[GPIO_PinSource >> 0x03] | temp;
  GPIOx->AFR[GPIO_PinSource >> 0x03] = temp_2;
}

显而易见,在进行相关引脚功能配置时,他没有和初始化函数一样,用for语句进行逐一判断。所以使用只能逐次对单一GPIO使用该函数。

结语:

比较初级的单片机,STM32的GPIO功能强化很多,也将很多功能整合了进去。对于GPIO的使用,有如下步骤:
1、配置相应GPIO所挂载的时钟总线。
2、设置GPIO初始化结构体,并使用函数配置(若使用了复用功能,要注意需要再调用复用功能配置函数)

你可能感兴趣的:(嵌入式)