平台:
Code Composer Studio 6.2.0 + Grace 2.2.0
MSP430G2553 LaunchPad™ Development Kit (MSP-EXP430G2ET)
以下大部分内容摘自《LaunchPad口袋实验平台 —— MSP-EXP430G2篇》傅强、杨艳 编著(TI大学计划嵌入式微控制器技术丛书)
概述
这是 MSP430G2553 单片机P1.0-P1.2 的原理框图,其他 IO 口的结构还不完全一样。
为什么 IO 口需要这么复杂的构造呢,我们来列举一下:
- 对集成电路来说,input 和 output 就是需要两套电路来实现的,并不是像一根导线那样信号能左右互通。这两套电路要有切换装置,否则输入输出会互相矛盾,于是有了 IO 方向寄存器 PxDIR。
- IO 口作为输入口时,如何把输入信号“告诉”CPU 呢?需要通过外部电路将寄存器PxIN 置位或复位,这样 CPU 就能随时读取寄存器 PxIN 的值了。
- CPU 如何命令 IO 口输出某一电平呢?CPU 需要去写寄存器 PxOUT,然后会有相应的缓冲电路将 PxOUT 的信号传递到单片机的外部引脚处。
- 数字电路中实现高低电平有多种方式,上拉(强 0 弱 1)、下拉(强 1 弱 0)还是图腾柱(强 1 强 0)的性质有很大差别,PxREN 寄存器用于控制内部上下拉电阻。
- 所有 MSP430 单片机的 P1 和 P2 口是带中断的。中断可不是“吱一声”就能有的,需要复杂的配套电路,这就有了是否允许 IO 中断寄存器 PxIE,中断标志位寄存器PxIFG,中断的边沿选择寄存器 PxIES。
- 对于高性能单片机,IO 口是高度复用的。何为复用呢,就是本来某功能最好是自己长出个引脚出来,现在被迫与 IO 共用引脚了。所以又需要选择开关了,PxSEL 和PxSEL2 寄存器就是干这个的。
- 部分 MSP430 单片机的 IO 口带振荡器,可以在不附加任何外部器件的情况下,实现电容触摸按键的识别,最流行的电容触摸也可以有。
以上总总迹象表明,IO 口没有最复杂只有更复杂,我们不关心用了多少硬件电路才实现了 IO 的这些功能,我们关心怎么配置寄存器去享用这些功能。
IO 的一般读写控制
读写 IO 口前,必须先设置 PxDIR,PxDIR 高电平代表 IO 是输出,低电平代表 IO 口是输入。CPU 读 IO,实际上是读 PxIN 寄存器。CPU 写 IO,实际是写 PxOUT 寄存器。
提示:作为高阻输入 IO 时,务必关掉内部上下拉电阻开关 PxREN,否则输入就不是高阻
态了。
IO 的输出类型
数字电路的输出有高电平 1、低电平 0 和高阻态 z。决定高低电平的是 VCC 和 GND,在初学者映像中,VCC 算电源,GND 不算电源,这样的理解不够准确和深刻。GND 也是电源,一样得有吞吐电流的能力,没有 GND 也就没有 VCC。准确的说 VCC 和 GND 都是电压源,所以 GND 经常用 VSS 来代替。根据输出电平的“强弱”分类,可将 IO 输出分为图腾柱输出、上拉电阻输出、下拉电阻输出 3 种。
图腾柱输出
图腾柱、推挽/推拉、Push and Pull、Totem Pole 指的都是同一种电路。下图是图腾柱输出电路简图,图中的开关是受控电子开关,可以由三极管或场效应管构成。由两个开关分别连接 VCC 和 VSS 构成的输出电路称为图腾柱输出,此电路输出为强 1,强 0。
- 如 (a) 所示,无论OUTPUT被接在什么电路上,OUTPUT一定是高电平1,这就是所谓的输出强1。
- 如 (b) 所示,无论OUPUT被接在什么电路上,OUTPUT一定是低电平0,这就是所谓输出强0。
- 如 © 所示,输出为高阻Z,这代表不会对其他电路造成影响。当作为输入口时,OUTPUT 应置为高阻态。
有人可能有疑问,OUTPUT 为强 1 输出接在外部电路的 GND 上会怎样?会短路。那么这种情况下到底还是不是输出 1 呢,这个问题是典型的“自相矛盾”。对于理想电压源VCC,接在哪都得是 VCC,同样对于理想的地,谁接在上面都是 0V,短路情况下到底电压是多少伏,那要看 VCC 和 GND 哪个先“扛不住”了。
初学者还有一个观点,编程并不会“实质性”损坏硬件电路,这也是不确切的。如下图(b)所示,当两个 IO 口相连(这是极其正常普遍的),且两个 IO 是图腾柱输出的话,编程就可以造成硬件损坏。
1) 当程序使得器件 1 输出强 0,器件 2 输出强 1,短路便发生了,是否会永久损坏硬件视短路持续时间的长短。
2) 如果像下图(b)那样,两个图腾柱 IO 都设置为输出,发生短路是迟早的事情。正确的做法是像下图(a)那样,将器件 2 的图腾柱输出变为高阻态,仅作为输入口使用。
3) 现在的新型单片机的 IO 口,多数属于图腾柱输出,所以,在使用 IO 口前正确配置IO 方向尤为重要。从安全考虑,CPU 上电后所有 IO 一般都会设置为下图(a)器件2 那样的输入状态,也就是图腾柱输出为高阻。
下拉电阻输出
下图所示为下拉电阻输出,该电路为强 1 弱 0 电路。
下图(a)输出为弱 0,为什么叫弱 0 呢?因为实际 OUTPUT 输出电平是不是 0 还取决于接什么样的负载。
1) 如上图(c)所示,器件 1“打算”输出 0 给器件 2,但是器件 2 不是高阻输入结构(比如 TTL 电平器件),那么器件 2 是否能正确识别输入为 0 电平,取决于 R1 和R2 的比值,以及器件 2 的 1/0 识别门限值。
2) 下拉电阻 R1 越小,电平逻辑错误就越不易发生,但是这是以功耗为代价的。
上图(b)的输出为强 1,无论 OUTPUT 接什么负载,均输出高电平 1。这就是强 1 弱 0 的由来。
与图腾柱输出不同,两个下拉电阻输出的 IO 口相连,永远不会发生短路(前提是两 IO 的 VCC 必须等电位)。
上拉电阻输出
下图所示为上拉电阻输出,该电路为强 0 弱 1 电路。
下图(a)输出为弱 1,为什么叫弱 1 呢?
1) 如果 OUTPUT 接的是高阻负载,输出肯定是 1。
2) 其他负载情况则需根据负载和上拉电阻的分压关系来计算。
3) 如下图©所示,要根据负载情况合理设定上拉电阻的取值。
下图(b)的输出为强 0,无论 OUTPUT 接什么负载,均输出低电平 0。这就是强 0 弱 1的由来。
同样两个上拉电阻输出的 IO 口相连,永远不会发生短路(前提是两 IO 的 VSS 必须等电位)。
上拉电阻输出的应用范围非常广泛:
- 上拉电阻输出可用于两种供电电压器件之间的电平匹配。如下图所示,器件 1 供电电压为 VCC1,器件 2 的供电电压为 VCC2。通过外置上拉电阻到 VCC2,器件 1 可实现与器件 2 的输出电平匹配。这种输出方式的芯片,说明书中会称为集电极开路输出(OC 输出)或漏极开路输出(OD 输出),两者区别是开关使用的是三极管还是场效应管。凡是 OC 或 OD 输出的数字器件,一定要外接上拉电阻到 VCC,否则器件将无法得到高电平。
- 上拉电阻输出还广泛用于实现“线与”逻辑。如下图所示,所有器件输出和输入同时进行(图腾柱无法做到这一点),任何一个器件都可以将公共总线电平拉低,但只有所有器件输出都为“高”,公共总线的电平才能为高,这就是“线与”逻辑“线与”逻辑更独特之处在于,所有器件都可以通过输入缓冲一直“监视”总线电平的高低,这样就能知道总线电平是否与“自己期望电平”一致,从而判断是否有别的器件在与自己争夺总线。“线与”逻辑广泛用干多机总线通信中。
MSP430 单片机的 IO 输出
IO 的内部电阻
MSP430 单片机的内部电阻构造如下图所示:
- PxREN.y 控制位决定了是否引入内部电阻。默认值 PxREN.y=0 时,不接入内部电阻。
- 当配置 PxREN.y=1 时,内部电阻被接入电路。电阻一端固定接在了 IO 输出端,至于另一端是接在 VCC(DVCC)还是 GND(DVSS),则由 PxOUT.y 决定。
- PxOUT.y 在图 5.8 中,即决定 IO 输出电平,又决定上下拉电阻的接法,要特别注意
注:芯片说明书中,PxOUT(PxREN)中的 x 代表端口号,如 P1 和 P2。PxOUT.y(
PxREN.y)中 y 的意思是某个具体的 IO,例如 P1.3 中的 3。
MSP430 的 IO 口内部电阻与 5.3 节的“上拉输出”还是“下拉输出”本质不同,因为图腾柱输出并联上拉下拉电阻丝毫不会改变输出电平值!所以,MSP430 只有一种输出方式——图腾柱输出。
那 MSP430 的内部电阻有什么用呢?
- 当IO口设为输入口时,内部电阻可通过写PxOUT.y 位固定配置为上拉或是下拉,这样就可以作为输入按键的上下拉电阻使用。
- 如下图(a)所示,按键电路输入高电平1是可靠的,但是输入低电平0则不可靠因为按键不按下时输入被悬空,电平浮动。
- 下图 (b) 所示电路中,启用了内部电阻R并置于下拉,这样一来,输入低电平就是稳定的。
- 同理,也可将开关接在VSS端,使用上拉电阻的方式构建按键电路。
线与逻辑的实现
线与逻辑在总线通信中非常有用,普通 51 单片机的 IO 口类似上图中的器件,为上拉电阻输出,天生具备线与逻辑功能。MSP430 的 IO 输出只能是图腾柱输出,如何实现“线与”呢?MSP430 单片机模拟线与逻辑输出:
- 如下图所示,首先外接上拉电阻 R。这里要注意,一定不能使用单片机 IO 内部电阻充当上拉电阻,因为内部电阻在 IO 处于输出状态是无法固定为上拉的(输出高电平时为上拉,输出低电平时为下拉)。
- 当需要输出线与逻辑 1 时,把 MSP430 IO 设为输入状态即可。
- 当需要输出线与逻辑 0 时,则正常输出低电平即可。
在实际编程中,可以用宏定义来消除“模拟”线与逻辑带来的不便。例如,将 P1.0 设为线与逻辑输出,可以用如下宏定义来描述:
#define P10_ON P1DIR &= ~BIT0 // IO设为输入,相当于线与输出1
#define P10_OFF P1DIR |= BIT0; P1OUT &= ~BIT0 // IO 设为输出,输出 0
注:宏定义是完全替换代码的意思,所以要注意分号的使用。
这样在后续的程序中,就可以用 P10_ON 和 P10_OFF 代替输出高低电平代码。使用宏定义来消除“硬件差异”的做法是极其有用的编程思想。
MSP430 单片机的 IO 输入中断
IO 外部中断使用方法
高级单片机的全部 IO 口都带外部中断功能,比如 ARM 系列。MSP430 单片机只有 P1和 P2 口带外部中断功能。要使用外部中断,遵循以下步骤:
- 通过 PxDIR 将 IO 方向设为输入。
- 通过写 PxIES,决定中断的边沿是上升沿、下降沿或两种情况均中断。
- 如果是机械按键输入,可以通过 PxREN 启用内部上下拉电阻,根据按键的接法,设定 PxOUT(决定最终是上拉电阻还是下拉电阻)。
- 通过配置 PxIE 寄存器开启 IO 中断,通过“
_enable_interrupts();
”开启总中断。 - 在中断子函数中,通过 if 语句查询具体中断的 IO 口,如果是机械按键输入,还需有消抖代码。
- 根据具体 IO 的输入,编写事件处理函数。
- 退出中断前,使用“PxIFG = 0;”来清除 IO 中断标志位。
机械按键的消抖
如下图所示,机械按键按下和弹起时,会有毛刺干扰。在一次按键过程中,会有若干次下降沿,只有①是真正的按键事件。如何避免其他几次下降沿“中断”的影响呢?
- 检测到下降沿①和④后用延时可以消灭②、③、⑤。
- ①延时后,电平仍然是 0,④延时后,电平会变成 1。据此用 if 语句可判断出④不是按下,而是弹起的毛刺。
Grace中配置GPIO
点击GPIO模块,如Port P1,
GPIO-Overview
该页部分内容机翻如下:
介绍
MSP430器件最多可以实现八个数字I/O端口,即P1至Px。每个端口有八个I/O引脚。每个I/O引脚都可以单独配置为输入或输出方向,并且每个I/O线都可以单独读或写。端口P1和P2具有中断能力。在一些设备上有更多的中断能力的引脚。P1和P2 I/O线的每个中断可以单独启用和配置,以便在输入信号的上升沿或下降沿提供中断。所有的P1 I/O线的来源是一个单一的中断向量,而所有的P2 I/O线的来源是一个不同的单一中断向量。
请注意,在Grace中,通过直接操作GPIO模块的引脚配置应该最后进行,因为Grace管理的外设模块会自动执行所需的引脚设置。
用例:端口引脚配置为输出方向
在引脚被配置为I/O功能和输出方向的情况下,每个PxOUT寄存器中的个别位代表相应I/O引脚的输出值。在这种模式下,内部上拉/下拉电阻被禁用。位=0时,输出被设置为低电平,位=1时,输出被设置为高电平。
Grace配置
- 在GPIO模块中选择适用器件封装的Pinout View
- 从P1.5和P1.7的下拉框中,选择GPIO输出选项
- 要选择GPIO输出的高电平或低电平状态,请导航到Power User视图来配置
用户代码:
// Drive P1.5, P1.7 output high
P1OUT |= (BIT5 + BIT7);
// Drive P1.5, P1.7 output low
P1OUT &= ~(BIT5 + BIT7);
用例:端口引脚配置为输入方向
在引脚被配置为I/O功能和输入方向的情况下,每个PxIN寄存器中的个别位代表相应I/O引脚的输入值。PxIN寄存器是只读寄存器。位=0表示外部输入信号为低,位=1表示为高。
Grace配置
- 在GPIO模块中选择适用器件封装的Pinout View
- 从P1.5的下拉框中,选择GPIO输入选项
用户代码:
// Check P1.5 input status
if (P1IN & BIT5) {
// P1.5 input level = logic high
// >>>>>>>> Fill-in user code here <<<<<<<<
}
// Check P1.5 input status
if (!(P1IN & BIT5)) {
// P1.5 input level = logic low
// >>>>>>>> Fill-in user code here <<<<<<<<
}
用例:使用轮询对端口输入引脚进行事件捕获
端口引脚P1.5被配置为I/O功能,输入方向。P1.5被轮询,直到读取逻辑高电平作为输入电平。
Grace配置
- 在GPIO模块中选择适用器件封装的Pinout View
- 从P1.5的下拉框中,选择GPIO输入选项
用户代码:
// Loop until P1.5 input level = logic high
while (!(P1IN & BIT5));
// P1.5 input level = logic high
// >>>>>>>> Fill-in user code here <<<<<<<<
用例:使用中断对端口输入引脚进行事件捕获
端口P1和P2的每个引脚都有中断能力。每个PxIFGx位是其对应的I/O引脚的中断标志,当选定的输入信号边沿在该引脚发生时,该标志被置位。每个PxIFG端口的中断标志必须用软件复位。
例子:端口引脚P1.5被配置为I/O功能,输入方向,端口中断与适当的端口中断边沿选择(低到高或高到低转换)一起被启用。在等待端口1中断时,器件被置于LPM状态。P1.5引脚上的适当信号边沿触发了端口中断,端口1中断服务例程被用来处理用户固件中的这一事件。
Grace配置
- 在GPIO模块中选择适用器件封装的Pinout View
- 从P1.5的下拉框中,选择GPIO输入选项
- 在Power User视图中,选择中断启用下拉框,并选择下降沿或上升沿触发的中断启用。
用户代码:
// Enter LPM with global interrupt enabled
__bis_SR_register(LPM0_bits + GIE);
// On P1.5 rising edge capture event, device wakes up to service Port ISR
// >>>>>>>> Fill-in user code here <<<<<<<<
导航到InterruptVectors_init.c
文件,在Port1 ISR的用户代码部分之间添加以下代码。
/* USER CODE START (section: PORT1_ISR_HOOK) */
// Rising edge captured on P1.5 input pin
P1IFG &= ~BIT5; // Clear P1.5 IFG
// >>>>>>>> Fill-in user code here <<<<<<<<
/* USER CODE END (section: PORT1_ISR_HOOK) */
Pinout View模式
框内为不同封装的配置视图
Pinout 32-QFN
Pinout 20-TSSOP/20-PDIP
Pinout 28-TSSOP
Power User模式
该页部分内容机翻如下:
在这个Power User视图选择中,你可以将每个单独的GPIO引脚启用上拉/下拉电阻和/或使能上升/下降沿的GPIO中断启用。
注1:对于最低功率配置,将所有未使用的GPIO引脚设置为输出端口方向。。.
注2:只有当GPIO被配置为输入方向的引脚时,上拉/下拉电阻的配置才在设备上可用。
中断处理程序的启用可以独立于中断启用。然而,当中断被启用时,中断处理程序会自动启用,以确保正确的运行时执行。要启用每个GPIO端口中断处理程序,请单击下面相应的处理程序按钮。
寄存器模式
该页部分内容机翻如下:
备注:通过启用中断处理程序,Grace在src文件夹下的InterruptVectors_init.c文件中生成了一个完全工作的中断服务程序。用户可以在ISR的指定区域内插入代码,并且代码被保留下来。当用户禁用中断处理程序时,用户插入的代码会保留在文件的底部,如果用户重新启用中断处理程序,会自动重新插入。当不再需要时,用户也可以手动删除这些代码。
上机实战
参照【MSP430G2553】图形化开发笔记(1) 配置环境建立工程。
目标
实现LED1闪烁,LED2由S1控制翻转。
配置Grace
配置电压:
配置主频为16MHz
配置两个LED引脚 P1.0、P1.6为GPIO Output,
由于按键S1 的P1.3未按下时由上拉电阻保持为高电平,按下后接地为低电平,故设为下降沿触发:
编程
main.c
主循环:以1s为周期闪烁D1:
/*
* ======== Standard MSP430 includes ========
*/
#include <msp430.h>
/*
* ======== Grace related includes ========
*/
#include <ti/mcu/msp430/Grace.h>
#define MCLK_IN_HZ 16000000
#define delay_us(x) __delay_cycles((MCLK_IN_HZ/1000000*(x)))
#define delay_ms(x) __delay_cycles((MCLK_IN_HZ/1000*(x)))
/*
* ======== main ========
*/
int main(void)
{
Grace_init(); // Activate Grace-generated configuration
// >>>>> Fill-in user code here <<<<<
while(1)
{
P1OUT |= BIT0;
delay_ms(500);
P1OUT &= (~BIT0);
delay_ms(500);
}
// return (0);
}
InterruptVectors_init.c
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR_HOOK(void)
{
/* USER CODE START (section: PORT1_ISR_HOOK) */
/* replace this comment with your code */
#define MCLK_IN_HZ 16000000
#define delay_us(x) __delay_cycles((MCLK_IN_HZ/1000000*(x)))
#define delay_ms(x) __delay_cycles((MCLK_IN_HZ/1000*(x)))
if(P1IFG & BIT3)
{
P1IFG &= ~BIT3; // Clear P1.3 IFG
delay_ms(20);
if((P1IN & BIT3) == 0)
{
if(P1OUT & BIT6)
P1OUT &= ~BIT6;
else
P1OUT |= BIT6;
while((P1IN & BIT3) == 0);
delay_ms(20);
}
}
/* USER CODE END (section: PORT1_ISR_HOOK) */
}
现象
烧录后可见LED1闪烁,LED2可由S1控制翻转。