精通STM32F103的学习指南:LED控制入门第一步

目录
一、前言
二、固件库
三、寄存器
四、用固件库控制LED——亮灭灯
五、用寄存器控制LED——亮灭灯
六、总结

一、前言
(如果不想了解怎么找到野火的官方文件的或已经知道怎么找的,可以跳过前言这一部分)
  要想学会一款单片机,先要学会看懂单片机的硬件部分。
图 1.1是野火指南者的硬件部分介绍
图 1.1
  当然了,除了硬看硬件也可以看野火给出的资料。
首先,百度找到野火电子论坛
图 1.2
图 1.3
找到在上方菜单中的下载中心,找到资料中心
图 1.4
  点击进去就可以看到图 1.5的内容,点击那个指南者STM32F103开发板就可以找到官方的资料了。

图 1.5

图 1.6
  官方不仅免费提供我们指南者的资还提供标准库和HAL库开发视频指南,可谓是非常贴心。因此我们要好好利用好官方提供给我们的资料,助我们扎紧根和更上一层楼。

二、固件库
  固件库是官方提供的STM32的外设例程,这些都是芯片公司编写而成,建议写程序从固件库里面直接复制修改,所以我们得很清楚自己想要什么。使用起来较为繁琐以及对于初学者来说不太友好,但与寄存器相比来说,固件库会比较直接一点,几乎所有的函数官方都会给出来,你只需要去使用就好了。

图 1.7
关于固件库的具体内容可以使用官方给出的资料,(Release_Notes.html)

图 1.8
三、寄存器
3.1什么是寄存器
  寄存器编程是单片机编程的基础,8位或者16位单片机大多采用寄存器编程。
  要想了解寄存器编程就要先了解我们的STM32F103芯片,寄存器编程离不开我们的芯片以及我们的外设结构。

  根据百度百科介绍,寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。

  简单来说,寄存器就是存放东西的东西。从名字来看,跟火车站寄存行李的地方好像是有关系的。只不过火车站行李寄存处,存放的行李;寄存器可能存放的是指令、数据或地址。

  存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像老张行李寄存处在101号店铺,老王行李寄存处在258号店铺。
  指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。
  寄存器相当于一个地址用来存放数据。
3.2寄存器映射
  寄存器映射就是C语言里的#define(不懂的可以复习一下C语言),就是将一大堆东西用一个我们看得懂的东西代替。

#define NO 0;
#define OFF 1;

  这段代码就是将 0 用NO这个单词来代替,表示“打开”,将 1 用OFF这个单词来代替,表示“关闭”。0在单片机里代表低电平,1在单片机里表示高电平。如下图
图 1.9
  寄存器映射其实只要知道芯片以及外设的端口是什么怎么去使用它们,然后用自己和别人能看得懂的词去替换它就好了。

四、用固件库控制LED——亮灭灯
  在我们编程之前,我们必须要看官方给出来的单片机原理图,不然有可能寸步难行或者导致芯片损坏,发生危险。
图2.1就是官方给出来的指南者的一部分原理图

图 2.1
  看完原理图之后我们可以尝试编程,找到我们的LED原理图,图2.2
blog.csdnimg.cn/94a87664108c41cd9f1c8ee0d37043e9.png)图 2.2
  图 2.2是一个LED的原理图,我们可以看到一开始的时候,PB0\PB1\PB5这三个端口都是与3.3v直接连接的,因此这三个端口都是高电平,相当于只有正极,因此这三个LED并不会亮。要使这三个LED亮起来就要让一端呈低电平状态,形成负极。我们能够控制的只有PB0\PB1\PB5这三个端口,当PB0 = 0时,LED2将会亮起来了,亮绿灯。理论成立,接下来我们就要实现它了。
  打开我们的KEil 5
  STM32在使用端口时都需要打开相对应的时钟,它才能工作,不然就一直输出0x00,一直不能正常工作。
  LED端口是GPIO端口,因此要打开GPIO端口的时钟,一般开启时钟的主要原因是降低功耗,还有一个原因,就是为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费。
不懂可以不用管,要是想搞明白可以到这个网站学习STM32使用时钟的原因
打开时钟

/*开启LED相关的GPIO外设时钟*/
  RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|   //LED1的端口时钟
                         LED2_GPIO_CLK|   //LED2的端口时钟和LED3的端口时钟
                         LED3_GPIO_CLK,ENABLE//开启时钟);

要是想更深一步地了解怎么开启时钟,可以查询RCC_APB2PeriphClockCmd这个函数,如图2.3所示
图2.3

  在LED的控制之前先映射一下,将看不懂的转换成看得懂的

//R-红色
#define LED1_GPIO_PORT             GPIOB
#define LED1_GPIO_CLK              RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN              GPIO_Pin_5
//G-绿色
#define LED2_GPIO_PORT             GPIOB
#define LED2_GPIO_CLK              RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN              GPIO_Pin_0
//B-蓝色
#define LED3_GPIO_PORT             GPIOB
#define LED3_GPIO_CLK              RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN              GPIO_Pin_1

/*直接操作寄存器的方法控制IO */
#define digitalHi(p,i)             {p->BSRR=i;}   //输出高电平
#define digitalLo(p,i)             {p->BRR=i;}   //输出低电平
#define digitalToggle(p,i)         {p->ODR ^=i;}   //输出反转状态

/*定义控制IO的宏*/
#define LED1_TOGGLE                 digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF                    digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON                     digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE                 digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF                    digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON                     digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE                 digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF                    digitalHi(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON                     digitalLo(LED3_GPIO_PORT,LED3_GPIO_PIN)

  接下来就可以初始化我们的三个LED端口

 /*选择要控制的GPIO引脚*/
  GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
  
  /*设置引脚模式为通用推挽输出*/
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  
  /*设置引脚速率为50MHz*/
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  /*调用库函数,初始化GPIO*/
  GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
  
  /*选择要控制的GPIO引脚*/
  GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
  
  /*调用库函数,初始化GPIO*/
  GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
  
   /*选择要控制的GPIO引脚*/
  GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
  
  /*调用库函数,初始化GPIO*/
  GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
  
  /*关闭LED1*/
  GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
  
    /*关闭LED2*/
  GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
  
    /*关闭LED3*/
  GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);

  LED初始化之后,这三个LED都是不亮的,在这里可以看到

  /*关闭LED1*/
  GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
  
    /*关闭LED2*/
  GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
  
    /*关闭LED3*/
  GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);

接下来就是控制LED灯的亮灭了

  while(1)
  {
    LED1_ON;             //亮
    SOFT_DELAY;
    LED1_OFF;            //灭
    
    LED2_ON;             //亮
    SOFT_DELAY;
    LED2_OFF;            //灭
    
    LED3_ON;             //亮
    SOFT_DELAY;
    LED3_OFF;            //灭
}

在完成全部代码的撰写后,我们可以通过MCU-ISP下载到单片机上验证。

  玩点花活 ^ — ^
在上面的基础增加下面的代码,我们可以玩出disco的效果,不再是简简单单的红绿灯

/*基本混色,后面高级用法使用PWM可混出全彩颜色,且效果更好*/

//红
#define LED_RED          \
                         LED1_ON;\
                         LED2_OFF;\
                         LED3_OFF
                         
//绿
#define LED_GREEN          \
                         LED1_OFF;\
                         LED2_ON;\
                         LED3_OFF
                         
//蓝
#define LED_BLUE          \
                         LED1_OFF;\
                         LED2_OFF;\
                         LED3_ON
                      
//黄(红+绿)
#define LED_YELLOW         \
                         LED1_ON;\
                         LED2_ON;\
                         LED3_OFF
               
//紫(红+蓝)
#define LED_PURPLE          \
                         LED1_ON;\
                         LED2_OFF;\
                         LED3_ON
        
//青(绿+蓝)
#define LED_CYAN          \
                         LED1_OFF;\
                         LED2_ON;\
                         LED3_ON

//白(红+绿+蓝)
#define LED_WHITE          \
                         LED1_ON;\
                         LED2_ON;\
                         LED3_ON
                         
//黑
#define LED_RGBOFF          \
                         LED1_OFF;\
                         LED2_OFF;\
                         LED3_OFF
 /*轮流显示红、绿、蓝、黄、紫、青、白颜色*/
    LED_RED;
    SOFT_DELAY;
    
    LED_GREEN;
    SOFT_DELAY;
    
    LED_BLUE;
    SOFT_DELAY;
    
    LED_YELLOW;
    SOFT_DELAY;
    
    LED_PURPLE;
    SOFT_DELAY;
    
    LED_CYAN;
    SOFT_DELAY;
   
    LED_WHITE;
    SOFT_DELAY;
    
    LED_RGBOFF;
    SOFT_DELAY;

  以上就是用固件库去控制LED的变化

五、用寄存器控制LED——亮灭灯
   寄存器是什么在前面已经介绍过了,这里就不再赘述了。
用寄存器去控制LED,首先还是一如既往地打开GPIO端口的时钟,
图 2.4
GPIOB端口的时钟在三号位就将其置为高电平(1)

  //开启GPIO端口时钟
  RCC_APB2ENR |= (1<<3);

接着我们就要分别清空PB0\PB1\PB5端口,

但我又整了个花活(这样可以直接控制三个LED,并组成多种颜色)

void LEDstyle(u16 light1,u16 light2,u16 light3,u16 num,u16 nums,u16 numb)
{ 
  RCC_APB2ENR  |= (1<<3); //设置PBX的时钟
  GPIOB_ODR = 0xffff;//一般不用清理输出缓存区,但我的单片机有残留,需要先清空ODR,也可用BSRR和BRR来复位ODR
  //清空控制PBX的端口位
  GPIOB_CRL &= ~((0x0F<< (4*light1)) | (0x0F<< (4*light2)) | (0x0F<< (4*light3)));
  //配置PBX为通用推挽输出,速度为10M
  GPIOB_CRL |= ((1<< 4*light1) | (1<< (4*light2)) | (1<< (4*light3)));
   //PBX输出电平
  GPIOB_ODR &= ((num<<light1) | (nums<<light2) | (numb<<light3));
  
}

正常来写的话就像下面这样

void LEDstyle1(void)
{ 
//蓝灯
  //清空控制PB1的端口位
  GPIO_CRL &= ~((0x0F<< 1);
  //配置PB1为通用推挽输出,速度为10M
  GPIO_CRL |= (1<< 4*1);
   //PB1输出电平
  GPIO_ODR &= ~(1<<1);

  //等等等等还有很多除了RGB三色的其他颜色,用 “|”来连接两部分,这里只展示蓝色的代码
}

   为啥要用推挽输出,主要原因是:推挽输出既可以输出低电平,也可以输出高电平,可以直接驱动功耗不大的数字器件。使用10MHz是因为10MHz比较稳定,其实也可以用其他频率的。
   GPIOB_CRL(端口配置低寄存器)
大于7的端口用GPIO_CRH(端口配置高寄存器),操作差不多的
图2.5
在图2.5中我们可以看到,如何可以清空PB1端口
就是将第0,1,2,3这四位置为“1”,因此

 //清空控制PB1的端口位
  GPIO_CRL &= ~((0x0F<< 1);

推挽输入和10MHz输入对应的是“0001”,因此

  //配置PB1为通用推挽输出,速度为10M
  GPIO_CRL |= (1<< 4*1);

不懂的可以翻翻数模电。
  接下来就是GPIO_ODR的配置
图2.6

 //PB1输出电平
  GPIO_ODR &= ~(1<<1);

ODR有16的IO端口,不分高低,但要跟IDR(端口输入数据寄存器)相区别,ODR就是哪个端口要改变,就在对应的电平改变。
  配置好后我们就可以控制LED玩style了
接着前面的花活

#define LED_GREEN LEDstyle(0,1,5,0,1,1)
#define LED_BLUE LEDstyle(0,1,5,1,0,1)
#define LED_RED LEDstyle(0,1,5,1,1,0)
#define LED_WHITE LEDstyle(0,1,5,0,0,0)
#define LED_RGBOFF LEDstyle(0,1,5,1,1,1)

while(1)
{
LED_GREEN;
Delay(0xffff);
LED_RED;
Delay(0xffff);

//等等等
}


正常版的

while(1)
{
LEDstyle1();
Delay(0xffff);
LEDstyle2();
Delay(0xffff);
}

这样就实现对LED的控制,让LED不同颜色闪烁。

六、总结
  固件库虽好但寄存器更香,想要学好单片机就要多练多学,笔记可有可无,反正也记不住,用好百度能够解决很多问题。
  作者水平不高,文章如有纰漏,请评论出来

参考内容:对固件库的理解https://www.jianshu.com/p/a21666f65805
STM32寄存器的简介、地址查找,与直接操作寄存器
https://blog.csdn.net/geek_monkey/article/details/86291377

物联沃分享整理
物联沃-IOTWORD物联网 » 精通STM32F103的学习指南:LED控制入门第一步

发表评论