1.2 任务2 点亮一个LED
任务要求
使用STM32F103R6芯片,其PB8引脚接LED的阴极,通过C语言程序控制,从PB8引脚输出低电平,使LED点亮。
1.2.1 用Proteus设计第一个STM32的LED控制电路
1. Proteus仿真软件简介
本书使用的Proteus 8.6 Professional是英国Lab Center Electronics公司最新推出的一款EDA工具(仿真软件),支持STM32 Cortex-M3系列的仿真,是目前世界上唯一将电路仿真软件、PCB(印制电路板)设计软件和虚拟模型仿真软件三合一的设计平台。它实现了在计算机上,从原理图与电路设计、电路分析与仿真、单片机代码级调试与仿真、系统测试与功能验证,到形成PCB的完整的电子设计研发过程,真正实现了从概念到产品的完整设计。
2. STM32的LED控制电路设计分析与实现
Proteus 8.6 Professional仿真软件只支持STM32 Cortex-M3系列6种型号的芯片,本任务选择STM32F103R6芯片,该芯片是由ST公司生产的。
LED加正向电压发光,反之,不发光。LED电路设计一般采用的方法是:阳极接高电平,阴极接输出控制引脚。当该引脚输出低电平时,LED点亮;该引脚输出高电平时,LED不亮。这样我们只须编程控制该引脚,就可以控制LED亮或灭。
按照任务要求,点亮一个LED电路可由STM32F103R6和LED电路构成。LED阳极通过100Ω限流电阻后连接高电平(电源),电阻在这里起到了限流分压的作用。STM32F103R6芯片的PB8引脚连接LED阴极,PB8引脚输出低电平时,LED点亮,输出高电平时,LED熄灭。STM32的LED控制电路设计如图1-24所示。
图1-24 STM32的LED控制电路
3. 用Proteus设计STM32的LED控制电路
这里介绍两种运行Proteus仿真软件的方法。第一种是双击桌面上的Proteus 8 Professional图标;第二种是单击屏幕左下方的“开始”→“程序”→“Proteus 8 Professional”→“Proteus 8 Professional”应用程序,进入Proteus 8 Professional主页,如图1-25所示。
图1-25 Proteus 8 Professional主页
(1)新建Proteus工程
在设计原理图之前,必须先新建一个Proteus工程。由于本书没有涉及PCB绘制,所以这里新建一个带有原理图和无PCB的Proteus工程。
在图1-25中,单击Proteus 8 Professional主页顶部的New Project按钮,弹出New Project Wizard:Start对话框,在Name栏中输入新建工程名“LED点亮控制”,并选择新建工程保存路径,如图1-26所示。
图1-26 New Project Wizard:Start对话框
单击Next按钮,弹出New Project Wizard:Schematic Design对话框,如果不需要绘制原理图,可直接选择Do not create a schematic。本任务是从模板创建原理图的,先选择Create a Schematic from the selected template,然后选择默认DEFAULT模板,如图1-27所示。
图1-27 New Project Wizard:Schematic Design对话框
单击Next按钮,弹出New Project Wizard:PCB Layout对话框,因为不需要进行PCB设计,直接选择Do not create a PCB Layout,如图1-28所示。
图1-28 New Project Wizard: PCB Layout对话框
单击Next按钮,弹出New Project Wizard: Firmware对话框,若需要仿真,在仿真页面选择Create Firmware Project,并设置Family(系列)为Cortex-M3,Controller(控制器)为STM32F103R6,Compiler(编译器)为GCC for Arm(not configured),也就是在此设计外部代码编译器,如图1-29(a)所示。若不需要仿真,则可直接选择No Firmware Project,如图1-29(b)所示。
图1-29 New Project Wizard: Firmware对话框
单击Next按钮,弹出New Project Wizard: Summary对话框,如图1-30所示。
图1-30 New Project Wizard: Summary对话框
如果单击图1-30(a)的Finish按钮,就完成带有Schematic Capture(电路图绘制)和Source Code(源代码)选项卡的新建工程,如图1-31所示。
图1-31 Schematic Capture和Source Code选项卡
Proteus 8.0或以上版本都自带源代码编辑器、编译器,不再需要外部文本编辑器。研发人员只要在源代码编辑器中,把自己所写的源代码添加进去就可以了,如图1-31(b)所示。由于这部分内容不是本书重点,这里只是简单介绍一下。
单击图1-30(b)的Finish按钮,只完成带有Schematic Capture(电路图绘制)选项卡的新建工程,如图1-32所示。
图1-32 Schematic Capture选项卡
(2)设置图纸尺寸
单击System→Set Sheet Sizes,在弹出的Sheet Size Configuration对话框中选择A4图纸尺寸或自定义尺寸后单击OK按钮。
(3)设置网格
单击View→Toggle Grid,显示网格(再次单击,隐藏网格)。单击Toggle Grid→Snap ××th(或Snap ×.×in),可改变网格单位,默认为“Snap 0.1in”。
(4)添加元器件
单击模式选择工具栏中的Component Mode按钮,单击“器件选择”按钮,在弹出的Pick Devices(选取元器件)对话框的Keywords栏中输入元器件名称“STM32F103R6”,与关键字匹配的STM32F103R6元器件显示在元器件列表中,如图1-33所示。
图1-33 Pick Devices对话框
然后双击选中的元器件STM32F103R6,便将所选元器件STM32F103R6加入到对象选择器窗口,单击“确定”按钮完成元器件选取。
用同样的方法可以添加其他元器件。在本任务中,需要添加STM32F103R6、RES(电阻)、LED-YELLOW(黄色发光二极管)等元器件。
注意
• Proteus 8.6 Professional仿真软件只支持STM32 Cortex-M3系列的6种型号芯片,本书选择STM32F103R6芯片;
• 任何一款Cortex-M3芯片,其内核结构都是一样的,不影响其他Cortex-M3芯片的学习。
(5)放置元器件
单击对象选择器窗口的元器件STM32F103R6,元器件名STM32F103R6变为蓝底白字,预览窗口显示STM32F103R6元器件。
单击方向工具栏的按钮可以实现元器件的左旋、右旋、水平和垂直翻转,以调整元器件的摆放方向。
将鼠标指针移到编辑区某一位置,单击一次就可放置元器件STM32F103R6。
然后,参考上述放置STM32F103R6的步骤,依照图1-24放置其他元器件。
(6)调整元器件位置
在要移动的元器件上单击鼠标左键,元器件变为红色(表明被选中),在被选中的元器件外单击,即可撤销选中。
按住鼠标左键拖动被选中的元器件,移到编辑区某一位置后松开,即完成元器件的移动;在被选中的元器件上单击鼠标右键,利用弹出的快捷菜单可实现元器件的旋转和翻转,以及删除元器件。
按照上述方法,依照图1-24所示的元器件位置,可以对已放置的元器件进行位置调整。
(7)放置终端
单击模式选择工具栏中的Terminals Mode按钮,再单击对象选择器窗口的电源终端POWER,该终端名背景变为蓝色,预览窗口也将显示该终端;单击方向工具栏中的“左旋转”按钮,电源终端逆时针旋转90°;将鼠标指针移到编辑区某一位置,单击一次就可放置一个终端。以后,可以用同样的方法放置接地终端GROUND。
(8)连线
当鼠标指针接近元器件的引脚端时,该处会自动出现一个小红方块,表明可以自动连接到该点。依照图1-24所示,单击要连线的元器件起点和终点,完成连线。
(9)属性设置
在电阻R1上单击鼠标右键,弹出快捷菜单,单击Edit Propertise(编辑属性)选项,弹出Edit Component(编辑元件)对话框,如图1-34所示。
图1-34 “编辑元件”对话框
将电阻值改为100,单击OK按钮完成电阻R1属性的编辑设置。用同样的方法设置其他元器件的属性。
到此,STM32的LED控制电路就设计完成了。在以后的任务中,设计Proteus仿真电路时再涉及以上步骤,就不作详细说明了。
1.2.2 开发第一个基于工程模板的Keil μVision4工程
前面的任务已经建立了基于固件库的Keil μVision4工程模板,我们如何利用工程模板来开发第一个“点亮一个LED”工程呢?
1. 移植工程模板
(1)在建立“点亮一个LED”工程之前,先建立一个“任务2点亮一个LED”工程目录,然后把STM32_Project工程模板直接复制到该目录下。
(2)在“任务2点亮一个LED”工程目录下,将工程模板目录名STM32_Project修改为“点亮一个LED”。
(3)在USER子目录下,把STM32_Project.uvproj工程名修改为leddl.uvproj。
2. 编写第一个基于库函数的“点亮一个LED”的控制代码
在USER文件夹下面新建一个文件,并保存为leddl.c,在该文件中输入如下代码:
#include "stm32f10x.h" int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PB8引脚配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 配置PB8 为推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIOB 速度为50 MHz GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB8 GPIO_SetBits(GPIOB,GPIO_Pin_8); //PB8输出高电平,LED熄灭 while(1) { GPIO_ResetBits(GPIOB,GPIO_Pin_8); //PB8输出低电平,LED点亮 } }
代码说明:
(1)“GPIO_InitTypeDef GPIO_InitStructure;”语句声明一个结构体GPIO_InitStructure,结构体原型由GPIO_InitTypeDef确定。设置完GPIO_InitStructure里面的内容后,就可以执行“GPIO_Init(GPIOB,&GPIO_InitStructure);”语句,对PB端口进行初始化。
(2)GPIO_ResetBits()和GPIO_SetBits()是库函数,可以对多个I/O口同时置1或置0。
(3)Keil μVision4支持C++风格的注释,可以用“//”进行注释,也可以用“/*……*/”进行注释。“//”注释只对本行有效,书写比较方便。所以在只需要一行注释的时候,往往采用这种格式。“//”注释的内容可以单独写在一行上,也可以写在一个语句之后。
3. 添加主文件leddl.c到工程与编译
完成leddl.c主文件编写后,打开leddl.uvproj工程;先把工程模板里原来的main.c主文件移出工程;然后把leddl.c主文件添加到工程里面;再按图1-13所示的Components Environment and Books对话框,把Project Targets栏下的STM32_Project名修改为Leddl,即可完成“点亮一个LED”工程建立。
最后,单击Rebuild按钮对工程进行编译,生成“leddl.hex”目标代码文件。若编译发生错误,要进行分析检查,直到编译正确,修改之后再次编译,只要单击工具栏的Build按钮即可,如图1-35所示。
图1-35 完成工程编译
4. Keil μVision4和Proteus联合调试
打开Proteus“LED点亮控制”电路,双击STM32F103R6芯片,在弹出的Edit Component对话框中单击Program File后的打开按钮,在弹出的Select File Name对话框中选中前面编译生成的“leddl.hex”文件,然后单击“打开”按钮,完成“leddl.hex”加载HEX文件的选择,如图1-36所示。
图1-36 加载目标代码文件
单击仿真工具栏的“运行”按钮,STM32F103R6芯片全速运行程序。观察LED是否点亮,若LED点亮,则说明LED点亮控制是好的,如图1-37所示。
图1-37 “LED点亮控制”Proteus仿真运行
1.2.3 位操作
位操作就是对基本类型变量在位级别上进行的操作。C语言支持如表1-1所示的6种位操作。
表1-1 C语言支持的位操作
下面我们围绕表1-1中的6种位操作,着重介绍在STM32程序开发中使用位操作的相关关键技术。
1. 在不改变其他位的状况下,对某几个位赋值
针对这种情况,应该怎么做才能实现对某几个位赋值呢?我们可以把“&”和“|”两个位操作结合起来使用,步骤如下。
(1)先对需要设置的位用“&”操作符进行清零操作。
(2)再用“|”操作符赋值。
例如,在初始化时,若配置PD8引脚为推挽输出、速度为50 MHz,需将GPIOD->CRH的0~3位设置为3(即二进制0011B),这时可先对寄存器的0~3位进行“&”清零操作。
GPIOD->CRH&=0Xfffffff0; //清掉原来的设置,同时不影响其他位设置
然后再与需要设置的值进行“|”运算:
GPIOD->CRH|=0X00000003; //设置0~3 位的值为3,不改变其他位的值
2. 使用移位操作,提高代码的可读性
移位操作在STM32程序开发中也非常重要。比如在初始化时,若需要使能GPIOD口的时钟,就可使用移位操作来实现,使能PORTD时钟的语句是:
RCC->APB2ENR|=1<<5;
使能GPIOD和GPIOE口时钟的语句是:
RCC->APB2ENR|=3<<5;
这个左移位操作,就是将RCC->APB2ENR寄存器的第5位设置为1,使能PORTD时钟。为什么要通过左移而不是直接设置一个固定的值来对寄存器进行操作呢?其实,这样做是为了提高代码的可读性以及可重用性。读者可以很直观明了地看到,这行代码是将第5位设置为1。如果写成:
RCC->APB2ENR =0x00000020;
这样的代码既不好看也不好重用。类似这样的代码很多,如:
GPIOD->ODR|=1<<8; //PD8 输出高电平,不改变其他位
这样非常一目了然,8告诉读者这是第8位,也就是PD8,1告诉读者设置为1。
3. 取反位操作的应用
SR寄存器的每一位都代表一个状态。在某个时刻,我们希望设置某一位为0,同时其他位都保留为1,简单的做法是直接给寄存器设置一个值。
TIMx->SR=0xF7FF;
上述代码设置第11位为0,但代码可读性很差。你可以这样写:
#define TIM_FLAG_Update ((uint16_t)0x0001) TIMx->SR &=~(TIM_FLAG_Update <<11);
从上面的代码中,读者可以从第一条语句看出,宏定义了TIM_FLAG_Update第0位是1,其他位是0;第二条语句让TIM_FLAG_Update左移11位取反,第11位就为0,其他位都为1;最后通过按位与操作,使第11位为0,其他位保持不变。这样,读者就能很容易地看明白代码,所以代码的可读性也就非常强。