【海思】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.hardware\chip\document_cn\HI3531AV100_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.hardware\chip\document_cn\Hi3531A 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.hardware\chip\document_cn\Hi3531A H.264編解碼處理器用戶指南.pdf 文檔中了。打開文檔,我們發現該片選的寄存器是在 3.系統/3.5.系統控制器/3.5.5.外設控制寄存器 的目錄中,是外設功能選擇寄存器2(MISC_CTRL5)控制的片選:

  • 外設控制寄存器概覽:

  • MISC_CTRL5寄存器:

這樣的話我么就知道片選引腳的寄存器地址是 0x1212 0014、對應的值也可以通過上圖MISC_CTRL5寄存器得知了。

三、配置和使能與SPI相關的管腳

到了這里,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工具的使用。

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.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。所以在配置片選的時候,要注意這一點。

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\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;
}

4.2 效果

使用示波器來采集SPI管腳的輸出,如下圖:

因為沒有從設備發送數據,所以輸入引腳是空的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM