打印
[其他ST产品]

CC3200之GPIO引脚分析

[复制链接]
506|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
尽快回复过|  楼主 | 2024-3-30 15:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
预备知识:
(1)volatile关键字:
volatile定义的变量一般为无需开发者自己赋值,会自动改变的变量。
在普通的程序中,编译器都具有优化功能,为了避免浪费计算机资源,会将重复或无用的语句删除。
大家观察下面一段代码。

//code1:
double gpio_input = 0;
printf("The GPIO Input1 is %f", gpio_input);

/*
光照传感器开始读取光照数据,并赋值给gpio_input
*/
printf("The GPIO Input2 is %f", gpio_input);

//code2:
volatile double gpio_input = 0;
printf("The GPIO Input1 is %f", gpio_input);

/*
光照传感器开始读取光照数据,并赋值给gpio_input
*/

printf("The GPIO Input2 is %f", gpio_input);


使用特权

评论回复
沙发
尽快回复过|  楼主 | 2024-3-30 15:33 | 只看该作者
我们将一个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灯,那么就等同于电压的叠加,此时通过芯片的电流很大,对芯片的伤害大,因此反过来,更有利于保护芯片。

使用特权

评论回复
板凳
尽快回复过|  楼主 | 2024-3-30 15:33 | 只看该作者
切入主题
CC3200的GPIO引脚和其他arm核的处理器外设使用是一样的步骤。

step1:设置时钟
CC3200简单的函数调用对想要了解开发板的初学者来说是极大地便利了,但是作为想要进一步了解的“油头”青年,只能硬着头皮去分析功能的具体实现

  MAP_PRCMPeripheralClkEnable(PRCM_GPIOA1, PRCM_RUN_MODE_CLK);

使用特权

评论回复
地板
尽快回复过|  楼主 | 2024-3-30 15:34 | 只看该作者
这句话的含义是。开启GPIOA第二组时钟(下标从0开始)。
翻开这个函数的具体定义:
#define HWREG(x) (*((volatile unsigned long *)(x)))
//外设寄存器地址放入内存,x为外设寄存器地址

void
PRCMPeripheralClkEnable(unsigned long ulPeripheral, unsigned long ulClkFlags)
{
  if(ulPeripheral != PRCM_ADC)
  {
    HWREG(ARCM_BASE + PRCM_PeriphRegsList[ulPeripheral].ulClkReg) |= ulClkFlags;
     //启用指定的外设时钟,对PRCM_ADC不做任何操作,同时启动指定的外设寄存器
     //等同于 (*((volatile unsigned long *)(0x44025000 + PRCM_PeriphRegsList[ulPeripheral].ulClkReg)))
  }
  //如果是camera,为其重新定义时钟
  if(ulPeripheral == PRCM_CAMERA)
  {
    HWREG(ARCM_BASE + APPS_RCM_O_CAMERA_CLK_GEN) = 0x0404;
    //(*((volatile unsigned long *)(0x44025000 + 0x00000000)));
  }
}

使用特权

评论回复
5
尽快回复过|  楼主 | 2024-3-30 15:34 | 只看该作者
step2:设置引脚
上一步我们对GPIOA第二组的时钟进行了设置,由于GPIO引脚具有两个方向,同时每组内包含8个引脚,因此,我们需要

设置组内引脚及方向

 MAP_PinTypeGPIO(PIN_64, PIN_MODE_0, false); 
//引脚,引脚模式,开漏输出/推挽输出
MAP_GPIODirModeSet(GPIOA1_BASE, 0x2, GPIO_DIR_MODE_OUT);
//基地址,组内号,方向


以上的code不难看出这是设置输出引脚PIN_64,由于引脚复用,因此当对同一引脚使用不同功能时,需要设置不同的引脚模式值,具体参考引脚模式对应表。

使用特权

评论回复
6
尽快回复过|  楼主 | 2024-3-30 15:34 | 只看该作者
同样,我们看函数的定义部分

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);
    //设置引脚模式
}

使用特权

评论回复
7
尽快回复过|  楼主 | 2024-3-30 15:35 | 只看该作者
设置GPIO引脚类型设置

void PinConfigSet(unsigned long ulPin,unsigned long  ulPinStrength,unsigned long ulPinType)
{
          unsigned long ulPad;
        ulPad = g_ulPinToPadMap[ulPin & 0x3F];

    //将0x4402E144置1,允许输入。
    //但是这个寄存器0x4402E144是干什么的不太清楚
    HWREG(0x4402E144) &= ~((0x80 << ulPad) & (0x1E << 8));

    //计算寄存器地址,参考寄存器地址手册        
    ulPad = ((ulPad << 2) + PAD_CONFIG_BASE);

    //找到寄存器地址,并向寄存器中写入设置值,参考每个引脚不同的配置值对应不同的功能
    HWREG(ulPad) = ((HWREG(ulPad) & ~(PAD_STRENGTH_MASK | PAD_TYPE_MASK)) |(ulPinStrength | ulPinType ));
  }
}

使用特权

评论回复
8
尽快回复过|  楼主 | 2024-3-30 15:35 | 只看该作者
GPIO方向设置

void
GPIODirModeSet(unsigned long ulPort, unsigned char ucPins,
               unsigned long ulPinIO)
{
    ASSERT(GPIOBaseValid(ulPort)); //判断GPIO基地址是否合法
    ASSERT((ulPinIO == GPIO_DIR_MODE_IN) || (ulPinIO == GPIO_DIR_MODE_OUT));     //判断GPIO方向参数是否合法,输入为0,输出为1   
    HWREG(ulPort + GPIO_O_GPIO_DIR) = ((ulPinIO & 1) ?
                                  (HWREG(ulPort + GPIO_O_GPIO_DIR) | ucPins) :
                                  (HWREG(ulPort + GPIO_O_GPIO_DIR) & ~(ucPins)));
    //HWREG(ulPort + GPIO_O_GPIO_DIR)-》拿到GPIO组地址,ucPins拿到组内地址
}

使用特权

评论回复
9
尽快回复过|  楼主 | 2024-3-30 15:35 | 只看该作者
step3:寄存器编程
CC3200官方自带的GPIO使用例程为流水灯

首先是函数GPIO_IF_LedConfigure,此函数中的GPIO_IF_GetPortNPin函数为主要的设置函数,此函数是
根据给定的GPIO值计算出引脚的GPIO基地址值和组内引脚值
GPIO_IF_LedConfigure(LED1|LED2|LED3);//同时配置3个GPIO引脚

void GPIO_IF_GetPortNPin(unsigned char ucPin, unsigned int *puiGPIOPort, unsigned char *pucGPIOPin)
{
    *pucGPIOPin = 1 << (ucPin % 8);    //组内序号从0开始,按此计算得到组内位权
    *puiGPIOPort = (ucPin / 8);       //每组有8个引脚,计算得出所属的GPIO组
    *puiGPIOPort = ulReg[*puiGPIOPort];    //取GPIO基地址值
}

使用特权

评论回复
10
尽快回复过|  楼主 | 2024-3-30 15:35 | 只看该作者
拿到了引脚的GPIO基地址值和组内引脚值,接下来我们就需要针对具体GPIO引脚进行0/1的设置了,针对
流水灯中的一个灯的具体设置进行分析
其他设置是相同的步骤

    while(1)
    {
        MAP_UtilsDelay(8000000);
        GPIO_IF_LedOn(MCU_RED_LED_GPIO); //gpio9引脚置1,点亮LED
        MAP_UtilsDelay(8000000);
        GPIO_IF_LedOff(MCU_RED_LED_GPIO);//gpio9引脚置0,关闭LED
    }

使用特权

评论回复
11
尽快回复过|  楼主 | 2024-3-30 15:37 | 只看该作者
进入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);//gpio值为1时,ucGPIOValue 值为0;反之,值为1
    ...........
}


void GPIOPinWrite(unsigned long ulPort, unsigned char ucPins, unsigned char ucVal)
{
    ASSERT(GPIOBaseValid(ulPort));
    HWREG(ulPort + (GPIO_O_GPIO_DATA + (ucPins << 2))) = ucVal;  //ucVal表示要配置gpio组内的第几个引脚的位值0/1,将对应的位值写入ucPins指定的输出引脚
}

使用特权

评论回复
12
尽快回复过|  楼主 | 2024-3-30 15:37 | 只看该作者
GPIO_IF_LedOff函数也是相同的配置

延时函数UtilsDelay

    __asm(     //_asm表示内联汇编,即在C/C++里使用汇编指令
              "    .sect \".text:UtilsDelay\"\n"
          "    .clink\n"
          "    .thumbfunc UtilsDelay\n"
          "    .thumb\n"
          "    .global UtilsDelay\n"
          "UtilsDelay:\n"
          "    subs r0, #1\n"   //r0-1;注意sub r0,r0,#1<——>r0=r0-1放到r0
          "    bne.n UtilsDelay\n"    //16位的BNE指令,“不相等(或不为0)跳转指令”,如果r0不为0就跳转到后面指定的地址,继续执行;为0,则继续执行下面语句
          "    bx lr\n");    //转到lr中存放的地址处

使用特权

评论回复
13
尽快回复过|  楼主 | 2024-3-30 15:37 | 只看该作者
lr:连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一、保存子程序返回地址;二、当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。
这段汇编还有点不太懂

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

27

主题

443

帖子

0

粉丝