2.1 MSP430 GPIO概述
通用输入/输出接口(General Purpose Input/Output,简称GPIO)是单片机通过引脚来控制或者采集外部电路的接口,是单片机最基础的功能之一。
不同型号的MSP430可以提供一个或者多个8位I/O口。一般来说引脚越多的芯片提供的I/O口也越多。每8位I/O口会被分为一组(port),在每组当中再给每位编号(bit)。例如Port1中的第一位I/O口就命名为P1.0。
每一组I/O口都由单片机中的寄存器来控制。例如要读取P1.0上的电平,可以通过读输入寄存器P1IN上的bit0来实现。本章中我们将介绍与I/O口有关的寄存器,以及如何用这些寄存器来控制I/O口。
2.2 GPIO寄存器
MSP430的每组GPIO都是由对应的寄存器控制的,其中有3个寄存器是最重要的,分别是方向寄存器(PxDIR)、输出寄存器(PxOUT)和输入寄存器(PxIN)。方向寄存器决定了引脚的功能是输入还是输出;输出和输入寄存器则直接关联引脚的电平状态。
本节所介绍的寄存器详细说明请见“MSP430x2xxFamily User's guide”的8.1-8.3节。
2.2.1 方向寄存器(PxDIR)
在MSP430中,每个I/O口都可以被单独配置成输入或者输出状态。是配置成输入还是输出是由方向寄存器(也叫DIR寄存器)决定的。方向寄存器为0是输入,为1是输出。例如,要把P1.0和P1.7同时配置成输出,需要设置P1DIR=0x81,如下图所示。
想要配置MSP430的寄存器,还有多种方式可以实现。
1) 上图中的Register example是直接给寄存器赋值,这是最直接的方式。
2) 利用MSP430FR6989.h中的宏定义,也可以让寄存器的配置更直观更好记。例如头文件中已有以下宏定义:
#define BIT0 (0x0001)#define BIT1 (0x0002)#define BIT2 (0x0004)#define BIT3 (0x0008)#define BIT4 (0x0010)#define BIT5 (0x0020)#define BIT6 (0x0040)#define BIT7 (0x0080)复制代码
那么P1DIR = 0x81就可以改写为P1DIR = BIT0 + BIT7;这样就不用每次都将想要写入寄存器的值换算成16进制了。
3) 另外图中的 MSP430wareexample是调用了MSP430驱动库来实现同样的功能。驱动库中提供了一个函数GPIO_setAsInputPin()来将I/O口配置为输出。
2.2.2 输出寄存器(PxOUT)
利用方向寄存器将I/O口配置为输出以后,就可以通过写输出寄存器PxOUT来给端口赋值了。例如要将P1.7设为高电平,直接写
P1OUT|= 0x80;
或者
P1OUT |= BIT7;
就可以了。
2.2.3 输入寄存器(PxIN)
当引脚作为输入时,引脚上的电平值会被缓存到输入寄存器PxIN当中。读取PxIN的值就可以得知当前的引脚状态。
与输出相比,I/O口的输入状态配置起来会更复杂一点。原因在于一个问题:如果一个引脚作为输入,当它没有被外部电路赋值时,引脚是什么电平?实际上这种情况发生时引脚处于浮动状态——也就是说即有可能是高电平也有可能是低电平。浮动状态是我们不想看到的,因为它即可能影响程序的逻辑,也会在电平转换时消耗不必要的能量。因此一般我们在引脚作为输入时会通过一个电阻将该引脚接到电源或地,这样就形成了一个弱上拉/下拉状态,这个电阻成为上拉/下拉电阻。接了上拉/下拉电阻以后,输入引脚的电平就不会乱跑了,而当外部电路的高/低电平接到引脚上时,该引脚又可以根据外部电平改变状态。
为了使用方便,MSP430单片机内集成了内部的上拉/下拉电阻。通过配置PxREN寄存器可以使能上拉/下拉电阻,然后再配置PxOUT寄存器可以选择是上拉还是下拉。
下面来看一个例子,如果要将P1.7配置为输入,同时使能内部上拉电阻,代码如下:
P1DIR &=~ BIT7; // Set P1.7 as inputP1OUT |= BIT7; // Set pull-up resistor for P1.7P1REN |= BIT7; // Enable internal pull-up resistor编译并下载程序,开始运行后按下S2按键,如果一切正常,绿色LED会亮起,松开S2后绿色LED会熄灭。
思考题:本实验中按键的状态检测是通过不断读取P1IN来实现的,这种方式叫做轮询。轮询是一种比较消耗CPU资源的方式,因为CPU需要不断读取GPIO的状态,就好像主人在家等快递,但家里没有门铃(也不能敲门),主人只能一遍又一遍的打开门看看外面有没有人。有没有另一种方式能够帮主人节省精力呢?下一章我们将介绍中断,中断就好像装上了门铃,可以提醒主人有人来了,大大节省主人(也就是单片机CPU)的精力和时间。
2.3 GPIO引脚复用
现在的单片机集成的功能越来越多,例如串口、定时器、ADC等,这些外设都需要引脚,但如果为每个外设都留下单独的引脚,单片机的引脚数量将会巨大,这既不经济也不实用。另外这些外设并一定会同时使用,因此单片机提供了引脚复用的功能,将GPIO和其他外设放在一个引脚上,使用的时候通过寄存器来选择用哪一个功能。
例如下图中,MSP430G2553的引脚8,它既可以作为GPIO模块中的P2.0,也可以作为定时器的TA1.0端口。其他引脚也是类似。具体每一个引脚有哪些功能请见MSP430G2553的datasheet。
复用的引脚可以通过PxSEL寄存器来选择功能。鉴于有些引脚的功能多于2个,1位寄存器不够用,所以MSP430G2553有PxSEL和PxSEL2两个功能选择寄存器,它们两个再加上PxDIR寄存器配合起来可以选择多个引脚功能。
每个MSP430单片机的引脚功能具体定义在芯片datasheet中,查找“PortSchematics”部分可以看到每个引脚有什么功能,以及如何配置寄存器来启用这些功能。
在User’s Guide中,也会介绍PxSEL寄存器,但不会具体介绍针对每个芯片的具体引脚定义。一般来说引脚复用功能可以分为Primaryfunction和Secondary function,对照datasheet和User’sGuide可以发现二者是可以对应的。
例如我们要将P1.0端口配置成TA0.TACLK功能(在这里先不关心这个功能具体有什么用),查阅datasheet可以找到该功能对应的3个寄存器的值,因此将寄存器对应赋值即可启用这个功能。
P1DIR &=~ BIT0; // Set P1DIRP1SEL |= BIT0; // Set P1SEL and P1SEL2P1SEL2 ^=~ BIT0;2.4 GPIO实验
下面我们将用两个实验来熟悉GPIO的两种基本应用场景——LED闪烁和读取按键。这两个应用场景分别对应GPIO的输出和输入,也是最经典的应用。通过这两个实验也可以进一步熟悉单片机GPIO的寄存器操作。在实验开始之前需要清楚MSP430G2 LaunchPad板上LED和按键的硬件电路。板上共有2个LED灯和2个按键,P1.0连接红色LED,P1.6连接绿色LED。这两个LED都是通过跳线帽连接的,如果想断开LED,只需拔掉跳线帽即可。P1.3连接左边的按键,这个按键可以由用户自定义功能。右边的按键是Reset键,按下之后单片机会复位。
2.4.1 GPIO输出实验——LED闪烁
LED闪烁实验可谓是单片机编程中的“Hello World”实验,我们将用这个实验来熟悉GPIO寄存器的操作,同时利用该实验也将讲解如何进行程序调试。实验目标:让MSP430G2553 LaunchPad上的绿色LED闪烁。
1) 新建一个CCS工程
新建一个CCS工程,芯片型号选择MSP430G2553,工程模板选择EmptyProject(with main.c)
2) GPIO初始化
将Lab2a_Output.txt中的代码拷贝到main.c中。这段代码中已经写好了程序框架,需要自行编写的是GPIO的初始化和LED闪烁的代码。首先在主程序中需要对GPIO做初始化,请根据之前介绍的LaunchPad硬件电路找到绿色LED对应的I/O口,并将其初始化为输出状态。请在“// Addyour code below to initialize the GPIO”下面一行添加你自己的初始化代码。 int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer // Add your code below to initialize the GPIO // End of your code 复制代码
3) LED闪烁
初始化完成后,就要通过编程让LED开始闪烁了。闪烁的过程是通过一个while循环完成的,每个循环中先将LED对应I/O口的电平置高,再置低,中间添加延时就可以实现LED闪烁。程序中已经设置好了延时的时间,请自行添加改变GPIO状态的语句。如果一切正常,代码执行后绿色LED将以1s左右为周期闪烁。 while(1) { // Add your code below to set P1.6 high // End of your code _delay_cycles(500000); // Add your code below to Set P1.6 low // End of your code _delay_cycles(500000); } return 0; 复制代码
4) 在线调试程序
将程序编译并下载到LaunchPad中开始运行,LED是否按照预期开始闪烁了呢?如果没有也不用着急,下面我们将介绍如何在CCS中利用断点和单步调试功能对一个程序进行调试。在此之前请参考本实验的solution代码,该代码是本实验的示例代码。
在程序运行状态下点击暂停按钮,再点击Restart按钮,让程序重新回到初始状态。
下面我们打开寄存器查看窗口,来看一下P1DIR和P1OUT寄存器的值。在CCS Debug界面下,右上方应该会有寄存器查看窗口(Registers),如果没有可以通过View->Registers菜单打开。Registers窗口中可以看到所有的寄存器,展开Port_1_2可以找到P1DIR和P1OUT两个寄存器。
下面我们要查看程序执行过程中这两个寄存器的变化过程,但因为程序执行的速度是很快的,要看到寄存器变化的瞬间,必须利用CCS的单步调试功能。点击Step Over按钮,可以让程序一句一句的执行,同时在程序每一行的最左边会出现一个蓝色箭头,表示当前程序执行到这一句了。点击Step Over按钮,直到箭头指到GPIO初始化语句P1DIR |=BIT6这一句上。
此时程序还未执行这一句,再点击一下Step Over按钮,程序就将执行该语句。点击Step Over之后请观察Registers窗口,我们会发现P1DIR寄存器变成了黄色,这表示刚才执行的语句使P1DIR发生了变化。展开P1DIR,会发现其中第6位即P1.6从0变成了1,这表示我们已经成功将P1.6端口设成了输出端口。
下面我们将观察单片机是如何让LED实现闪烁的。这次我们使用断点功能来调试。所谓断点其实就是人为设置的程序暂停点,程序执行到断点处将自动暂停,以方便用户观察寄存器变化。
分别在“P1OUT |=BIT6”和“P1OUT&=~ BIT6”这两句之前的蓝色区域用鼠标双击,会出现一个小方块,表示我们在这两句之前加了断点。现在再点击前进按钮 ,程序会自动执行到第一个断点之前停下。
再点击一次前进,程序会停在第二个断点处。此时程序已经执行完“P1OUT |=BIT6”以及延时语句,我们观察Registers窗口,会发现P1OUT的第6位变成了1,同时观察LaunchPad,应该会发现绿色LED已经亮起。
再点击一次前进,程序会继续执行,直到遇到下一个断点,此时程序已完成一个while循环并回到第一个断点处,观察寄存器会看到P1OUT第6位变成了0,同时LaunchPad上的绿色LED熄灭。
不断重复点击前进,就会发现LED灯不断重复点亮和熄灭的过程,这就是LED闪烁的实现方式。
学会了使用单步调试和断点功能,当程序遇到问题时我们就可以利用这些调试手段快速找到出现问题的地方,以便我们分析程序出错的原因。上述的调试方法需要大家熟练掌握。
思考题1:刚才的程序中,我们使用了2句语句来分别将P1.6置高和置低。请将这两句合并成一句,完成同样的闪烁功能。 提示:回顾1.7节中介绍过的“取反”操作。
思考题2:本实验中使用了延时函数来实现LED闪烁,其实这是一种很浪费CPU资源的实现方式。请思考原因并想想有没有更好地解决方法。 2.4.2 GPIO输入实验——读取按键 本实验中我们将通过GPIO的输入功能来实现按键状态的读取,并结合上一个实验中学到的输出功能来实现用按键控制LED灯。 实验目标:读取MSP430G2553 LaunchPad上S2按键状态,并用该按键控制绿色LED。按键按下时让LED亮起,按键松开时让LED熄灭。 1) 新建一个CCS工程 新建工程的过程与之前相同,此处不再赘述。 2) 初始化GPIO 将Lab2b_Input.txt中的代码拷贝到main.c中。首先要添加的是GPIO初始化代码,请回想2.2节的内容,对输入引脚做初始化时需要考虑上拉/下拉电阻的设定。查看“MSP-EXP430G2 LaunchPad Development Kit User's Guide”手册,其中有LaunchPad的原理图,我们发现S2按键连接的P1.3端口连接了R34上拉电阻,但请注意LaunchPad板上的R34并没有焊接,所以P1.3端口相当于悬空。因此我们必须使用单片机GPIO内部上拉电阻来初始化P1.3。在程序中初始化GPIO处添加合适的语句来初始化P1.3输入端口。 除了输入端口的初始化,本实验中我们还需要使用绿色LED灯,因此也需要在程序中添加输出端口初始化代码。 总结一下,初始化程序中我们需要做的是: 将S2对应的I/O口(P1.3)设置为输入;设置P1.3的内部上拉电阻;将绿色LED对应的I/O口设置为输出。 请在程序中添加GPIO初始化代码: int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer // Add your code below to initialize the GPIO // End of your code 复制代码 3) 按键读取及LED控制 初始化完成之后,我们需要做的就是检测S2按键的状态,并根据按键状态控制LED灯的亮灭。 如果上一步骤中P1.3初始化正常,那么现在S2按键所对应的引脚状态是: S2按下:P1.3 = 0(接地)S2松开:P1.3 = 1(接Vcc) 因此按键检测的实现方式就是读取P1.3端口的输入状态,这需要通过读取P1IN寄存器来实现。在下面程序中的while循环中,我们不断重复读取P1.3的输入状态,当检测到P1.3为0时,就点亮LED灯;否则就关掉LED灯。 while (1) // Test P1.3 { if (/* Add your code here to determine if P1.3 = 0 */) { // Add your code to turn on the green LED } else // Add your code to turn on the green LED } 复制代码 编译并下载程序,开始运行后按下S2按键,如果一切正常,绿色LED会亮起,松开S2后绿色LED会熄灭。 思考题:本实验中按键的状态检测是通过不断读取P1IN来实现的,这种方式叫做轮询。轮询是一种比较消耗CPU资源的方式,因为CPU需要不断读取GPIO的状态,就好像主人在家等快递,但家里没有门铃(也不能敲门),主人只能一遍又一遍的打开门看看外面有没有人。有没有另一种方式能够帮主人节省精力呢?下一章我们将介绍中断,中断就好像装上了门铃,可以提醒主人有人来了,大大节省主人(也就是单片机CPU)的精力和时间。
本文参考:https://e2echina.ti.com/group/universityprogram/students/f/11/p/149721/424157#424157