由于部门的一个负责海思驱动开发的老同事另谋高就了,部门又暂时找不到人来对接他的任务,因此领导就让我这个菜鸟来硬着头皮顶上了。在这我也对这位老同事表示深入的感谢,在对接的期间,那么耐心教导我,让我这个刚出来社会不久、又没怎么接触过海思平台的菜鸟学习到了不少东西。linux
回归正题:部门在海思3531A产品调试的时候发现SPI通讯不正常,使用示波器发现SPI的CS片选引脚怎么也拉不低,SCLK(时钟)、SDI(数据输入引脚)和SDO(数据输出引脚)的信号也不正常。在我不停地阅读官方资料和询问大神们,最终调好了。app
因此接下来,本文章会针对 海思SPI配置的问题 来记录下我调试的整一个过程,包括官方资料的阅读。ide
在一个新的平台上面开发,咱们首先须要阅读官方的资料,并获取到本身须要的资料。这里主要是针对与 SPI配置 相关的资料查找。工具
由于涉及到SPI的使用(若是不熟悉SPI通讯协议的,能够先去搜索了解下先),那就会涉及到管脚的使用,因此咱们先找到芯片的管脚信息表,这个管脚信息表通常芯片平台的官方资料里面都会有,并且通常都会放在硬件的目录下。海思平台的管脚信息表的路径是 00.hardware\chip\document_cn\HI3531AV100_PINOUT_CN.xlsx ,打开能够看到这个表格有不少页,这些页的内容包括管脚的功能/复用功能、默认值、特性以及管脚复用寄存器等信息。学习
咱们在HI3531AV100_PINOUT_CN.xlsx这个表格的《1.管脚信息表》页找到SPI管脚。命令行
Pin Num | Pin Name | Voltage(V) | Default | IO Type | Mux Control Register Name | Drive&Slew Control Register Name | Function n |
---|---|---|---|---|---|---|---|
管脚号 | 管脚名称 | 管脚输入或输出的高/低电平 | 默认的管脚是输入/输出高仍是低等。 | IO口的类型 | 复用寄存器名称,有的话就表明该管脚有复用功能。若是不是硬件复用可在《3.管脚复用寄存器》页查找该名称,能够看到该管脚的复用寄存器地址以及功能描述;若是是硬件复用,则能够在《5.硬件复用关系》页查找到。 | 这是管脚的驱动能力寄存器,可在《4.管脚驱动能力寄存器》页查看。 | 这是对应管脚的功能描述。 |
经过上面管脚信息表,咱们发现和SPi相关的有7个管脚,并且都是复用管脚,其中四个是片选管脚,其余则是SPi的时钟(SPI_SCLK)、数据输入(SPI_SDI)、数据输出(SPI_SDO)管脚。咱们须要使用SPI的功能,那么就须要正确配置每一个引脚。3d
接下来咱们先根据上面的复用管脚的复用寄存器名称,到《3.管脚复用寄存器》页或者《5.硬件复用关系》页里面找:调试
片选管脚是硬件复用的,这通常不须要软件来进行配置,须要硬件工程师来配合。经过我的的调试经验来讲,须要用到的的片选管脚硬件上不要接上拉电阻或者下拉电阻,不然海思芯片有可能会没法控制该片选管脚。code
经过上图得知,SPI_SCLK、SPI_SDI、SPI_SDO管脚的默认值都不是和SPI通讯相关的,因此咱们须要配置这些管脚为SPI功能,也就是要往寄存器写0x01的值。blog
在HI3531AV100_PINOUT_CN.xlsx 表格中,SPI_SCLK、SPI_SDI、SPI_SDO管脚的寄存器的地址和值都找到了,可是片选管脚的寄存器呢?不在这个表格,那就是在上面提到的 00.hardware\chip\document_cn\Hi3531A H.264编解码处理器用户指南.pdf 文档中了。打开文档,咱们发现该片选的寄存器是在 3.系统/3.5.系统控制器/3.5.5.外设控制寄存器 的目录中,是外设功能选择寄存器2(MISC_CTRL5)控制的片选:
这样的话我么就知道片选引脚的寄存器地址是 0x1212 001四、对应的值也能够经过上图MISC_CTRL5寄存器得知了。
到了这里,SPI全部引脚的寄存器地址和对应的值都知道了,那这些应该怎么配置呢?
先不要着急,咱们先来打开 01.software\board\document_cn\Hi3531A SDK 安装以及升级使用说明.txt 这个文档(当咱们第一次拿到官方SDK的时候就应该看这个来进入着手,里面内容写得不是很详细,可是覆盖面挺大的。01.software\board\document_cn这个目录下的文档都是与软件开发相关的文档),看一下有没有和管脚配置的相关的引导。
能够发现是有说到与管脚配置相关的说明,并且正好是复用管脚的使用。经过以上的文字可知,颇有可能咱们的相关管脚就是在mpp/ko目录下的sh脚本中配置的,咱们到SDK的mpp/ko目录看下sh脚本。其中pinmux_vicap_i2c_i2s.sh脚本的内容以下:
#!/bin/sh echo "run $0 begin!!!"; #I2C himm 0x120F01CC 0x1; # 0:GPIO19_6 01:I2C0_SDA himm 0x120F01D0 0x1; # 0:GPIO19_7 01:I2C0_SCL himm 0x120F0148 0x2; # 0: GPI13_0 01: SPI_SCLK 2:I2C1_SCL #恰好是SPI_SCLK复用寄存器的地址,这里设置为了I2C1_SCL功能 himm 0x120F014C 0x2; # 0: TEST_CLK 01: SPI_SDO 2:I2C1_SDA #是SPI_SDO复用寄存器的地址,这里设置为了I2C1_SDA功能 #VICAP himm 0x120F0000 0x2; himm 0x120F0024 0x2; himm 0x120F0048 0x2; # 0:GPIO21_2 01:VI_ADC_REFCLK0 2:VI1_CLK himm 0x120F004c 0x2; himm 0x120F0070 0x2; himm 0x120F0094 0x2; # 0:GPIO21_5 01:VI_ADC_REFCLK1 2:VI3_CLK himm 0x120F0098 0x2; himm 0x120F00bc 0x2; himm 0x120F00E0 0x2; # 0:GPIO12_1 01:VI_ADC_REFCLK2 2:VI5_CLK himm 0x120F00e4 0x2; himm 0x120F0108 0x2; himm 0x120F012C 0x2; # 0:GPIO20_6 01:VI_ADC_REFCLK3 2:VI7_CLK #UART himm 0x120F0200 0x3; # 0: GPIO18_2 1:VOU_SLV_DAT11 2:UART1_RTSN 3:UART0_RTSN himm 0x120F0204 0x3; # 0: GPIO18_3 1:VOU_SLV_DAT10 2:UART1_CTSN 3:UART0_CTSN #I2S himm 0x120F0130 0x1; # 0: GPIO11_0 1: I2S0_BCLK_RX himm 0x120F0134 0x1; # 0: GPIO11_1 1: I2S0_WS_RX himm 0x120F0138 0x1; # 0: GPIO11_2 1: I2S0_SD_RX himm 0x120F013C 0x1; # 0: GPIO11_3 1: I2S1_BCLK_RX himm 0x120F0140 0x1; # 0: GPIO11_4 1: I2S1_WS_RX himm 0x120F0144 0x1; # 0: GPIO11_5 1: I2S1_SD_RX himm 0x120F01AC 0x1; # 0: GPIO11_6 1: I2S2_BCLK_RX himm 0x120F01B0 0x1; # 0: GPIO11_7 1: I2S2_WS_RX himm 0x120F01B4 0x1; # 0: GPIO12_0 1: I2S2_SD_RX himm 0x120F01B8 0x1; # 0: GPIO12_3 1: I2S2_SD_TX 2:SLAVE_MODE himm 0x120F01BC 0x1; # 0: GPIO12_4 1: I2S2_MCLK 2:BOOT_SEL1 himm 0x120F0198 0x1; # 0: GPIO12_5 1: I2S3_BCLK_TX himm 0x120F019C 0x1; # 0: GPIO12_6 1: I2S3_WS_TX himm 0x120F01A0 0x1; # 0: GPIO12_7 1: I2S3_SD_TX 2:PCIE_REFCLK_SEL echo "run $0 end!!!";
经过这个配置文件,咱们能够看到是用himm来配置引脚属性的,这是海思的一款工具。并且能够看到SPI有两个管脚的功能被复用成了I2C了,因此咱们须要从新配置管脚的功能。那怎么修改呢?本文章也是会使用himm工具来配置,接下来说解下himm工具的使用。
海思提供的himm工具,能在linux命令行中,直接对gpio进行操做。此工具能够在已经编译好的SDK中 osdrv/tools/board/reg-tools-1.0.0/bin 这个目录下能够看到。
咱们看一看himm工具的本质,ls -al
-rwxr-xr-x 1 root root 45540 Nov 20 23:57 btools lrwxrwxrwx 1 root root 6 Nov 20 23:57 himm -> btools
能够看到himm工具其实就是btools可执行文件,所以若是板子上没有这个工具,则咱们只须要将btools放到板子上,而且创建连接,作好以后就可使用了。
himm执行的格式:
himm 寄存器地址 须要设置的值
那如今咱们就用himm来配置SPI管脚,经过第二章,咱们都知道SPI相关的寄存器地址和相关的功能了。如今我将管脚配置成SPI功能、使用片选0管脚而且低电平有效,对应寄存器的地址和值以下:
寄存器 | 寄存器地址 | value |
---|---|---|
SPI_SCLK管脚复用寄存器 | 0x120F0148 | 0x1 |
SPI_SDO管脚复用寄存器 | 0x120F014C | 0x1 |
SPI_SDI管脚复用寄存器 | 0x120F0150 | 0x1 |
片选CS外设功能选择寄存器2 | 0x12120014 | 0x0 |
使用的配置指令以下:
himm 0x120F0148 0x1; # 0: GPI13_0 01: SPI_SCLK 2:I2C1_SCL himm 0x120F014C 0x1; # 0: TEST_CLK 01: SPI_SDO 2:I2C1_SDA himm 0x120F0150 0x1; # 0: TEST_CLK 01: SPI_SDO 2:I2C1_SDA himm 0x12120014 0x0;
以上的指令能够直接在板子上终端输入,可是这是一次性的设置,重启以后配置就不在了,以下图:
为了每次启动都有效,咱们能够把以上的配置指令添加到上面说起到的mpp/ko/pinmux_vicap_i2c_i2s.sh脚本里面,而后编译根文件系统到板子上便可,也能够直接在板子上修改这个脚本:/nand/ko/pinmux_vicap_i2c_i2s.sh,以下图:
咱们配好了SPI管脚,那么在上层应用app怎么使用这个SPI功能呢?既然这个芯片有这个SPI的硬件功能,那么通常来讲都会有直接使用它的软件接口。该SPI的使用指南就在 01.software\board\document_cn\外围设备驱动 操做指南.pdf 这个文档里,咱们跳到 5.SPI操做指南 的章节阅读,你就知道怎么操做啦。
看到 5.3.1 SPI读写命令示例 这里的时候,咱们能够看到上面说的四个片选管脚并非一个SPI控制器来控制的,Hi3531A的SPI控制器0有1个片选、控制器1有3个片选,咱们在到开发板看一看 ls /dev/ 是否是有两个SPI的驱动,一个是spidev0.0,另一个是spidev0.1。也就是说,若是你使用spidev0.0的驱动节点,那么就只能使用片选0;若是是使用spidev0.1,则能够选择片选1-3。因此在配置片选的时候,要注意这一点。
下面示例是用户态的SPI读写程序(也能够参考 外围设备驱动 操做指南.pdf 文档的示例),隔500ms发送一次1-9的数据:
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdint.h> #include <stdlib.h> #include <signal.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/types.h> #include <linux/spi/spidev.h> unsigned char isExit = 0; unsigned char mode = 0; unsigned char bits = 8; unsigned int speed = 9*1000*1000; unsigned short delay = 0; unsigned char cs_change = 1; static unsigned int m_spi_write(int fd, int len, unsigned char* sbuf, unsigned char* rbuf); void interrupt(int sig) { isExit = 1; } static unsigned int m_spi_write(int fd, int len, unsigned char* sbuf, unsigned char* rbuf) { int ret; struct spi_ioc_transfer tr; tr.tx_buf = (unsigned long)sbuf; tr.rx_buf = (unsigned long)rbuf; tr.len = len; tr.delay_usecs = delay; tr.speed_hz = speed; tr.bits_per_word = bits; tr.cs_change = cs_change; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if(ret < 1){ printf("spi_write error\n"); return -1; } return tr.len; } int spi_init(unsigned char *arg) { int fd = 0; int ret = 0; unsigned int reg_value = 0; fd = open(arg, O_RDWR); if (fd<0) { printf("Open /dev/spidev0.0 dev error!\n"); return -1; } printf("Open /dev/spidev0.0 dev success!\n"); ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); if(ret == -1){ printf("set spi WR mode error\n"); return -1; } ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); if(ret == -1){ printf("set spi RD mode error\n"); return -1; } ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); if(ret == -1){ printf("set spi write bits per word error\n"); return -1; } ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); if(ret == -1){ printf("set spi read bits per word error\n"); return -1; } ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); if(ret == -1){ printf("set spi write Max speed error\n"); return -1; } ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); if(ret == -1){ printf("set spi write Max speed error\n"); return -1; } printf("int spi success, mode:%d, bits:%d, speed:%d MHz\n", mode, bits, speed/1000000); return fd; } int main(int argc , char* argv[]) { int i = 0, fd = -1; unsigned int rsize = 0;; unsigned char sbuf[1024] = {1,2,3,4,5,6,7,8,9}; unsigned char rbuf[1024] = {0}; if(argc != 2){ printf("pls input ./hisi2mcu /dev/spidev0.0 or /dev/spidev0.1"); return -1; } signal(SIGINT, interrupt); signal(SIGTERM, interrupt); fd = spi_init((unsigned char *)argv[1]); if(fd < 0){ printf("spi_init error\n"); return -1; } while(1) { if(isExit == 1) break; memset(rbuf, 0, sizeof(rbuf)); rsize = m_spi_write(fd, strlen(sbuf), sbuf, rbuf); printf("hisi recv szie = %d\n", rsize); if(rsize == 0) continue; for(i=0; i<rsize; ++i) { printf("0x%02x ", rbuf[i]); if((i+1)%9 == 0) printf("\n"); } printf("\n"); usleep(500*1000); } close(fd); return 0; }
使用示波器来采集SPI管脚的输出,以下图:
由于没有从设备发送数据,因此输入引脚是空的。