由於部門的一個負責海思驅動開發的老同事另謀高就了,部門又暫時找不到人來對接他的任務,因此領導就讓我這個菜鳥來硬着頭皮頂上了。在這我也對這位老同事表示深入的感謝,在對接的期間,那麼耐心教導我,讓我這個剛出來社會不久、又沒怎麼接觸過海思平臺的菜鳥學習到了不少東西。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管腳的輸出,以下圖:
由於沒有從設備發送數據,因此輸入引腳是空的。