Arduino 101 GPIO 实现详解

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

概述

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

CPU

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

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 灯。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
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

1
2
3
4
5
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):

1
2
3
4
5
6
7
8
9
10
11
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

1
2
#define SOC_GPIO_BASE_ADDR              0xB0000C00
#define SOC_GPIO_SWPORTA_DDR 0x04

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

查找手册相应说明如下:

GPIO_SWPORTA_DDR

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

LED_sch

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

SPI1_M_SCK

如此证明之前的推测是正确的,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 封装,所以查看引脚表格也是看该封装的。

  3. 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] 是否使能上拉

Arduino 101 GPIO 实现详解
https://wishlily.github.io/article/arduino/2017/04/20/undefined/
作者
Wishlily
发布于
2017年4月20日
许可协议