目录

Arduino 101 GPIO 实现详解

最近买了一块 Arduino 101 开发版,准备学习研究一下。
首先从最简单的 GPIO 开始。

概述

要知道 GPIO 基本上都是 CPU 直接控制的,我们先查找一下 CPU 的资料。

CPU

主控制器是 Intel® Curie™ Module 1,其整体结构图如下:

/article/2017/04/20/arduino-101-gpio-implementation-details/Intel_Curie_Module.png

这是一个集成的控制芯片,我们发现其中的真正微控制器其实是 Intel® Quark™ SE Microcontroller C1000 2,其结构框图如下:

/article/2017/04/20/arduino-101-gpio-implementation-details/Intel_Quark_C1000.png

GPIO

手册中 1.10 节概述了 GPIO 控制器如下:

GPIO Controller

  • Provides 32 independently configurable GPIOs
  • All GPIOs are interrupt capable, supporting level sensitive and edge triggered modes
  • Debounce logic for interrupt source
  • 16 additional GPIOs available via Sensor Subsystem
  • 6 additional Always-on interrupt and wake capable GPIOs

概括来说 GPIO 由三部分组成:

  • 32 个独立配置口(暂用 SOC_GPIO_32 代表)
  • 16 个额外通过传感器子系统提供(暂用 SS_GPIO_16 代表)
  • 6 个额外提供中断及唤醒口(暂用 AON_GPIO_6 代表)

分析

Arduino 自带的 Blink 程序就是控制 GPIO 高低,从而开关 LED 灯。

// the setup function runs once when you press reset or power the board
void setup() {
    // initialize digital pin LED_BUILTIN as an output.
    pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);                       // wait for a second
}

初始化

void pinMode( uint8_t pin, uint8_t mode )
  • pin: Arduino 定义的 GPIO 编号
  • mode: INPUT,INPUT_PULLUP,OUTPUT

那么这个函数是怎么一步步配置到寄存器的呢?
首先找到 pinMode 函数实现如下:

C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\wiring_digital.c

void pinMode( uint8_t pin, uint8_t mode )
{
    /* 101 NUM_DIGITAL_PINS=32 */
    if (pin >= NUM_DIGITAL_PINS) return;
    /* g_APinDescription 为映射结构体数组 */
    PinDescription *p = &g_APinDescription[pin];
    ...
}

PinDescription 映射配置结构体包含如下:

C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\Arduino.h

typedef const struct _PinDescription
{
    uint32_t                ulGPIOId;               // GPIO port pin
    uint32_t                ulGPIOPort;             // GPIO port ID
    uint32_t                ulGPIOType;             // LMT or SS
    uint32_t                ulGPIOBase;             // GPIO register base address
    uint32_t                ulSocPin;               // SoC pin number
    uint32_t                ulPinMode;              // Current SoC pin mux mode
    uint32_t                ulPwmChan;              // PWM channel
    uint32_t                ulPwmScale;             // PWM frequency scaler
    uint32_t                ulAdcChan;              // ADC channel
    uint32_t                ulInputMode;            // Pin mode
} PinDescription;

g_APinDescription 就是提前配置好的全局结构体数组,输入 pin=LED_BUILTIN,即编号十三引脚。

C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\variants\arduino_101\pins_arduino.h

static const uint8_t LED_BUILTIN = 13;

而十三引脚的映射如下:

C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\variants\arduino_101\variant.cpp

PinDescription g_APinDescription[]={
    ...
    {8, SOC_GPIO_32, SOC_GPIO, SOC_GPIO_BASE_ADDR, 42, GPIO_MUX_MODE, INVALID, INVALID, INVALID, INPUT_MODE}, // Arduino IO13
    ...
};

mode=OUTPUT 把编号十三引脚设置为输出模式,进入如下分支(ulGPIOType=SOC_GPIO):

void pinMode( uint8_t pin, uint8_t mode ) {
    ...
    if (mode == OUTPUT) {
        ...
        else if (p->ulGPIOType == SOC_GPIO) {
            uint32_t reg = p->ulGPIOBase + SOC_GPIO_SWPORTA_DDR;
            SET_MMIO_BIT(reg, p->ulGPIOId);
        }
    }
    ...
}

可以看出映射到的寄存器地址是 0xB0000C04,
(reg = SOC_GPIO_BASE_ADDR + SOC_GPIO_SWPORTA_DDR)

C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\system\libarc32_arduino101\common\scss_registers.h

#define SOC_GPIO_BASE_ADDR              0xB0000C00
#define SOC_GPIO_SWPORTA_DDR            0x04

SET_MMIO_BIT 是在 0xB0000C04 地址 bit 8 位设置为 1。

查找手册相应说明如下:

/article/2017/04/20/arduino-101-gpio-implementation-details/PortA_DDR.png

即把 SOC_GPIO_32[8] 设置为输出,那么 LED 实际上到底是不是 GPIO[8] 呢?
原理图 LED 部分如下:

/article/2017/04/20/arduino-101-gpio-implementation-details/LED_sch.png

ATPSCK/IO2_3V_IO13 和 Curie 芯片有两处相连接,分别是 SPI1_M_SCKATP_SPI_S_SCK
在 Curie 中找到相应引脚信息如下:

/article/2017/04/20/arduino-101-gpio-implementation-details/LED_pin_map.png

如此证明之前的推测是正确的,SOC_GPIO_32[8] 控制着 LED 开关,GPIO 为高时,LED 亮,反之,LED 灭。

输出

void digitalWrite( uint8_t pin, uint8_t val )
  • pin: 0~31
  • val: HIGH,LOW

输入

int digitalRead( uint8_t pin )
  • pin: 0~31

总结

由上面分析可知,程序是通过一个事先定义好的映射表来查找对应的寄存器地址,再来设置相应的数据。
较重要的几个文件是:

  • wiring_digital.c:设置 GPIO 寄存器,包括初始化,读和写
  • variant.cpp:配置映射表
  • scss_registers.h:寄存器地址

配置

SOC_GPIO_32

  1. GPIO_SWPORTA_DDR 设置输入/输出口
  2. PMUX_PULLUP [0..3] 设置是否使能上拉

这是一个多路复用寄存器可以设置外部引脚是否为上拉输入,
寄存器和引脚的映射,我没有找到明确的解释,但是查看官方的软件说明3后,
我基本确定 4 × 32 的寄存器标号即 EXTERNAL_PAD_XXXX 标号4
如:GPIO[8] 为 EXTERNAL_PAD_42 那么如果设置其为上拉,
则,PMUX_PULLUP[1] bit 10 = 1
ps,手册中有两种封装,因为我们用的是 Curie 芯片,该芯片使用的是 WLCSP 封装,所以查看引脚表格也是看该封装的。

  1. PMUX_SEL [0..5] 设置引脚复用

SS_GPIO_16

在手册中我没有找到该部分寄存器定义,以下根据程序得出

  1. SS_GPIO0_SWPORTA_DDR or SS_GPIO1_SWPORTA_DDR 设置输入/输出
  2. PMUX_PULLUP [0..3] 设置是否使能上拉
  3. PMUX_SEL [0..5] 设置引脚复用

AON_GPIO_6

  1. GPIO_AON_SWPORTA_DDR 设置是输入/输出
  2. PMUX_PULLUP [0..3] 设置是否使能上拉
  3. PMUX_SEL [0..5] 设置引脚复用

SOC_GPIO_32

读取 GPIO_EXT_PORTA 寄存器状态

SS_GPIO_16

读取 SS_GPIO_EXT_PORTA 寄存器状态

AON_GPIO_6

读取 GPIO_AON_EXT_PORTA 寄存器状态

SOC_GPIO_32

  1. 写入 GPIO_SWPORTA_DR 寄存器
  2. 设置 PMUX_PULLUP [0..3] 是否使能上拉

SS_GPIO_16

  1. 写入 SS_GPIO_SWPORTA_DR 寄存器
  2. 设置 PMUX_PULLUP [0..3] 是否使能上拉

AON_GPIO_6

  1. 写入 GPIO_AON_SWPORTA_DR 寄存器
  2. 设置 PMUX_PULLUP [0..3] 是否使能上拉