本文介绍海思平台 Hi3516 的 GPIO 使用,包括手册介绍及用户空间控制。
Hi3516 芯片支持 12 组GPIO(General Purpose Input/Output),即GPIO0~GPIO11。每组 GPIO 提供 8 个可编程的输入输出管脚(GPIO11 只有 4个) 。
每个管脚可以配置为输入或者输出。这些管脚用于生成特定应用的输出信号或采集特定应用的输入信号。作为输入管脚时,GPIO 可作为中断源;作为输出管脚时,每个 GPIO 都可以独立地清 0 或置 1。
对于引脚复用的说明,海思在 SDK 包中提供了一个 excel 表说明,该表十分重要,包括了引脚复用地址及对应比特的说明。由于内容较多,可进行搜索定位。
GPIO 寄存器对应基地址如下:
GPIO控制器 | 基地址 |
---|---|
GPIO11 | 0x120D_B000 |
GPIO10 | 0x120D_A000 |
GPIO9 | 0x120D_9000 |
GPIO8 | 0x120D_8000 |
GPIO7 | 0x120D_7000 |
GPIO6 | 0x120D_6000 |
GPIO5 | 0x120D_5000 |
GPIO4 | 0x120D_4000 |
GPIO3 | 0x120D_3000 |
GPIO2 | 0x120D_2000 |
GPIO1 | 0x120D_1000 |
GPIO0 | 0x120D_0000 |
从表格中看出,寄存器的基地址分布是十分有规律的。不同的寄存器,其操作的偏移量均相同。如 GPIO_DIR 偏移量为 0x400,不同 GPIO 寄存基地址加上该偏移量即为对应的 GPIO_DIR。
控制 GPIO 步骤分三步:先复用引脚为 GPIO 功能,再设置方向(输出或输入),寄存器为 GPIO_DIR,最后设置高低电平或读取引脚值,寄存器为 GPIO_DATA。
注意,不同平台实现的机制不同,但大体上都有这三个步骤,。
注意:
当GPIO_DIR相应的比特配置为输入时,有效读取的结果将返回管脚的值;当配置为输出的时候,有效读取的结果将返回写入的值。
GPIO_DATA 寄存器利用PADDR[9:2]实现了读写寄存器比特的屏蔽操作。该寄存器对应256个地址空间。PADDR[9:2]分别对应 GPIO_DATA[7:0],当相应的bit为高时,则可以对相应的位进行读写操作;反之,若对应bit为低则不能进行操作。例如:
若地址为 0x3FC(0b11_1111_1100),则对GPIO_DATA[7:0]这8bit操作全部有效。
若地址为 0x200(0b10_0000_0000),则仅对GPIO_DATA[7]的操作有效。
GPIO_DIR 偏移值为 0x400,可单独设置某一引脚,也可同时设置多个 GPIO 引脚。GPIO_DATA 范围为 0~0x3fc。
根据手册和经验,得到下表:
GPIO控制器 | 偏移量 | 值 |
---|---|---|
GPIOX_0 | 0x004 | 0x01 |
GPIOX_1 | 0x008 | 0x02 |
GPIOX_2 | 0x010 | 0x04 |
GPIOX_3 | 0x020 | 0x08 |
GPIOX_4 | 0x040 | 0x10 |
GPIOX_5 | 0x080 | 0x20 |
GPIOX_6 | 0x100 | 0x40 |
GPIOX_7 | 0x200 | 0x80 |
上表中X表示不同的 GPIO 组(bank)。后面数字为其具体的引脚。
下面为演示示例。
红灯 高电平亮 GPIO2_7:
himm 0x120D2400 0x80 0x120D2000为GPIO2基地址,400为方向,0x80表示bit7为1
himm 0x120D2200 0x80 接上,200表示GPIO7数据寄存器,80表示bit7为1
复用GPIO示例:
GPIO8_0 himm 0x112F0020 0x604
GPIO8_1 himm 0x112F0024 0x604
GPIO8_2 himm 0x112F0028 0x504
GPIO8_3 himm 0x112F002C 0x404
设置 GPIO8 指定引脚的方向:
himm 0x120D8400 0x1F
单独设置各个 GPIO 的方向及电平:
GPIO8_0:
himm 0x120D8400 0x1
himm 0x120D8004 0x1
GPIO8_1:
himm 0x120D8400 0x2
himm 0x120D8008 0x2
GPIO8_2:
himm 0x120D8400 0x4
himm 0x120D8010 0x4
前面只是介绍寄存器及操作的值,SDK 提示了名为 himm 的命令行工具,可以使用该工具直接对寄存器地址进行设置。 该工具的原理细节不展开,只描述大概涉及的点:
void * memmap(unsigned long phy_addr, unsigned long size)
fd = open ("/dev/mem", O_RDWR | O_SYNC);
addr = mmap((void *)0, size_in_page, PROT_READ|PROT_WRITE, MAP_SHARED, fd, phy_addr_in_page);
#define DEFAULT_MD_LEN 128
void himm_value(unsigned long ulAddr, unsigned long ulValue)
{
void* pMem = NULL;
pMem = memmap(ulAddr, DEFAULT_MD_LEN);
*(unsigned int*)pMem = ulValue;
}
void gpio_output(int type, int value)
{
switch (type)
{
case GPIO8_0:
if (value == 1)
himm_value(0x120D8004, 1);
else if (value == 0)
himm_value(0x120D8004, 0);
break;
case GPIO8_1:
if (value == 1)
himm_value(0x120D8008, 2);
else if (value == 0)
himm_value(0x120D8008, 0);
break;
case GPIO8_2:
if (value == 1)
himm_value(0x120D8010, 4);
else if (value == 0)
himm_value(0x120D8010, 0);
break;
default:
break;
}
}
由上述可知,海思平台的 GPIO 控制还是比较直观的,至少在代码层面可以与手册直接关联起来。另外,可以直接在用户空间进行操作(需其实现的内存映射模块)。当然也可以自己写驱动,只是实现相同的功能,但其底层的操作方式,是一样的。
一般嵌入式 Linux 系统的 GPIO 操作,需要计算某个引脚属于系统的第几个 GPIO 号,需要进行换算。海思平台不再需要,不过,是否也可实现传统的那种方式,还没研究。
笔者实际上接触海思仅几天时间,该平台的使用和之前做的方法不太一样,有必须记录下来。本文没有涉及 GPIO 的输入、中断,等。
海思 Hi3516DV300 SoC 用户指南。
SDK 源码。
https://blog.csdn.net/qingzhuyuxian/article/details/82981613
李迟 2020.6.21 周日 晚