This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
序言
1.1 综述
第一章 怎样使用库函数(CCS版)1.1 开发环境介绍
第二章 GPIO
2.1 本章引言
2.2 函数总览
1
|
void GPIO_setAsOutputPin (uint8_t selectedPort, uint16_t selectedPins)
|
该函数可以配置所选管脚作为输出管脚
|
|
2
|
void GPIO_setAsInputPin (uint8_t selectedPort, uint16_t selectedPins)
|
该函数可以配置所选管脚作为输入管脚
|
|
3
|
void GPIO_setAsPeripheralModuleFunctionOutputPin (uint8_t selectedPort, uint16_t selectedPins, uint8_t mode)
|
该函数配置所选管脚上输出方向的外设
|
|
4
|
void GPIO_setAsPeripheralModuleFunctionInputPin (uint8_t selectedPort, uint16_t selectedPins, uint8_t mode)
|
该函数配置所选管脚上输入方向的外设
|
|
5
|
void GPIO_setOutputHighOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
该函数在所选管脚上输出高电平
|
|
6
|
void GPIO_setOutputLowOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
该函数在所选管脚上输出低电平
|
|
7
|
void GPIO_toggleOutputOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
该函数翻转所选管脚的输出电平
|
|
8
|
uint8 t GPIO_getInputPinValue (uint8_t selectedPort, uint16_t selectedPins)
该函数可以获取所选管脚的输入值
|
9
|
void GPIO_enableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
|
该函数使能所选管脚的端口中断功能
|
|
10
|
void GPIO_disableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
|
该函数禁用所选管脚端口的中断
|
|
11
|
uint16 t GPIO_getInterruptStatus (uint8_t selectedPort, uint16_t selectedPins)
|
该函数可以获取被选中的端口的中断状态
|
|
12
|
void GPIO_clearInterruptFlag (uint8 t selectedPort, uint16 t selectedPins)
|
该函数可以清除所选管脚的中断标志
|
|
13
|
void GPIO_interruptEdgeSelect (uint8_t selectedPort, uint16_t selectedPins, uint8_t edgeSelect)
|
该函数可以对所选管脚的中断触发边沿类型进行选择(是上升沿触发还是下降沿触发)
|
void GPIO_clearInterruptFlag (uint8 t selectedPort, uint16 t selectedPins)
|
void GPIO_disableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_enableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
|
uint8 t GPIO_getInputPinValue (uint8_t selectedPort, uint16_t selectedPins)
|
uint16 t GPIO_getInterruptStatus (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_interruptEdgeSelect (uint8_t selectedPort, uint16_t selectedPins, uint8_t edgeSelect)
|
void GPIO_setAsInputPin (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_setAsOutputPin (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_setAsPeripheralModuleFunctionInputPin (uint8_t selectedPort, uint16_t selectedPins, uint8_t mode)
|
void GPIO_setAsPeripheralModuleFunctionOutputPin (uint8_t selectedPort, uint16_t selectedPins, uint8_t mode)
|
void GPIO_setOutputHighOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_setOutputLowOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
void GPIO_toggleOutputOnPin (uint8_t selectedPort, uint16_t selectedPins)
|
2.3 例程
2.4 本章小结
[size=11.0000pt] GPIO_setAsOutputPin()
[size=11.0000pt] GPIO_setAsInputPin()
[size=11.0000pt] GPIO_setAsPeripheralModuleFunctionOutputPin()
[size=11.0000pt] GPIO_setAsPeripheralModuleFunctionInputPin()
[size=11.0000pt] GPIO_setOutputHighOnPin()
[size=11.0000pt] GPIO_setOutputLowOnPin()
[size=11.0000pt] GPIO_toggleOutputOnPin()
[size=11.0000pt] GPIO_getInputPinValue()
[size=11.0000pt] GPIO_interruptEdgeSelect()
|
selectedPort
|
selectedPins
|
GPIO_PORT_P1
GPIO_PORT_P2
GPIO_PORT_P3
GPIO_PORT_P4
GPIO_PORT_P5
GPIO_PORT_P6
GPIO_PORT_P7
GPIO_PORT_P8
GPIO_PORT_P9
GPIO_PORT_P10
GPIO_PORT_P11
GPIO_PORT_PA
GPIO_PORT_PB
GPIO_PORT_PC
GPIO_PORT_PD
GPIO_PORT_PE
GPIO_PORT_PF
GPIO_PORT_PJ
|
GPIO_PIN0
GPIO_PIN1
GPIO_PIN2
GPIO_PIN3
GPIO_PIN4
GPIO_PIN5
GPIO_PIN6
GPIO_PIN7
GPIO_PIN8
GPIO_PIN9
GPIO_PIN10
GPIO_PIN11
GPIO_PIN12
GPIO_PIN13
GPIO_PIN14
GPIO_PIN15
|
GPIO_enableInterrupt()
GPIO_disableInterrupt()
GPIO_getInterruptStatus()
GPIO_clearInterruptFlag()
|
selectedPort
|
selectedPins
|
GPIO_PORT_P1
GPIO_PORT_P2
GPIO_PORT_PA
|
GPIO_PIN0
GPIO_PIN1
GPIO_PIN2
GPIO_PIN3
GPIO_PIN4
GPIO_PIN5
GPIO_PIN6
GPIO_PIN7
GPIO_PIN8
GPIO_PIN9
GPIO_PIN10
GPIO_PIN11
GPIO_PIN12
GPIO_PIN13
GPIO_PIN14
GPIO_PIN15
|
[size=11.0000pt] GPIO_setAsPeripheralModuleFunctionOutputPin()
[size=11.0000pt] GPIO_setAsPeripheralModuleFunctionInputPin()
|
selectedPort
|
selectedPins
|
mode
|
GPIO_PORT_P1
GPIO_PORT_P2
GPIO_PORT_P3
GPIO_PORT_P4
GPIO_PORT_P5
GPIO_PORT_P6
GPIO_PORT_P7
GPIO_PORT_P8
GPIO_PORT_P9
GPIO_PORT_P10
GPIO_PORT_P11
GPIO_PORT_PA
GPIO_PORT_PB
GPIO_PORT_PC
GPIO_PORT_PD
GPIO_PORT_PE
GPIO_PORT_PF
GPIO_PORT_PJ
|
GPIO_PIN0
GPIO_PIN1
GPIO_PIN2
GPIO_PIN3
GPIO_PIN4
GPIO_PIN5
GPIO_PIN6
GPIO_PIN7
GPIO_PIN8
GPIO_PIN9
GPIO_PIN10
GPIO_PIN11
GPIO_PIN12
GPIO_PIN13
GPIO_PIN14
GPIO_PIN15
|
GPIO_PRIMARY_MODULE_FUNCTION
GPIO_SECONDARY_MODULE_FUNCTION
GPIO_TERNARY_MODULE_FUNCTION
|
2.5 问题提出
21ic网友针对教程的学习讨论异常热烈,现将部分精彩问答及分享内容整理呈现:
A提问:
有这么一段话:“单个端口可以作为可以以字节宽度进行访问,也可以合二为一组合成字宽,以字的格式进行访问。P1/P2, P3/P4, P5/P6, P7/P8,四对端口分别对应PA、PB、PC、PD。除了中断向量P1IV和P2IV外,所有的端口寄存器是按照这种命名约定方式处理的,也就是说不存在中断向量PAIV。”后半句不理解:”除了中断向量P1IV和P2IV外,所有的端口寄存器是按照这种命名约定方式处理的,也就是说不存在中断向量PAIV“不太理解这块,还有那个”断言语句“第一次了解。
B回复:
灰常好,
编程断言:编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。
以上是百度百科里讲的。我是根据TI的英文版说明直接翻译的,但是我觉得这个断言应该是指的断点调试相关的东西吧。所以需要和网友互动,大家一起讨论其中的疑点。
另外一个关于中断向量的问题,因为在IO端口使用上,我们可以使用P1,P……,也可以使用PA,PB,, 另外前面说PA是字的宽度(16bits)P1,P2分别是字节宽度(8bits),另外PA=P1和P2,而中断向量里只定义了对应P1,和P2宽度的,没有定义对应PA宽度的。
A提问:
第一个问题中的函数名,在2.0的手册中,名字已经更改了:void GPIO selectInterruptEdge ( uint8 t selectedPort, uint16 t selectedPins, uint8 t
edgeSelect )
对于这样库函数,我感觉,在IO口操作时,选择寄存器,会来的更方便,在操作外设,比如定时器,ADC的时候,函数写起来就更方便了
B回复:
嗯,你很细心啊,那个问题就是他们制作库函数时候的一个失误,不过最新版的CCS给的是1.97,这几天刚更新了2.0,所以我赶紧传上来供大家找问题时候看看他们是否纠正,当时我做课件时候对这一点就很怀疑了,所以放在问题里了。
1、为什么关于中断边沿选择的函数GPIO_interruptEdgeSelect()的参数selectedPort可以有18个量可以选择,而不是像其他4个关于中断设置的函数,只有GPIO_PORT_P1、GPIO_PORT_P2、GPIO_PORT_PA 三个量可以选择?
问题一:
首先,要准备下基础知识:
1、端口P1和P2都具备中断功能。P1和P2两组端口的每一位管脚都可以单独配置成在输入一个上升沿或下降沿时候触发中断的功能。
2、单个端口可以作为可以以字节宽度进行访问,也可以合二为一组合成字宽,以字的格式进行访问。P1/P2, P3/P4, P5/P6, P7/P8,四对端口分别对应PA、PB、PC、PD。除了中断向量P1IV和P2IV外,所有的端口寄存器是按照这种命名约定方式处理的,也就是说不存在中断向量PAIV。
从这里我们可以看出,为什么只有GPIO_PORT_P1、GPIO_PORT_P2、GPIO_PORT_PA这三个量,而不是P3或者PB,因为只有端口P1和P2都具备中断功能。
那么下面我们继续来解释这个问题,解决这个问题前,依旧需要准备点知识,我们来看下这个五个中断函数以及功能:
void GPIO_enableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
该函数使能所选管脚的端口中断功能
void GPIO_disableInterrupt (uint8_t selectedPort, uint16_t selectedPins)
该函数禁用所选管脚端口的中断
uint16 t GPIO_getInterruptStatus (uint8_t selectedPort, uint16_t selectedPins)
该函数可以获取被选中的端口的中断状态
void GPIO_clearInterruptFlag (uint8 t selectedPort, uint16 t selectedPins)
该函数可以清除所选管脚的中断标志
void GPIO_interruptEdgeSelect (uint8_t selectedPort, uint16_t selectedPins, uint8_t edgeSelect)
该函数可以对所选管脚的中断触发边沿类型进行选择(是上升沿触发还是下降沿触发)
从这里可以看到这GPIO_enableInterrupt 、GPIO_disableInterrupt 、GPIO_getInterruptStatus 、GPIO_clearInterruptFlag 这几个中断函数都是针对具备中断功能的端口的,所有根据之前的介绍,很容易明白“其他4个关于中断设置的函数,只有GPIO_PORT_P1、GPIO_PORT_P2、GPIO_PORT_PA 三个量可以选择”。
说了这么多,还有一个关键的没讲“为什么关于中断边沿选择的函数GPIO_interruptEdgeSelect()的参数selectedPort可以有18个量可以选择”,继续补充必要的知识:
1、该系列单片机具备的I/O端口数量最高可达12组(P1至P11,外加PJ端口)。
2、具体的18个值,selectedPort可选的有效值有18(11+7)个:GPIO_PORT_P1、GPIO_PORT_P2……GPIO_PORT_P11,GPIO_PORT_PA、GPIO_PORT_PB、GPIO_PORT_PC、GPIO_PORT_PD、GPIO_PORT_PE、GPIO_PORT_PF、GPIO_PORT_PJ。
从这里可以看出18个值是怎么来的,另外注意看下GPIO_interruptEdgeSelect的功能是“该函数可以对所选管脚的中断触发边沿类型进行选择(是上升沿触发还是下降沿触发)”也就是说所有的脚都是它可以选择的,也就是说所以的端口都是可以选择的,所有就有了18个量。
1. 2.0版本的库函数 好像并不存在 所提的 问题一 好像参量全部统一了 我对比了下头文件 ,还有那个函数的名称也改变了 。 看手册的 库函数的 寄存器手册 ,只有p1 ,p2端口存在独立的中断功能,独立的中断参数。所以,如果函数没封装好的话,会导致问题一的出现 。但是 我看了下 代码 ,发现其实除了1 2口 有定义地址之外 其它 的参数 应该都是返回0xffff的 ,好像没什么意义。
2. p1 ,p2 假如这两个按照字来操作的话 ,从库函数 selectedPins <<= 8;可以看出 加入是 p1 p2结合操作的话 就是P2 在前。位并没有进行变动 ,那么就还是高位在前。
3.对于出东西来说 ,肯定是库函数 有优势,但是 就是相对于学习来说 ,从库函数入手 ,然后再到 底层 去看一下 学习一下 别人的做法 也未尝不是一件
好事 ,可能库函数对于某些操作比较频繁的东西 有锁欠缺,但是可以说 是 学习的 好帮手,出产品的利刃啊 。
作者文中写到:
uint8 t GPIO_getInputPinValue (uint8_t selectedPort, uint16_t selectedPins)
|
A提问:
if((selectedPort & 1) ^ 1)。这个是库函数gpio.c中函数GPIO_interruptEdgeSelect()中的一句话,不太明白意图,难道只是判断奇偶?还有GPIO_interruptEdgeSelect()这个函数是如何区分传入GPIO_PORT_P1和 GPIO_PORT_PA 这两个参数的呢?
下面是整个函数体
void GPIO_interruptEdgeSelect(uint8_t selectedPort,
uint16_t selectedPins,
uint8_t edgeSelect) {
uint16_t baseAddress = GPIO_PORT_TO_BASE[selectedPort];
#ifndef NDEBUG
if(baseAddress == 0xFFFF)
{
return;
}
#endif
// Shift by 8 if port is even (upper 8-bits)
if((selectedPort & 1) ^ 1)
{
selectedPins <<= 8;
}
if(GPIO_LOW_TO_HIGH_TRANSITION == edgeSelect)
{
HWREG16(baseAddress + OFS_PAIES) &= ~selectedPins;
}
else
{
HWREG16(baseAddress + OFS_PAIES) |= selectedPins;
}
}
B回复:
我刚开始和你一样困惑,不知道分析可对。
#define HWREG16(x) (*((volatile uint16_t *)((uint16_t)x)))
if((selectedPort & 1) ^ 1) 这句话所有的GPIO函数都用到了,个人感觉是先判断端口是奇数端口还是偶数端口,
如果是奇数,不区分是GPIO_PORT_P1或是GPIO_PORT_PA,所操作的寄存器直接对对应selectedPins进行操作,不执行 selectedPins <<= 8;
如果是偶数端口需要移位执行selectedPins <<= 8; GPIO_PORT_P1和GPIO_PORT_P2传递的基地址相同!!!!
这里全部都按照16位地址操作的!!!!!
主要原因是因为HWREG16(X)宏定义。
HWREG16(baseAddress + OFS_PAIES) &= ~selectedPins; //
uint16_t baseAddress = GPIO_PORT_TO_BASE[selectedPort]; //我跟踪一下GPIO_PORT_P1和GPIO_PORT_P2传递的基地址相同
static const uint16_t GPIO_PORT_TO_BASE[] = {
0x00,
#if defined(__MSP430_HAS_PORT1_R__)
__MSP430_BASEADDRESS_PORT1_R__,
#elif defined(__MSP430_HAS_PORT1__)
__MSP430_BASEADDRESS_PORT1__,
#else
0xFFFF,
#endif
#if defined(__MSP430_HAS_PORT2_R__)
__MSP430_BASEADDRESS_PORT2_R__,
#elif defined(__MSP430_HAS_PORT2__)
__MSP430_BASEADDRESS_PORT2__,
#else
0xFFFF,
。。。。。}
版主的第一个问题“1.为什么关于中断边沿选择的函数GPIO_interruptEdgeSelect()的参数selectedPort可以有18个量可以选择,而不是像其他4个关于中断设置的函数,只有GPIO_PORT_P1、GPIO_PORT_P2、GPIO_PORT_PA 三个量可以选择?”
第一:是因为在12个端口中只有端口P1和P2具备中断功能,其余端口都不具有中断功能,而P1/P2对端口对应PA,所以PA端口也具备中断功能;
第二:处理GPIO中断的有:GPIO_enableInterrupt()、GPIO_disableInterrupt()、GPIO_clearInterruptFlag()、GPIO_getInterruptStatus()、GPIO_interruptEdgeSelect(),前四个(使能中断+禁用中断、清除中断标志+获取中断状态)从其名称上看,明显是只有具备中断功能的端口才能适用的,试想一个没有中断功能的端口怎么去使能其中断,怎么去清除其中断标志位?而第五个是中断沿翻转,每个端口都可以请求中断,这就决定了每个端口都可以选择其请求的中断是什么沿触发。
个人理解,未涉及到寄存器,只从其字面含义去理解,如若有错欢迎指出。
A提问:
在2.2小节中的“函数文档”部分很多函数都提到了“selectedPins是所选端口上的管脚。其掩码值可以是GPIO_PIN0、GPIO_PIN1、GPIO_PIN2……GPIO_PIN15等十六个值的逻辑或。该函数是通过修改寄存器PxOUT的位实现,返回值 None(空)。”这里我有些不明白。
我的理解是selectedPins应该是0~15个pin中的某一个,但是这里说的“掩码值”指什么呢?是“十六个值的逻辑或”?那我的理解是GPIO_PINn(n=0~15)就是16位的寄存器,selectedPins是几就在那一位上置1,最后将16个寄存器的16位值逻辑或,得到一个寄存器的16位表示,第几位是1就表示第几个pin被选。
比如说selectedPins=pin0,那GPIO_PIN0=0x1000_0000_0000_0000,其余的GPIO_PIN=0x0000_0000_0000_0000.
但是如果真是这样的话,感觉好浪费寄存器。我的理解是不是不对啊?
B回复:
"那GPIO_PIN0=0x1000_0000_0000_0000,其余的GPIO_PIN=0x0000_0000_0000_0000."这句话表述就有错了,0x代表16进制数。不会浪费寄存器,GPIO_PINn(n=0~15)在头文件中被宏定义,比如:0x0001、0x0002、0x0004、0x0008······,操作的时候只要运用逻辑运算将寄存器的某些位置1或清0就行。
A回复:
0x那是我表述错了。
只说了我的理解,想看下我的理解是不是不对,因为按我的理解是相当浪费i寄存器了。只有16种情况,用1个4位的寄存器就可以表示清楚的,为什么用了16个16位的寄存器呢?不过按我所说的理解,只用1个4位的寄存器还需要提供些解码电路设计。
期待正确的理解。
B回复:
不过还是用版主最后的话来解释比较好——“根本不用管关心需要操作什么寄存器,毕竟寄存器名字读起来没有自然语言表达更清楚明了。”
也是,通过例程来看,使用这些GPIO驱动库函数时根本不需要去关注操纵什么寄存器那些的。
A回复:
原来你的“浪费寄存器”是这个意思啊,那只是1个16位的寄存器,不是16个。
B回复:
即使是一个16位的,我也不明白那段话,你能详细说一下吗?
“其掩码值可以是GPIO_PIN0、GPIO_PIN1、GPIO_PIN2……GPIO_PIN15等十六个值的逻辑或。”这逻辑或之后怎么去表示是哪个pin被选中了呢?
A回复:
16位的寄存器,每一位控制一个引脚,GPIO_PIN0就是0000 0000 0000 0001,与寄存器进行逻辑或操作后,寄存器的0bit不就置1选中了吗,以此类推。
A
一直对MSP430很好奇
现在32位的单片机天下,这个16bit的单片机,Ti还在推
看来真的学习学习了,想必有独到之处吧。。。
B回复
是的,MSP430凭借独特的低功耗特性完胜手持设备,电池供电设备以及太阳能供电设备的其他主控MCU应用,低功耗以其独有专利的分级系统休眠特性实现。所以MSP430单片机家族只会壮大不会被淘汰,先前推出的MSP430G2xx系列就凭借超高性价比受到了市场的高度认可,这次推出升级版MSP430i2xx系列,同时也不甘落后正式推出了相关库函数,库函数的推出就是为了适应MSP430外设功能越来越强大而带来了学习负担,这样我们就不用把精力关注在内部硬件结构的学习上了,从而可以更快的进入到产品的开发应用。另外这次以MSP430i2xx,为主线进行讲解,目的是为了让大家能够轻松学会,这些对于后续学习MSP430FRxxx系列奠定了基础,TI的铁电系列功能更加强大,性能更加优良,希望大家也能够有兴了解一下MSP430FRxx系列。如果有需要后续会推出相关教程,或者视频教程。
A提问:
刚刚看了一下库函数,在2.0的库里没找到GPIO_interruptEdgeSelect().
后来发现,DriverLib1.97版里面是GPIO_interruptEdgeSelect(),DriverLib2.0版里面是GPIO_selectInterruptEdge().怎么库升级把函数名都变了啊?这岂不是给程序的移植制造麻烦吗?你觉得有必要吗?
B回复:
字宽度和字节宽度不同,通过这个实现的,因为在内存中他们是连续的,且高位在前,低位在后。如果定义一个16bits宽度的字,那么就等于两个连续的8bits字节。
A回复:
程序中那个地方用来区分字寻址和字节寻址的呢?我没找到。能否指出来?谢了。
B回复:
#define GPIO_PORT_P1 1
#define GPIO_PORT_P2 2
#define GPIO_PORT_P3 3
#define GPIO_PORT_P4 4
#define GPIO_PORT_P5 5
#define GPIO_PORT_P6 6
#define GPIO_PORT_P7 7
#define GPIO_PORT_P8 8
#define GPIO_PORT_P9 9
#define GPIO_PORT_P10 10
#define GPIO_PORT_P11 11
#define GPIO_PORT_PA 1
#define GPIO_PORT_PB 3
#define GPIO_PORT_PC 5
#define GPIO_PORT_PD 7
#define GPIO_PORT_PE 9
#define GPIO_PORT_PF 11
#define GPIO_PORT_PJ 13--------------------------------------------------------------------------------------
看头文件的宏,在A开始后,是按照2字节宽度开始起名字,所以如果操作时候使用2字节宽度的操作就是2字节,如果还是使用1字节宽度的操作,那么就是只对PA的低8位操作
A回复:
不好意思还是不太明白,能不能在.c里面指出具体的地方,谢了!
B回复:
在头文件的.h里。