STM32点灯


     学习stm32有一个多月了,现在开始整理下思路,最大的感受就是,看起来是在学ARM,实际上是在补51,或者说,学习ARM之前真应该好好学学单片机。或者我根本就不应该分这么清楚,学就是了。都说学过了单片机再学ARM就很容易了,自以为单片机学的不错,想当然的认为ARM就不那么难了,刚开始的时候确实是这么想的,买了开发板,打开做好的工程,修修改改,从点灯开始呗,也没觉得有什么难的,不求甚解,达到目的就行了。过了几天,深入的研究下,仔细看下库函数什么的就不明白了,更不用谈*.s的启动文件了,工程下面那么多文件夹也不知道是什么意思,要自己从0写的话根本无从下手,突然觉得有了51的基础怎么学起来还这么难。难道别个说错了?直到看到这篇帖子:从51到ARM这路怎么走?才恍然大悟,当年学51的时候,写个100多行的程序用矩阵键盘和LCD做个密码锁,觉得还行,然后就一直停在那个阶段了,没什么进步,现在看来只是掌握了最基本的开发流程,并没有真正懂51。一直觉得自己不会用到汇编,所以一遇到汇编就skip,至于c语言,也是一知半解。总之,以前欠的太多了,现在只有补呗。

 

就从点灯开始,用库做就是这样

Step1 包含*.h头文件

#include "stm32f10x.h"

 

Step2 定义GPIO初始化结构体

GPIO_InitTypeDef GPIO_InitStructure;

 

Step3 先定义LED的亮灭,我的芯片是STM32F103VET6LED接的是PB5

#define LED_ON GPIO_SetBits(GPIOB, GPIO_Pin_5);  

 

Step4 各种声明,时钟配置、LED配置和延时函数。

void RCC_Configuration(void);

void LED_Config(void);

 

Step5 各种配置,初始化函数。

//系统时钟配置为72MHZ

void RCC_Configuration(void)

{   

  SystemInit();

}

 

//LED 控制初始化函数

void LED_Config(void){

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB| , ENABLE);

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;   

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;   

  GPIO_Init(GPIOB, &GPIO_InitStructure);  

}

 

Step6 终于进main()函数了。

Int main(void)

{

  RCC_Configuration();   //系统时钟配置

  LED_Config(); //LED控制配置

  LED_ON;                     //LED1

  while (1)

  {

  

  }

}

开发板接好线,编译成功之后loadOKLed闪烁了。是不是认为做完了,该收工了。其实还没开始呢。分析下每步到底是什么意思,为什么要这样写。

 

Step1 中为什么要包含#include "stm32f10x.h"这样的一个头文件,有什么用?

Stm32f10x.h是控制器专用头文件,包含了STM32F10X全系列所有外设寄存器的定义(寄存器的基地址和布局)、位定义、中断向量表、存储空间的地址映射等。所以要包含这个文件。 

 

Step2 GPIO_InitTypeDef GPIO_InitStructure;这是什么意思?

GPIO_InitTypeDef是自己定义的一个数据类型(stm32f10x.h):

typedef struct

{

  __IO uint32_t CRL;

  __IO uint32_t CRH;

  __IO uint32_t IDR;

  __IO uint32_t ODR;

  __IO uint32_t BSRR;

  __IO uint32_t BRR;

  __IO uint32_t LCKR;

} GPIO_TypeDef;

利用关键字typedefstruct,自定义了一个结构体变量,结构体变量的类型名就是GPIO_TypeDef,然后利用这个自定义的数据类型定义了名为GPIO_InitStructure的结构体变量,这个变量在LED 控制初始化函数中将会用到。

 

Step3中是这样定义led的亮:

#define LED_ON GPIO_SetBits(GPIOB, GPIO_Pin_5); 

    看下这个函数(在stm32f10x_gpio.c中):

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

{

  

  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  assert_param(IS_GPIO_PIN(GPIO_Pin));

  

  GPIOx->BSRR = GPIO_Pin;

}

传递了两个参数:GPIOxGPIO_Pin,对应着上面的GPIOB, GPIO_Pin_5, assert_param()这个函数是检查输入参数是否有效,如果无效就产生异常;如果有效,带入参数,执行完GPIOx->BSRR = GPIO_Pin之后就是把PB5设置为1。具体是怎么操作的?先看两个参数的类型,GPIOx是指向GPIO_TypeDef这个自定义结构体类型的指针,类型为指针,指向一个自定义的结构体(在stm32f10x.h中):

typedef struct

{

  __IO uint32_t CRL;

  __IO uint32_t CRH;

  __IO uint32_t IDR;

  __IO uint32_t ODR;

  __IO uint32_t BSRR;

  __IO uint32_t BRR;

  __IO uint32_t LCKR;

} GPIO_TypeDef;

结构体的类型为自己定义的GPIO_TypeDef。其中__IOuint32_t是什么东东?

__IO定义在这里(core_cm3.h):

#define     __IO    volatile       

这个宏定义就是用__IO来替换volatile的,那volatile这个关键字又有什么作用。Volatile是不稳定的意思,说明这种类型的值容易发生变化,使用volatile就是不让编译器进行优化,每次读取或者修改值的时候,都必须通过它的硬件地址重新读取或修改,如果不加volatile关键字,编译器就会进行优化,把定义的常量保存在寄存器中,以后就通过访问这个寄存器来访问这个值,因为访问寄存器的速度比访问内存还要快,就达到了优化的效果,但是这个值可能会在外部发生改变,尤其是在嵌入式与硬件相关或者中断的这些地方,它的值在外部改变了之后,装在寄存器中的那个值并没有随着更新,依旧取的是之前优化的时候存下的值,所以取到的就是错误的值了。

__IO:输入输出口,作为输入的时候,当然不能进行优化,它的值随时都会改变,作为输出的时候;也不能优化,优化之后,输出的始终是同一个值,而这个值是会改变的,如果连续两次输出相同值,编译器认为没改变,就会忽略后面那次输出,这就很严重了。

 

uint32_t定义在这里(stdin.h):

typedef unsigned int uint32_t;

    使用typedef定给unsigned int 类型气的别名,因为不同的平台会有不同的字长,所以利用预编译和typedef可以最有效的维护代码。这样很明显的看出是4个字节。

 

好了,另一个参数自然也就明白了,是2字节的变量了。

再回到这个函数:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

由于GPIO是个指针,GPIO_Pin为常量,所以

    #define LED1_ON GPIO_SetBits(GPIOB, GPIO_Pin_5); 预编译之后相当于

GPIOB->BSRR = GPIO_Pin_5;就是把GPIOBBSRR寄存器设置为0x0020,刚好 第五位置为1,由于是指针变量,所以用“->”直接取自定义结构体GPIOB 中的BSRR 变量的地址,然后赋值为GPIO_Pin_5,在stm32f10x_gpio.c中有这样的定义:

        #define GPIO_Pin_5                ((uint16_t)0x0020)  

0x00200x 0000 0000 0010 0000,然后查STM32参考手册:

把GPIOBBSRR寄存器的第五位置1,这个寄存器为端口为设置/清除寄存器。

 

Step4 声明这个文件需要用到的函数,时钟、LED

  

Step5 

首先是时钟配置,只需要调用系统初始化函数void SystemInit (void)OK了,在system_stm32f10x.c中,这是微控制器专用系统文件,而函数SystemInit用来初始化为控制器。

然后是LED控制初始化函数,使能APB2口中GPIOB的时钟,设置为通用推挽输出模式,最大输出速度为50MHz,然后向PB5口送数据,调用GPIO_Int函数,OK(至于推挽模式和最大输出速度为什么这样设置,现在还不是很清楚,研究中···)

 

 Step6 进入main函数,时钟和led配置完了就停在while1)中,成功点亮led

 

 现在不用库做,直接操作寄存器。

 

Step1 定义变量

 

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018

#define GPIOB_CRL  *(volatile unsigned long *)0x40010C00

#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C

 

Step2 进入main()函数

 

void main(void)

{

        RCC_APB2ENR |= 1<<3;

        GPIOB_CRL = (2<<20) | (0<<22); 

        GPIOB_ODR = 1<<5;

        while (1)

        {

 

        }

}

保存,编译,loadOK!点亮led

 

然后分析下为什么这样做:

Step1 要操作PB5口,首先要使能PB口的CLK,这是stm32特殊的地方,上电时默认外设时钟都是关的;然后是设置PB口的控制寄存器;最后就是送数据。

GPIOB的时钟通过APB2连接,所以应该设置RCC_APB2ENR,查参考手册:

第三位就是PB的时钟使能。然后就是找这个寄存器的地址了。再查memory  mappingstm32f103ve.pdf基地址就是0x4002 1000偏移地址:0x18,所以RCC_APB2ENR地址为基地址+偏移   地址:0x4002 1000 + 0x18 = 0x4002 1018

还可以去查keil给出的头文件\Keil\ARM\INC\ST\STM32F10x\stm32f10x_map.h
#define PERIPH_BASE           ((u32)0x40000000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
#define RCC_BASE              (AHBPERIPH_BASE + 0x1000)

加上偏移0x18,则RCC_APB2ENR的地址为:

0x4000 0000 + 0x2 0000 + 0x1000 + 0x18 = 0x4002 1018

于是就这样定义了:

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018

0x40021018只是个值,(volatile unsigned long *)进行强制转换,说明这个值是个地址,类型是unsigned long,意思是,读写这个地址时,写入和读出的都是unsigned long类型。加了volatile确保不被编译器优化,每次直接读值。

(volatile unsigned long *)0x40021018是一个指针,不会变,但里面的值容易变,再在前面加“*”,则可以直接操作这个指针指向的地址里面的值,然后就可以直接对这个内存进行读写操作。

剩下的两个用同样的方法找到地址然后定义。

 

Step2 main()函数

    RCC_APB2ENR |= 1<<3;查手册BIT3就是PBEN时钟使能位,置“1”,其他位不管。

GPIOB_CRL = (3<<20) | (0<<22);  将PB5设置为输出,看手册得出 MODE5 
bit 20 21 控制的,CNF5 bit 22 23,MODE5应该设置 100x2) 选择 2MHZ 输出,CNF5 选择000x0),通用推挽模式,于是将这个值写入

GPIOB_ODR = 1<<5;在相应的ODR为写“1”。

OK!点亮之后停在while1)中。

 

好吧,点个灯花了半个月,写个总结又花了2天,真是了解和做出来不是一回事,做出来和写出来也不是一回事,就算写出来了也未必理解的是对的。现在差不多知道该怎么学了,突然发现还有好多东西要学。

 

 

网上的资源真是太好了,向这些作者表示感谢:

51arm

http://www.ourdev.cn/thread-5462507-1-1.html

 

__I、 __O 、__IO是什么意思?


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM