预备知识:
(1)volatile关键字:
volatile定义的变量一般为无需开发者自己赋值,会自动改变的变量。
在普通的程序中,编译器都具有优化功能,为了避免浪费计算机资源,会将重复或无用的语句删除。
大家观察下面一段代码。
double gpio_input = 0;
printf("The GPIO Input1 is %f", gpio_input);
printf("The GPIO Input2 is %f", gpio_input);
volatile double gpio_input = 0;
printf("The GPIO Input1 is %f", gpio_input);
printf("The GPIO Input2 is %f", gpio_input);
我们将一个GPIO INPUT的值赋值给gpio_input,第一段代码使用double类型来定义变量,并且两次输出的结果均为0;而第二次使用volatile类型来定义变量,它的两次结果分别为0和真实的光照数值。
第一段代码:是因为编译器优化功能。编译器作为计算机的节耗小能手,不允许一个变量没有赋值而反复读取,因此它会自动过滤掉再次读取变量地址的数值的那段代码,直接从缓冲区中找到上次的值,而不是再一次根据变量地址访问主存器,进而找到主存中的变量值。而外设的数值改变一般都是存储器的数值变化,由于优化功能,光照传感器读取的数值无法被读取到,造成两次结果都是0。
[缓冲区的访问速率高于主存,但容量小于主存。因此,一般都是先从缓存中先查找变量,如果找不到,就一边从主存中查找,一边将查找的数据调至缓存,供下一次使用]
第二段代码:使用volatile关键字就可以告诉编译器gpio_input这个变量他自己会变,不要把它当成坏人,直接抹杀掉,因此下一次的读取是从主存中读取的值,即真实的外设读取的数值。
(2)HWREG(x)函数:
把x转为一个指向x的volatile unsigned long指针,然后从中取值
(3)当GPIO引脚输出1时,需要设置引脚值为0;反之,为1。
原因:引脚输出的电压都来自开发板的板供电压,点亮LED所需的电压最大为120mA,电流均来自芯片内部,如果很多LED灯,那么就等同于电压的叠加,此时通过芯片的电流很大,对芯片的伤害大,因此反过来,更有利于保护芯片。
切入主题
CC3200的GPIO引脚和其他arm核的处理器外设使用是一样的步骤。
step1:设置时钟
CC3200简单的函数调用对想要了解开发板的初学者来说是极大地便利了,但是作为想要进一步了解的“油头”青年,只能硬着头皮去分析功能的具体实现
MAP_PRCMPeripheralClkEnable(PRCM_GPIOA1, PRCM_RUN_MODE_CLK);
这句话的含义是。开启GPIOA第二组时钟(下标从0开始)。
翻开这个函数的具体定义:
#define HWREG(x) (*((volatile unsigned long *)(x)))
void
PRCMPeripheralClkEnable(unsigned long ulPeripheral, unsigned long ulClkFlags)
{
if(ulPeripheral != PRCM_ADC)
{
HWREG(ARCM_BASE + PRCM_PeriphRegsList[ulPeripheral].ulClkReg) |= ulClkFlags;
}
if(ulPeripheral == PRCM_CAMERA)
{
HWREG(ARCM_BASE + APPS_RCM_O_CAMERA_CLK_GEN) = 0x0404;
}
}
step2:设置引脚
上一步我们对GPIOA第二组的时钟进行了设置,由于GPIO引脚具有两个方向,同时每组内包含8个引脚,因此,我们需要
设置组内引脚及方向
MAP_PinTypeGPIO(PIN_64, PIN_MODE_0, false);
MAP_GPIODirModeSet(GPIOA1_BASE, 0x2, GPIO_DIR_MODE_OUT);
以上的code不难看出这是设置输出引脚PIN_64,由于引脚复用,因此当对同一引脚使用不同功能时,需要设置不同的引脚模式值,具体参考引脚模式对应表。
同样,我们看函数的定义部分
void PinTypeGPIO(unsigned long ulPin,unsigned long ulPinMode,tBoolean bOpenDrain)
{
if(bOpenDrain)
{
PinConfigSet(ulPin, PIN_STRENGTH_2MA, PIN_TYPE_OD);
}
else
{
PinConfigSet(ulPin, PIN_STRENGTH_2MA, PIN_TYPE_STD);
}
PinModeSet(ulPin, ulPinMode);
}
设置GPIO引脚类型设置
void PinConfigSet(unsigned long ulPin,unsigned long ulPinStrength,unsigned long ulPinType)
{
unsigned long ulPad;
ulPad = g_ulPinToPadMap[ulPin & 0x3F];
HWREG(0x4402E144) &= ~((0x80 << ulPad) & (0x1E << 8));
ulPad = ((ulPad << 2) + PAD_CONFIG_BASE);
HWREG(ulPad) = ((HWREG(ulPad) & ~(PAD_STRENGTH_MASK | PAD_TYPE_MASK)) |(ulPinStrength | ulPinType ));
}
}
GPIO方向设置
void
GPIODirModeSet(unsigned long ulPort, unsigned char ucPins,
unsigned long ulPinIO)
{
ASSERT(GPIOBaseValid(ulPort));
ASSERT((ulPinIO == GPIO_DIR_MODE_IN) || (ulPinIO == GPIO_DIR_MODE_OUT));
HWREG(ulPort + GPIO_O_GPIO_DIR) = ((ulPinIO & 1) ?
(HWREG(ulPort + GPIO_O_GPIO_DIR) | ucPins) :
(HWREG(ulPort + GPIO_O_GPIO_DIR) & ~(ucPins)));
}
至此,我们完成了配置。
step3:寄存器编程
CC3200官方自带的GPIO使用例程为流水灯
首先是函数GPIO_IF_LedConfigure,此函数中的GPIO_IF_GetPortNPin函数为主要的设置函数,此函数是
根据给定的GPIO值计算出引脚的GPIO基地址值和组内引脚值
GPIO_IF_LedConfigure(LED1|LED2|LED3);
void GPIO_IF_GetPortNPin(unsigned char ucPin, unsigned int *puiGPIOPort, unsigned char *pucGPIOPin)
{
*pucGPIOPin = 1 << (ucPin % 8);
*puiGPIOPort = (ucPin / 8);
*puiGPIOPort = ulReg[*puiGPIOPort];
}
拿到了引脚的GPIO基地址值和组内引脚值,接下来我们就需要针对具体GPIO引脚进行0/1的设置了,针对
流水灯中的一个灯的具体设置进行分析
其他设置是相同的步骤
while(1)
{
MAP_UtilsDelay(8000000);
GPIO_IF_LedOn(MCU_RED_LED_GPIO);
MAP_UtilsDelay(8000000);
GPIO_IF_LedOff(MCU_RED_LED_GPIO);
}
进入GPIO_IF_LedOn函数体内部,其主要作用的是MAP_GPIOPinWrite函数,其主要功能为
设置GPIO引脚值,点灯
GPIO_IF_Set(GPIO_LED1, g_uiLED1Port, g_ucLED1Pin, 1);
void GPIO_IF_Set(unsigned char ucPin, unsigned char ucGPIOValue)
{
...........
ucGPIOValue = ucGPIOValue << (ucPin % 8);
...........
}
void GPIOPinWrite(unsigned long ulPort, unsigned char ucPins, unsigned char ucVal)
{
ASSERT(GPIOBaseValid(ulPort));
HWREG(ulPort + (GPIO_O_GPIO_DATA + (ucPins << 2))) = ucVal;
}
GPIO_IF_LedOff函数也是相同的配置
延时函数UtilsDelay
__asm(
" .sect \".text:UtilsDelay\"\n"
" .clink\n"
" .thumbfunc UtilsDelay\n"
" .thumb\n"
" .global UtilsDelay\n"
"UtilsDelay:\n"
" subs r0, #1\n"
" bne.n UtilsDelay\n"
" bx lr\n");
lr:连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一、保存子程序返回地址;二、当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。
这段汇编还有点不太懂
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)