zoukankan      html  css  js  c++  java
  • 【海思】Hi3531A SPI功能的详细配置以及使用

    作者:李春港
    出处:https://www.cnblogs.com/lcgbk/p/14044478.html

    一、前言

    因为部门的一个负责海思驱动开发的老同事另谋高就了,部门又暂时找不到人来对接他的任务,所以领导就让我这个菜鸟来硬着头皮顶上了。在这我也对这位老同事表示深刻的感谢,在对接的期间,那么耐心教导我,让我这个刚出来社会不久的菜鸟学习到了很多东西。

    回归正题:部门在海思3531A产品调试的时候发现SPI通信不正常,使用示波器发现SPI的CS片选引脚怎么也拉不低,SCLK(时钟)、SDI(数据输入引脚)和SDO(数据输出引脚)的信号也不正常。在我不停地阅读官方资料和询问大神们,最终调好了。

    所以接下来,本文章会针对 海思SPI配置的问题 来记录下我调试的整一个过程,包括官方资料的阅读。

    二、SPI管脚信息获取

    在一个新的平台上面开发,我们首先需要阅读官方的资料,并获取到自己需要的资料。这里主要是针对与 SPI配置 相关的资料查找。

    2.1 SPI_SCLK、SPI_SDI、SPI_SDO管脚复用寄存器

    因为涉及到SPI的使用(如果不熟悉SPI通信协议的,可以先去搜索了解下先),那就会涉及到管脚的使用,所以我们先找到芯片的管脚信息表,这个管脚信息表一般芯片平台的官方资料里面都会有,而且一般都会放在硬件的目录下。海思平台的管脚信息表的路径是 00.hardwarechipdocument_cnHI3531AV100_PINOUT_CN.xlsx ,打开可以看到这个表格有很多页,这些页的内容包括管脚的功能/复用功能、默认值、特性以及管脚复用寄存器等信息。

    我们在HI3531AV100_PINOUT_CN.xlsx这个表格的《1.管脚信息表》页找到SPI管脚。

    • 《1.管脚信息表》页列说明:
    Pin Num Pin Name Voltage(V) Default IO Type Mux Control Register Name Drive&Slew Control Register Name Function n
    管脚号 管脚名称 管脚输入或输出的高/低电平 默认的管脚是输入/输出高还是低等。 IO口的类型 复用寄存器名称,有的话就代表该管脚有复用功能。如果不是硬件复用可在《3.管脚复用寄存器》页查找该名称,可以看到该管脚的复用寄存器地址以及功能描述;如果是硬件复用,则可以在《5.硬件复用关系》页查找到。 这是管脚的驱动能力寄存器,可在《4.管脚驱动能力寄存器》页查看。 这是对应管脚的功能描述。
    • 我们可以看到寄存器部分 HI3531AV100_PINOUT_CN.xlsx 表格就只是说了管脚复用的一些寄存器,那么其他管脚的寄存器呢?一般其他的寄存器文档也是会在这个目录下的,海思平台的这个文档是在 00.hardwarechipdocument_cnHi3531A H.264编解码处理器用户指南.pdf

    通过上面管脚信息表,我们发现和SPi相关的有7个管脚,而且都是复用管脚,其中四个是片选管脚,其他则是SPi的时钟(SPI_SCLK)、数据输入(SPI_SDI)、数据输出(SPI_SDO)管脚。我们需要使用SPI的功能,那么就需要正确配置每个引脚。

    接下来我们先根据上面的复用管脚的复用寄存器名称,到《3.管脚复用寄存器》页或者《5.硬件复用关系》页里面找:

    1. 片选管脚SPI_CSN0-SPI_CSN3:

    片选管脚是硬件复用的,这一般不需要软件来进行配置,需要硬件工程师来配合。通过个人的调试经验来说,需要用到的的片选管脚硬件上不要接上拉电阻或者下拉电阻,否则海思芯片有可能会无法控制该片选管脚。

    1. SPI_SCLK、SPI_SDI、SPI_SDO管脚复用寄存器:

    通过上图得知,SPI_SCLK、SPI_SDI、SPI_SDO管脚的默认值都不是和SPI通信相关的,所以我们需要配置这些管脚为SPI功能,也就是要往寄存器写0x01的值。

    2.2 片选SPI_CSN0-SPI_CSN3管脚寄存器

    在HI3531AV100_PINOUT_CN.xlsx 表格中,SPI_SCLK、SPI_SDI、SPI_SDO管脚的寄存器的地址和值都找到了,但是片选管脚的寄存器呢?不在这个表格,那就是在上面提到的 00.hardwarechipdocument_cnHi3531A H.264编解码处理器用户指南.pdf 文档中了。打开文档,我们发现该片选的寄存器是在 3.系统/3.5.系统控制器/3.5.5.外设控制寄存器 的目录中,是外设功能选择寄存器2(MISC_CTRL5)控制的片选:

    • 外设控制寄存器概览:

    • MISC_CTRL5寄存器:

    这样的话我么就知道片选引脚的寄存器地址是 0x1212 0014、对应的值也可以通过上图MISC_CTRL5寄存器得知了。

    三、配置和使能与SPI相关的管脚

    到了这里,SPI所有引脚的寄存器地址和对应的值都知道了,那这些应该怎么配置呢?

    先不要着急,我们先来打开 01.softwareoarddocument_cnHi3531A SDK 安装以及升级使用说明.txt 这个文档(当我们第一次拿到官方SDK的时候就应该看这个来进入着手,里面内容写得不是很详细,但是覆盖面挺大的。01.softwareoarddocument_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工具的使用。

    3.1 海思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,如下图:

    四、用户态APP使用SPI

    我们配好了SPI管脚,那么在上层应用app怎么使用这个SPI功能呢?既然这个芯片有这个SPI的硬件功能,那么一般来说都会有直接使用它的软件接口。该SPI的使用指南就在 01.softwareoarddocument_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。所以在配置片选的时候,要注意这一点。

    4.1 示例

    下面示例是用户态的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
    ");
    		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!
    ");
            return -1;
        }
    
        printf("Open /dev/spidev0.0 dev success!
    ");
    
        ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
        if(ret == -1){
    		printf("set spi WR mode error
    ");
    		return -1;	
        }
    
        ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
        if(ret == -1){
            printf("set spi RD mode error
    ");
            return -1;
        }   
    
        ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
        if(ret == -1){
    		printf("set spi write bits per word error
    ");
    		return -1;
        }
    
        ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
        if(ret == -1){
    		printf("set spi read bits per word error
    ");
    		return -1;
        }
    
        ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
        if(ret == -1){
    		printf("set spi write Max speed error
    ");
    		return -1;
        }
        
        ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
        if(ret == -1){
    		printf("set spi write Max speed error
    ");
    		return -1;
        }
    
        printf("int spi success, mode:%d, bits:%d, speed:%d MHz
    ", 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
    ");
    		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
    ", rsize);
    
    		if(rsize == 0)
    			continue;
    
    		for(i=0; i<rsize; ++i)
    		{
    			printf("0x%02x ", rbuf[i]);
    
    			if((i+1)%9 == 0)
    				printf("
    ");
    		}
    
    		printf("
    ");
    		usleep(500*1000);
        }
        
        close(fd);
        return 0;
    }
    

    4.2 效果

    使用示波器来采集SPI管脚的输出,如下图:

    因为没有从设备发送数据,所以输入引脚是空的。

  • 相关阅读:
    磁盘拓展
    echarts遇到resize失效问题
    技术的技术书籍
    beego的数据库操作优化
    Laravel学习笔记(六)数据库 数据库填充
    Laravel学习笔记(五)数据库 数据库迁移案例2——创建数据结构,数据表,修改数据结构
    Laravel学习笔记(四)数据库 数据库迁移案例
    Laravel学习笔记(二)Laravel 应用程序的体系结构
    Laravel学习笔记(三)数据库 数据库迁移
    Laravel安装配置开发环境
  • 原文地址:https://www.cnblogs.com/lcgbk/p/14044478.html
Copyright © 2011-2022 走看看