使用spi-gpio-custom模块配置SPI总线

使用spi-gpio-custom模块配置SPI总线

 

来源 https://www.xuzhe.tj.cn/index.php/2023/10/26/spi-gpio-customspi/

参考专栏 https://www.zhihu.com/column/c_1698084667767709696

 

1. 引言

SPI(Serial Peripheral Interface)是一种常见的串行通信协议,广泛应用于微控制器与外部设备的连接。

Linux内核中的spi-gpio与spi-bitbang模块可使用GPIO引脚进行SPI的位操作,spidev模块可将SPI暴露给用户空间。但是,这些模块并不能“直接”使用:它们被其他内核驱动程序使用。没有办法动态地说“我想在这些引脚上使用一个SPI”。相反,我们需要重新配置、编译内核。

spi-gpio-custom模块允许动态配置SPI总线及其节点,无需重新编译内核。对于测试、概念验证非常方便。此外,spi-gpio-custom模块的速度也相当不错,测试显示它可以达到1 MHz以上。

2. 安装spi-gpio-custom

以openwrt系统为例,安装模块包是最方便的。如果还要修改其它的openwrt构建设置,可在make menuconfig中选上此模块,然后重新编译openwrt。

2.1 离线安装模块包

在线安装最便捷,会自动安装依赖包,但需要网络条件。离线安装需要先下载好所需要的包,之后手动安装。

可使用LuCI网页界面安装,操作简单。简单直观,本文不再介绍。

下面介绍命令行安装方法,可在没有配置LuCI功能,或LuCI页面出错打不开时使用。

下载包:kernel、kmod-spi-bitbang、kmod-spi-dev、kmod-spi-gpio、kmod-spi-gpio-custom。其中只kmod-spi-gpio-custom是需要的spi-gpio-custom包,其它均为依赖包。

包要与操作系统及硬件主控对上。比如包名:

kmod-spi-gpio-custom_3.18.29-1-8876e460a901ba0991338a5b1846e893_ramips_24kec.ipk 

其中,各部分的含义为:

  • kmod-: 内核模块(Kernel Module)。
  • spi-gpio-custom: 内核模块的具体名称。
  • 3.18.29: 这是该内核模块所依赖的 Linux 内核版本。
  • -1: 这是该软件包版本的修订号,通常用于追踪小的更新或修补。
  • 8876e460a901ba0991338a5b1846e893: 这是一个校验和或唯一标识符,用于确保软件包的完整性和唯一性。
  • ramips: 这表示该模块是为基于 Ralink/MediaTek MIPS 架构(ramips)的设备编译的。
  • 24kec: 这是该 MIPS 架构下的一个特定型号或子架构。
  • .ipk: 这是软件包的文件扩展名,表明它是一个用于 OpenWRT 的安装包。

模块包的安装命令opkg install。如安装kmod-spi-gpio-custom包:

opkg install kmod-spi-gpio-custom*.ipk

在命令中,可用*号用替代后续字符。

按照这个顺序依次安装:kernel、kmod-spi-bitbang、kmod-spi-dev、kmod-spi-gpio、kmod-spi-gpio-custom。

检验是否安装成功:

opkg list-installed | grep spi-gpio

看到kmod-spi-gpio-custom,说明已安装好。

安装时,如还遇到缺少依赖包问题,需根据提示,依次安装。

2.2 编译安装

在系统源代码根目录,输入配置指令:

make menuconfig

在kernel model -> spi support,选中”kmod-spi-gpio-custom”,系统会自动选中三个依赖包:“kmod-spi-bitbang”、“kmod-spi-dev”、“kmod-spi-gpio”。

保存退出后,重新编译openwrt:

make j=2 //双线程编译

3 使用spi-gpio-custom模块配置SPI总线

spi-gpio-custom 的便利就在于可动态配置SPI总线,不需“修改dts文件、编译系统”的繁琐操作。也就是说,通过spi-gpio-custom使用spi时,dts文件中可以没有任何spi功能的定义。

常规的一个屏幕接口引脚定义

接口功能
MISO SPI输出
MOSI SPI输入
CLK SPI时钟输入
DC 数据/命令 切换控制,低电平表示输入命令,高电平表示输入数据
RES 复位屏幕
BLK 背光控制
VCC 电源
GND 接地

配置举例

配置一个ID为1的总线,使用GPIO3作为CLK,GPIO4作为MOSI,GPIO5作为MISO,在SPI模式0下工作、最大频率为20KHz、GPIO2作为CS的设备。

命令参数  功能描述
<id> ID to used as device_id for the corresponding bus (required)
<sck> GPIO pin ID to be used for bus SCK (required)
<mosi> GPIO pin ID to be used for bus MOSI (required*)
<miso> GPIO pin ID to be used for bus MISO (required*)
<modeX> Mode configuration for slave X in the bus (required) * (see /include/linux/spi/spi.h)
<maxfreqX> Maximum clock frequency in Hz for slave X in the bus (required)
<csX> GPIO pin ID to be used for slave X CS (required**)

运行命令:

# example spi-gpio
insmod spi-gpio-custom bus0=1,3,4,5,0,20000,2
# acu100 spi-gpio
insmod spi-gpio-custom bus0=10,12,13,16,0,12000000,2

请注意GPIOx中的x,不是引脚编号(pin),而是引脚名称。

如需修改spi总线的配置,需先卸载后再加载:

rmmod spi-gpio-custom 
insmod spi-gpio-custom <new parameters>

更复杂的配置,参考 原始文档: SPI over GPIO in OpenWrt

#include "dev-spi.h"
// ......
/*
#define AP143_GPIO_LED_WLAN 13
#define AP143_GPIO_LED_IOT 16
#define AP143_GPIO_LED_PWR 11 //14
*/
// ......
#define AP143_GPIO_BTN_WPS 4
#define AP143_KEYS_POLL_INTERVAL 20 /* msecs */
#define AP143_KEYS_DEBOUNCE_INTERVAL (3 * AP143_KEYS_POLL_INTERVAL)
#define AP143_MAC0_OFFSET 5 //0
#define AP143_MAC1_OFFSET 6
#define AP143_WMAC_CALDATA_OFFSET 0x1000
// ......
static struct spi_board_info spi_test = {  
	
    .modalias = "spi_test",  
    .mode = SPI_MODE_0,  
    .irq = 0,  
    .max_speed_hz = 12 * 1000 * 1000,  
    .bus_num = 10,  
    .chip_select = 1, //模拟gpio用不到该成员  
    .controller_data = (void*)(0),
		
};  
// ......
// static void __init ap143_setup(void)
spi_register_board_info(&spi_test, 1);

 

spidev:Linux内核中的用户空间SPI接口

来源  https://www.xuzhe.tj.cn/index.php/2023/10/24/spidevlinuxspi/

 

1 简介

SPI(串行外设接口)是一种常用的通信协议,用于高速全双工通信。Linux内核提供了一种名为spidev的用户空间接口,用于访问SPI设备。

spidev是一个位于Linux内核空间的接口程序。其作用是充当用户程序与内核空间SPI核心底层驱动之间的桥梁。通过spidev接口,开发者可以直接在用户空间进行程序开发以调用底层SPI驱动,这大大降低了SPI驱动开发的门槛。通常情况下,设备驱动的开发需要在内核空间中进行,这对大多数开发者来说是一项相对复杂和陌生的任务。然而,由于spidev的存在,开发者无需深入了解或学习内核开发的各种方法与流程,可以直接使用熟悉的开发工具和语言,来轻松地完成SPI驱动的开发。这样,即便是不熟悉内核开发的人员也能更方便地进行SPI通讯开发。

1.1 主要优点

  1. 安全性:因为用户空间与内核空间的隔离,即使代码存在错误,也不会导致整个Linux系统崩溃或不稳定。
  2. 易用性:用户空间提供了更多易用和强大的开发工具和库,比如各种语言的SDK、调试工具等。可以使用C、C++、Python等语言进行开发。
  3. 快速迭代:由于在用户空间中进行开发,代码的编译和测试周期通常更短,这有助于快速迭代和优化。

1.2 应用场景

  1. 原型开发:如果你只是需要进行一些基础的功能测试或者原型设计,spidev是一个很好的选择。
  2. 简单驱动程序的开发:对于那些不需要访问内核高级功能(如中断处理)的项目,使用spidev能快速地完成驱动程序的开发。

1.3 spidev的局限性

虽然spidev提供了一个相对简便的方式来访问SPI设备,它有一些情况下不能完全替代专门的SPI设备驱动。这是因为spidev主要用于在Linux内核与SPI主设备之间进行通信,但当需要访问内核空间的高级接口功能,如中断处理等,spidev就显得力不从心。在这种情况下,开发者应当考虑直接在内核空间开发SPI驱动程序,而不是依赖于spidev在用户空间进行编程。

2 spidev的安装

以openwrt系统为例,介绍启用spidev模块的方法。在openwrt系统中,默认是没有启用spidev模块的,有两种启用方法:一种是在openwrt系统中安装spidev模块包,这种方法较为简单;另一种方法是使用make menuconfig配置openwrt的源代码,然后重新编译,此方法较复杂,涉及到linux源码的配置与编译等内容。

2.1 安装spidev包的方法

这种方法不需要重新编译openwrt系统,非常方便。

1)获取kmod-spi-devipk安装包

http://openwrt.jaru.eu.org/gargoyle-pl/chaos_calmer/ramips/packages/中根据openwrt的版本下载”kmod-spi-dev-… .ipk”的安装包。这个源比官网更全。

我的openwrt版本是 Chaos Calmer 15.05.1。在这里查看其它openwrt版本。http://openwrt.jaru.eu.org/gargoyle-pl/

openwrt的版本以及linux内核版本号,可以在luci界面查看。在浏览器中输入openwrt的IP地址,登陆后就能看到。

2)安装ipk包

将ipk包拷贝到openwrt系统后,执行opkg install命令安装。

opkg install kmod-spi-dev*.ipk

2.2 配置并编译源代码的方法

在openwrt源代码的根目录下,使用make menuconfig命令打开配置窗口,在Kernel modules->SPI Support中,选中kmod-spi-dev。这里的kmod-spi-dev即是需要开启的spidev模块。

保存退出后。在openwrt源代码的根目录下,执行make命令重新编译openwrt系统:

make V=s j=2

其中 V=s j=2 是make编译命令的参数。使用了2个线程编译。如果编译出现问题,需要查看具体的出错情况时,需要把j = 2去掉,即使用make V=s重新编译重新编译,才能看到出错详情。

make 参数Vj的解释:

1). V=s 是一个环境变量设置,通常用于控制构建过程中的输出详细程度。

  • V=0 或者没有设置 V 通常意味着只显示必要的信息。
  • V=1 通常用于显示更详细的构建信息。
  • V=s 在显示详细的调试信息,但这取决于具体的 Makefile 如何处理这个变量。

2). j=2 是用于 make 的一个标准选项,表示并行编译,即同时进行两个任务。j 参数后面的数字 2 指定了可以同时进行的任务数量。这在多核或多线程的CPU上是有用的,因为它可以加速整个构建过程。

在使用上述的方法安装spidev模块或编译源码后,可以使用下面的方法查看spidev包是否已经安装。

opkg list-installed | grep spi

执行后会看到所有已经安装的名称包含spi的包。其中有kmod-spi-dev说明已经安装成功了。

3 在DTS中添加spidev

在安装spidev模块后,还需要在dts(设备树)文件中加入spidev的配置。以mt7628芯片为例,在mt7628.dts文件中的palmbus@10000000属性下的spi@b00中加入:

spidev@1{
    compatible = "linux,spidev";
    reg = <1 0>;
    spi-max-frequency = <10000000>;
};

完整的dts文件见文后。

在openwrt系统的/dev目录中看到spidevX.Y形式的文件名称,说明spidev已经在加载到内核中,用户空间程序就可以通过标准的open/read/write/ioctl接口来访问这个SPI设备了。

4 spidev通讯参数的设定(C/C++)

在完成了安装以及dts文件的修改后,就可以编程了。这里以C/C++开发为例说明。如果你使用python,可以去这里查看python调用spidev接口的方法。https://pypi.org/project/spidev/

在编写具体的通讯代码之前,先要对spi通讯的参数进行设定。ioctl()函数提供了一种强大的方式来与SPI设备进行交互。在本节中,将详细介绍几种重要的ioctl()请求,这些请求允许开发者读取或更改设备当前用于数据传输的参数设置。

4.1 SPI传输模式

使用SPI_IOC_RD_MODESPI_IOC_WR_MODE请求,可以获取或设置SPI传输模式。这些请求需要一个指向字节(byte)的指针,该字节将存储或接收SPI模式。

  • 读取模式(RD):获取当前的SPI模式。
  • 写入模式(WR):设置新的SPI模式。

可以使用预定义的常量,如SPI_MODE_0SPI_MODE_1等,或者直接使用SPI_CPOLSPI_CPHA标志来设置时钟极性和相位。

4.2 位对齐方式

SPI_IOC_RD_LSB_FIRSTSPI_IOC_WR_LSB_FIRST允许获取或设置位对齐方式——MSB-first或LSB-first。

  • MSB-first:最高有效位优先。
  • LSB-first:最低有效位优先。

数值为0,表示MSB-first,也是默认的设定值。当数值为非0时,表示很少使用的LSB-first。

SPI(串行外设接口)通信中,数据传输的位对齐方式是一个重要的参数。位对齐方式决定了在SPI字(通常为8位或16位)传输过程中,是最高有效位(MSB)优先还是最低有效位(LSB)优先。

MSB-first(最高有效位优先):在这种模式下,每个SPI字的最高有效位(MSB)将首先被发送或读取。这是SPI通信中最常见的配置。例如,如果你有一个8位的字节0b11001010,那么最左边的位(即最高有效位,这里是1)将首先被发送。

LSB-first(最低有效位优先):与MSB-first相反,在LSB-first模式下,每个SPI字的最低有效位(LSB)会首先被发送或读取。在某些特定应用或特定类型的SPI设备中,这种配置可能会更有用。例如,如果你有相同的8位字节0b11001010,最右边的位(即最低有效位,这里是0)将首先被发送。

4.3 每字位数

通过SPI_IOC_RD_BITS_PER_WORDSPI_IOC_WR_BITS_PER_WORD,可以读取或设置每个SPI传输字中的位数。通常情况下,默认值为8位。

4.4 最大传输速度

使用SPI_IOC_RD_MAX_SPEED_HZSPI_IOC_WR_MAX_SPEED_HZ,你可以获取或设置SPI设备的最大传输速度。这需要一个指向u32类型的指针,该指针用于存储或接收速度值(以Hz为单位)。

需要注意的是,虽然你可以设置一个理想的传输速度,但硬件和驱动程序可能不一定支持你设置的确切速度。

通过掌握这些ioctl()请求,你将能更灵活地控制SPI设备,从而实现更高效和可定制的数据传输。希望这篇文章能够帮助你更深入地了解如何使用spidev进行SPI编程。

4.5 在C++中使用spidev设置SPI设备:一个实例解析

下面是一段C++示例程序,使用spidev库和ioctl()函数来初始化SPI设备。

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 100000;
const char *device = "/dev/spidev32766.1";

bool SPIDevice::Open_Initialize() {
    fd = open(device, O_RDWR);
    if (fd < 0 || ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1 ||
     //   ioctl(fd, SPI_IOC_RD_MODE, &mode) == -1 ||
        ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1 ||
     //   ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) == -1 ||
        ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1 ||
     //   ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) == -1
     ) {
        return false;
    }
    return true;
}

程序中包含了spidev编程时要引用的头文件。

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

我们的程序示例定义了一个SPIDevice类,其中包含一个Open_Initialize()成员函数。这个函数执行以下几个主要任务:

  1. 打开SPI设备。
  2. 设置SPI传输模式。
  3. 设置SPI字的位数。
  4. 设置SPI的最大传输速度。

4.5.1 打开SPI设备

fd = open(device, O_RDWR);

这里使用Linux的open()系统调用来打开SPI设备。O_RDWR标志表示我们将以读写模式打开设备。打开标志的详细内容可以看这里:[open - open a file] https://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html

4.5.2 设置SPI传输模式

ioctl(fd, SPI_IOC_WR_MODE, &mode)

使用ioctl()函数和SPI_IOC_WR_MODE请求来设置SPI的传输模式。我们选择的是SPI_MODE_0。除此之外,还可使用SPI_MODE_1SPI_MODE_2SPI_MODE_3

4.5.3 设置SPI字的位数

ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits)

这里,使用ioctl()函数和SPI_IOC_WR_BITS_PER_WORD请求来设置每个SPI字的位数,这里设为8位。

4.6 设置SPI最大传输速度

ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)

最后,设置SPI的最大传输速度为100,000 Hz。

5 编写spidev通讯程序(C/C++)

通过read、write实现半双工的通讯,使用ioctl实现全双工通讯。

5.1 半双工spi通讯

下面是一个使用spidev的简单示例。可以实现半双工的读写以及关闭spidev的通道。

// 执行半双工读写
read(fd, rxbuf, len); 
write(fd, txbuf, len);

//
close(fd); //关闭spidev的通讯通道。

上面的代码是一个简单的示例,用于读写数据到一个通用设备(通常是一个SPI设备)通过文件描述符(fd)。

read(fd, rxbuf, len);

read 是一个系统调用,用于从文件(在这个情况下是一个设备)读取数据。

fd是文件描述符,标识了我们要操作的文件或设备。

rxbuf 是一个缓冲区(buffer),用于存储读取到的数据。

len 是读取的字节数。

当这一行代码执行后,rxbuf 将会填充从文件描述符fd 读取到的len 个字节的数据。

write(fd, txbuf, len);

write 是一个系统调用,用于向文件(在这个情况下是一个设备)写入数据。

txbuf 是一个缓冲区,包含我们想要写入的数据。

len 是写入的字节数。

当这一行代码执行后,txbuf 里面的数据(len 字节数)将会被写入到文件描述符 fd 指向的文件或设备。

close(fd);

这个调用用于关闭之前通过文件描述符 fd 打开的文件或设备,释放系统资源。

注意:这里是半双工操作,意味着读和写是分开进行的。这与全双工(同时进行读和写)不同。这里先进行读操作,然后进行写操作。

简单来说,这段代码从一个设备读取数据,然后写入数据,最后关闭与该设备的连接。

5.2 实现全双工spi通讯

这是一个函数,使用ioctl()函数和SPI_IOC_MESSAGE请求实现全双工的通讯。

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

// 执行全双工读写的函数,可被调用以进行SPI通讯。
bool SPIDevice::transfer(const uint8_t *tx, uint8_t *rx, size_t len) {
    struct spi_ioc_transfer tr;
    memset(&tr, 0, sizeof(tr));
    tr.tx_buf = (unsigned long)tx;
    tr.rx_buf = (unsigned long)rx;
    tr.len = len;
    tr.delay_usecs = 0;
    tr.speed_hz = speed;
    tr.bits_per_word = bits;
    bool a = (ioctl(fd, SPI_IOC_MESSAGE(1), &tr)==-1);
    if (a)
    {
        std::cout<<"transfer error"<<std::endl; //如果ioctl()调用返回-1,则输出一个错误消息。
    }
    return !a;
}

其中用到了SPI_IOC_MESSAGE,这是一个非常重要的ioctl()请求,用于执行实际的SPI数据传输。该命令允许应用程序在单个调用中执行一个或多个SPI消息的完整传输。

参数结构:spi_ioc_transfer

该命令通常与一个或多个spi_ioc_transfer结构体一同使用。每个spi_ioc_transfer结构体都描述了一个独立的SPI传输消息,包括以下几个重要的字段:

tx_buf:指向待发送数据的缓冲区。

rx_buf:指向用于接收数据的缓冲区。

len:传输的字节数。

speed_hz:传输速率。

bits_per_word:每个数据字的位数。

delay_usecs:传输完成后的延迟时间,单位为微秒。

在使用ioctl()进行SPI传输时,通常会像下面这样调用:

struct spi_ioc_transfer tr;
// 初始化 tr 结构体...
int ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

其中,fd是已打开的SPI设备的文件描述符,SPI_IOC_MESSAGE(1)指示我们将进行一个SPI消息的传输,&tr是指向已初始化的spi_ioc_transfer结构体的指针。

执行多个SPI传输

SPI_IOC_MESSAGE也支持一次执行多个SPI传输。例如,如果你有一个spi_ioc_transfer数组,你可以这样做:

struct spi_ioc_transfer trs[2];
// 初始化 trs 数组...
int ret = ioctl(fd, SPI_IOC_MESSAGE(2), trs);

这样,trs数组中的两个spi_ioc_transfer结构体会被依次用于SPI数据传输。

5.3 小结

上文主要讲述了如何在C/C++中使用spidev库实现SPI(Serial Peripheral Interface)通信。文章首先介绍了如何进行半双工通信,然后详细解释了如何使用ioctl()函数实现全双工通信。

(1)半双工SPI通信

  • 使用read()write()函数进行数据的读取和写入。
  • 最后通过close(fd)来关闭SPI的通信通道。

(2)全双工SPI通信

  • 使用ioctl()和特定的请求SPI_IOC_MESSAGE来进行全双工通信。
  • 具体通过一个名为SPIDevice::transfer的函数来实现。
  • 使用了spi_ioc_transfer结构体来描述SPI传输的各个参数,如待发送和接收的数据缓冲区、传输字节数、速度等。

(3)多消息SPI传输

  • SPI_IOC_MESSAGE也支持一次执行多个SPI传输,通过构建一个spi_ioc_transfer结构体数组来实现。

文章还深入介绍了spi_ioc_transfer结构体的各个字段和它们的意义,便于理解并实现基于spidev的SPI通信。

6 总结

在这篇文章中,我们深入探讨了spidev——Linux内核中的用户空间SPI接口。我们了解到,spidev作为一个桥梁,连接了用户程序与内核空间SPI核心底层驱动,使得开发者可以直接在用户空间进行程序开发以调用底层SPI驱动,大大降低了SPI驱动开发的门槛。

同时,我们也了解到,虽然spidev提供了一个相对简便的方式来访问SPI设备,但在需要访问内核空间的高级接口功能时,如中断处理等,spidev就显得力不从心。在这种情况下,开发者应当考虑直接在内核空间开发SPI驱动程序。

此外,我们还介绍了如何在openwrt系统中启用spidev模块,包括配置并编译源代码的方法,以及安装spidev包的方法。我们还详细介绍了如何使用ioctl()函数来与SPI设备进行交互,以及如何设置SPI通讯的参数。

最后,通过C/C++示例程序,展示了如何使用spidev库和ioctl()函数来初始化SPI设备并进行通讯。

总的来说,spidev是一个强大而灵活的工具,它为开发者提供了一个简单而直接的方式来访问和控制SPI设备。无论你是一个经验丰富的内核开发者,还是一个刚刚开始接触SPI编程的新手,相信你都能从这篇文章中找到有用的信息和启发。

参考资料

[1] https://www.kernel.org/doc/Documentation/spi/spidev

[2] https://blog.csdn.net/chinazhangzhong123/article/details/54707387

[3] https://juejin.cn/post/7153440681615163423

[4] https://blog.csdn.net/qq_37037348/article/details/130564284

[5] https://blog.csdn.net/yangguoyu8023/article/details/122636162

[6] https://blog.csdn.net/u011006622/article/details/124102680

[7] https://www.cnblogs.com/panda-w/p/11137887.html

[8] https://wiki.t-firefly.com/AIO-3399ProC/driver_spi.html

[9] https://www.eet-china.com/mp/a209080.html

[10] http://www.linkedkeeper.com/mobile/1410.html

[11] https://wiki.t-firefly.com/zh_CN/AIO-3128C/driver_spi.html

[12] https://blog.csdn.net/jia_weihui/article/details/129239470

[13] https://jb51.net/article/186517.htm

[14] https://doc.embedfire.com/linux/imx6/quick_start/zh/latest/quick_start/spi_bus/spi_bus.html

[15] https://elecfans.com/article/2052674.html

[16] https://e2echina.ti.com/support/microcontrollers/msp430/f/msp-low-power-microcontroller-forum/206161/msp430fr2433-spi

[17] http://xilinx.eetrend.com/d6-xilinx/blog/2017-02/10922.html

[18] https://wiki.phytec.com/plugins/servlet/mobile?contentId=158152813

[19] https://community.infineon.com/t5/USB-EZ-PD-Type-C/在-SPI-MOSI-和-MISO-上发送和接收字节的示例代码/td-p/623509

[20] https://wx.comake.online/doc/doc/SigmaStarDocs-Pudding-0120/customer/development/reference/spi dev.html

[21] https://hackmd.io/@amberchung/linux-spidev

[22] https://github.com/apache/dubbo-website/issues/575

[23] https://wiki.sipeed.com/soft/maixpy3/zh/usage/hardware/SPI.html

附录

mt7628.dts 文件

/dts-v1/;

/include/ "mt7628an.dtsi"

/ {
    compatible = "mediatek,mt7628an-eval-board", "mediatek,mt7628an-soc";
    model = "Mediatek MT7628AN evaluation board";

        chosen {
                bootargs = "console=ttyS0,115200";
        };

    memory@0 {
        device_type = "memory";
        reg = <0x0 0x10000000>; //256MB RAM
    //  reg = <0x0 0x8000000>; //128MB RAM
    //  reg = <0x0 0x4000000>; //64MB RAM
    };

    pinctrl {
        state_default: pinctrl0 {
            gpio {
        //      ralink,group = "i2c";
        //      ralink,function = "gpio";
            };
            p0_led {
                ralink,group = "p0_led_an";
                ralink,function = "ephy";
            };
            p1_led {
                ralink,group = "p1_led_an";
                ralink,function = "ephy";
            };
            p2_led {
                ralink,group = "p2_led_an";
                ralink,function = "ephy";
            };
            p3_led {
                ralink,group = "p3_led_an";
                ralink,function = "gpio";
            };
            p4_led {
                ralink,group = "p4_led_an";
                ralink,function = "gpio";
            };
            spis {
                ralink,group = "spis";
                ralink,function = "spis";
            };
                        pwm1 {
                                ralink,group = "pwm1";
                                ralink,function = "gpio";
                        };
                        pwm0 {
                                ralink,group = "pwm0";
                                ralink,function = "gpio";
                        };

            refclk {
                ralink,group = "refclk";
                ralink,function = "gpio";
            };
            gpio_0 {
                ralink,group = "gpio";
//              ralink,function = "refclk_fc";
                ralink,function = "gpio";
            };
                        i2s {
                                ralink,group = "i2s";
                                ralink,function = "i2s";
                        };

        };

            uart2_pins: uart2 {
                uart2 {
                        ralink,group = "uart2";
                            ralink,function = "gpio";
                         };
        };

    };

    palmbus@10000000 {
                i2c@900 {
                        status = "okay";
                };

        spi@b00 {
            status = "okay";

            m25p80@0 {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "en25q64";
                reg = <0 0>;
                linux,modalias = "w25q64", "w25q128", "w25q256";
                spi-max-frequency = <10000000>;
                m25p,chunked-io = <32>;

                partition@0 {
                    label = "u-boot";
                    reg = <0x0 0x30000>;
                    read-only;
                };

                partition@30000 {
                    label = "u-boot-env";
                    reg = <0x30000 0x10000>;
                    read-only;
                };

                factory: partition@40000 {
                    label = "factory";
                    reg = <0x40000 0x10000>;
                    read-only;
                };

                partition@50000 {
                    label = "firmware";
                //  reg = <0x50000 0x7b0000>; //8MB flash
                //  reg = <0x50000 0xfb0000>; //16MB flash
                    reg = <0x50000 0x1fb0000>; //32MB flash
                };
            };
            spidev@1{
                compatible = "spidev", "rohm,dh2228fv";
                reg = <1 0>;
                spi-max-frequency = <10000000>;
            };
        };
                uart1@d00 {
                        status = "okay";
                };

                uart2@e00 {
                        status = "okay";
                };

    };
        sdhci@10130000 {
                status = "okay";
                mediatek,cd-low;
        };

        ethernet@10100000 {
                mtd-mac-address = <&factory 0x28>;
        };

        gpio-leds {
                compatible = "gpio-leds";

        system {
                        label = "mediatek:green:system";
                        gpios = <&gpio1 5 1>;
                        default-state = "off";
                };

                wifi {
                        label = "mediatek:green:wifi";
                        gpios = <&gpio1 12 1>;
                        default-state = "off";
                };
        };

        gpio-keys-polled {
                compatible = "gpio-keys-polled";
                #address-cells = <1>;
                #size-cells = <0>;
                poll-interval = <20>;

                button_1 {
                        label = "button_1";
                        gpios = <&gpio1 6 0>;
                        linux,code = <0x101>;
                };
                button_2{
                        label = "button_2";
                        gpios = <&gpio1 8 0>;
                        linux,code = <0x102>;
                };
        };  
};

 

=========

MT7628dan 增加SPI接口

来源  https://blog.csdn.net/likang517/article/details/80864918

 

SPI是可以全双工通信的一种串行总线,两个设备之间双向通信的话一般使用3根线:SCLK,MISO,MOSI,多个设备之间双向通信的话,每个设备还需要再加上一根地址线CSn。相比之下I2C只能半双工,而且一般需要上拉电阻,但无论几个设备,都只需要2根线。更多基础知识请谷歌百度。

MT7628DAN芯片只有一个主控制器,但是有两个片选信号,可以接2个设备,其中一个已经被SPI FLASH占用,所以需要启用另外一个设备。

1、修改内核配置文件

make menuconfig

配置完成后退出保存。

1、修改文件mt7628an.dtsi

spi0: spi@b00 {
compatible = "ralink,mt7621-spi";
reg = <0xb00 0x100>;
resets = <&rstctrl 18>;
reset-names = "spi";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi_pins>,<&spi_cs1_pins>;
status = "disabled";

};

spi@b00代表一个spi控制器,是一个platform device,compatible = "ralink,mt7621-spi";和 platform driver 中的of_match_table 对应(如果要支持片选1,还得修改num_cs=2,前提是mt7628的spi控制器本来就支持两个片选).相同就会进入到probe函数中,再调用spi_register_master()注册一个spi主控制器.<&spi_cs1_pins>是新增片选引脚,文件mt7628an.dtsi中有定义:

spi_pins: spi {
spi {
ralink,group = "spi";
ralink,function = "spi";
};
};


spi_cs1_pins: spi_cs1 {
spi_cs1 {
ralink,group = "spi cs1";
ralink,function = "spi cs1";
};
};

3、修改MT7628.dts 文件

/dts-v1/;

#include "mt7628an.dtsi"


/ {
compatible = "mediatek,mt7628an-eval-board", "mediatek,mt7628an-soc";
model = "Mediatek MT7628AN evaluation board";


memory@0 {
device_type = "memory";
reg = <0x0 0x2000000>;
};
};


&pinctrl {
state_default: pinctrl0 {
gpio {
ralink,group = "i2c";
ralink,function = "gpio";
};
};
};


&wmac {
status = "okay";
};


&spi0 {
status = "okay";


m25p80@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <10000000>;
m25p,chunked-io = <32>;


partition@0 {
label = "u-boot";
reg = <0x0 0x30000>;
read-only;
};


partition@30000 {
label = "u-boot-env";
reg = <0x30000 0x10000>;
read-only;
};


factory: partition@40000 {
label = "factory";
reg = <0x40000 0x10000>;
read-only;
};


partition@50000 {
label = "firmware";
reg = <0x50000 0x7b0000>;
};
};
spidev@1 {
compatible = "rohm,dh2228fv";
reg = <1 0>;
spi-max-frequency = <1000000>;
};
};


&wmac {
status = "okay";

};

 

spi_register_master注册spi主控制器时就会扫描这些设备,并注册这些设备。 status = “okay”表示选中,否则不能编译进内核. m25p80@0表示在spi片选0下挂了一个m25p80的设备,reg=<0,0>表示片选0,compatible = "jedec,spi-nor"; 与驱动文件匹配.如果要在spi控制器的片选1上挂一个设备,就要修改dts文件,修改如下:

spidev@1 {
compatible = "rohm,dh2228fv";
reg = <1 0>;
spi-max-frequency = <1000000>;

};

compatible = "rohm,dh2228fv";与 spidev.c文件中compatible 一致。

 

4、保存修改文件,执行make 生产升级固件

lede-snapshot-r7346-7b74b40-ramips-mt76x8-mt7628-squashfs-sysupgrade.bin

5、烧写固件查看设备文件:

6、好不好用还没有测试。

7、编译spi-test进行spi测试

8、执行

root@OpenWrt:/# spidev_test -D /dev/spidev0.1

出现如下一连串错误


spi mode: 0x0[ 196.130000] ------------[ cut here ]------------

bits per word:[ 196.130000] WARNING: CPU: 0 PID: 161 at drivers/spi/spi-mt7621.c:137 mt7621_spi_transfer_one_message+0x158/0x360()

8

max speed: 5[ 196.140000] Modules linked in:00000 Hz (500 KH qcserialz)

pppoe ppp_async option iptable_nat usb_wwan sierra pppox ppp_generic nf_nat_ipv4 nf_conntrack_ipv6 nf_conntrack_ipv4 ipt_REJECT ipt_MASQUERADE xt_time xt_tcpudp xt_state xt_nat xt_multiport xt_mark xt_mac xt_limit xt_id xt_conntrack xt_comment xt_TCPMSS xt_REDIRECT xt_LOG xt_CT usbserial spidev slhc nf_reject_ipv4 nf_nat_masquerade_ipv4 nf_nat_ftp nf_nat nf_log_ipv4 nf_defrag_ipv6 nf_defrag_ipv4 nf_conntrack_rtcache nf_conntrack_ftp nf_conntrack iptable_raw iptable_mangle iptable_filter ip_tables crc_ccitt i2c_gpio i2c_algo_bit i2c_dev i2c_core mt76x8 ralink_eeprom_api ledtrig_usbdev ip6t_REJECT nf_reject_ipv6 nf_log_ipv6 nf_log_common ip6table_raw ip6table_mangle ip6table_filter ip6_tables x_tables ipv6 mmc_block mmc_core leds_gpio ohci_platform ohci_hcd ehci_platform ehci_hcd gpio_button_hotplug usbcore nls_base usb_common

[ 196.220000] CPU: 0 PID: 161 Comm: spi32766 Tainted: G W 3.18.20 #8
[ 196.230000] Stack : 00000000 00000000 00000000 00000000 803441f2 00000042 00000000 801ae398

00000001 8f8c7b88 802a53d0 802fc9c3 000000a1 8034341c 8f900bf8 8f8c7b88
00010000 80309578 00000000 80047400 00000003 80024170 00000089 8f8c7b88
802a88d4 8f979d74 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
...

[ 196.270000] Call Trace:
[ 196.270000] [<800140b4>] show_stack+0x48/0x70
[ 196.270000] [<800242ec>] warn_slowpath_common+0x84/0xb4
[ 196.280000] [<800243a4>] warn_slowpath_null+0x18/0x24
[ 196.290000] [<801ae398>] mt7621_spi_transfer_one_message+0x158/0x360
[ 196.290000] [<801ad4e8>] spi_pump_messages+0x3cc/0x438
[ 196.300000] [<80039b9c>] kthread_worker_fn+0xa8/0xf4
[ 196.300000] [<80039cc0>] kthread+0xd8/0xe4
[ 196.310000] [<80004878>] ret_from_kernel_thread+0x14/0x1c
[ 196.310000]
[ 196.310000] ---[ end trace 828b306131dd7246 ]---
can't send spi message: Input/output error
Aborted

查了好几天问题,没明白找到了一个帖子

http://dev.archive.openwrt.org/ticket/20521?action=new&attachfilebutton=Attach+file#no1

原因是

The spidev_test is transmitting a 38 bytes array, that is too large. The spi-mt7621.c will reject when Tx length > 16 (full duplex).

然后修改mt7621.c文件,把全双工注释掉可正常运行spi-test。

 

============

QCA9531修改寄存器值控制GPIO

来源  https://blog.csdn.net/cocos_yang/article/details/109249418

由高通9531芯片规格书可知,芯片对应的GPIO有18个GPIO0-17,下图是规格书定义。下面以SKYLAB的SKW99模块为例进行说明。

SKYLAB的SKW99模块使用源码为QSDK,GPIO0-3默认为JTAG功能,GPIO9和10为uart串口,剩下的GPIO11-16为默认为灯的功能,GPIO17为WPS按键功能。

GPIO11-17对应功能源码地址为:

qsdk/target/linux/ar71xx/files/arch/mips/ath79/mach-ap147.c

#define AP147_GPIO_LED_WAN	4
#define AP147_GPIO_LED_LAN1	16
#define AP147_GPIO_LED_LAN2	15
#define AP147_GPIO_LED_LAN3	14
#define AP147_GPIO_LED_LAN4	11
#define AP147_GPIO_LED_STATUS	13
#define AP147_GPIO_LED_WLAN_2G	12

#define AP147_GPIO_BTN_WPS	17

#define AP147_KEYS_POLL_INTERVAL	20	/* msecs */
#define AP147_KEYS_DEBOUNCE_INTERVAL	(3 * AP147_KEYS_POLL_INTERVAL)

#define AP147_MAC0_OFFSET	0x1000

下面以在固件中控制GPIO0进行讲述说明,固件中需要有io工具:

1、将GPIO1转为GPIO功能:

下面是芯片规格书中关于GPIO0-3功能的定义:对应寄存器的地址为0x1804006C

读取寄存器0x1804006C寄存器的值:

root@OpenWrt:~# io -4 0x1804006C
1804006c: 00000020
root@OpenWrt:~#
高通规格书,没有详细介绍bit1值对应的功能,根据实践:

默认0,对应jtag(默认),1对应的是GPIO功能。

将bit1设置为1,则对应寄存器的值应设置为0x00000022

root@OpenWrt:/# io -4 0x1804006c 0x00000022 //写寄存器值
root@OpenWrt:/#
root@OpenWrt:/# io -4 0x1804006c //读寄存器值
1804006c: 00000022
root@OpenWrt:/#
2、对GPIO1进行操作

步骤如下:

(1)先注册GPIO1

root@OpenWrt:/# echo 1 > /sys/class/gpio/export
root@OpenWrt:/#
root@OpenWrt:/# echo out > /sys/class/gpio/gpio1/direction
root@OpenWrt:/#
(2)查看系统GPIO的状态

root@OpenWrt:/# cat /sys/kernel/debug/gpio
GPIOs 0-17, ath79:
gpio-0 (sysfs ) out lo
gpio-1 (sysfs ) out lo
gpio-13 (sysfs ) out hi
gpio-17 (WPS button ) in hi
root@OpenWrt:/#
(3)对GPIO1进行拉高操作

(4)对GPIO1进行拉低操作

同理Link1灯GPIO16的操作控制如下:寄存器为0x1804003C,bit0-bit7对应GPIO16.

 

============== End

 

posted @ 2024-10-26 18:54  lsgxeva  阅读(56)  评论(0编辑  收藏  举报