欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

程序员文章站 2022-05-03 12:01:08
什么是 SPI 和上一篇文章的 I2C 总线一样,SPI(Serial Peripheral Interface,串行外设接口)也是设备与设备间通信方式的一种。SPI 是一种全双工(数据可以两个方向同时传输)的串行通信总线,由摩托罗拉于上个世纪 80 年代开发 "[1]" ,用于短距离设备之间的通信 ......

什么是 spi

和上一篇文章的 i2c 总线一样,spi(serial peripheral interface,串行外设接口)也是设备与设备间通信方式的一种。spi 是一种全双工(数据可以两个方向同时传输)的串行通信总线,由摩托罗拉于上个世纪 80 年代开发[1],用于短距离设备之间的通信。spi 包含 4 根信号线,一根时钟线 sck(serial clock,串行时钟),两根数据线 mosi(master output slave input,主机输出从机输入)和 miso(master input slave output,主机输入从机输出),以及一根片选信号 cs(chip select,或者叫 ss,slave select)。所谓的时钟线就是一种周期,两台设备数据传输不能各发各的,这样就没有意义,因此需要一种周期去对通信进行约束;数据线就是按照 mosi 和 miso 的中文翻译理解即可;片选信号用于主设备选择 spi 上的从设备,i2c 是靠地址选择设备,而 spi 靠的是片选信号,一般来说要选择哪个从设备只要将相应的 cs 线设置为低电平即可,特殊情况需要看数据手册。下图展示了一个 spi 主设备和三个 spi 从设备的示意图。

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

图源:wikipedia

spi 还有一个重要的概念就是时钟的极性(cpol,clock polarity)和相位(cpha,clock phase),对其这里不过多解释,我们只需要知道极性和相位的组合构成了 spi 的传输模式(spi mode)。在数据手册中,只要是 spi 通信协议的,一定会给出传输模式,我们根据数据手册进行设置即可。spi 的传输模式是有固定编号的,下表给出了各个模式,常用的模式有 mode0 和 mode3。

spi mode cpol cpha
mode0 0 0
mode1 0 1
mode2 1 0
mode3 1 1

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

该时序图显示了时钟的极性和相位。图源:wikipedia

spi 相比较 i2c 最大的优点就是传输速率高,并且数据在同一时间内可以双向传输,这都得益于它的两根输入和输出数据线。当然缺点也很明显,比 i2c 多了两根线,这就要多占用两个 io 接口。而且 spi 采用 cs 线去选择设备,不像 i2c 有寻址机制,如果你有很多个 spi 设备需要连接的话 io 接口的占用数量是相当高的。

在 raspberry pi 的引脚中,引出了两组 spi 接口。但有意思的是,在 raspbian 中 spi-1 是被禁用的,你需要修改一些参数去启用 spi-1。spi 接口的引脚编号如下图所示。

  提示

如何在 raspbian 上开启 spi-1?(在 win10 iot 上 spi-1 是开启的)

1. 使用编辑器打开 /boot/config.txt ,如:sudo nano /boot/config.txt
2. 添加 dtoverlay=spi1-3cs 并保存
3. 重启

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

raspberry pi b+/2b/3b/3b+/zero 引脚图

相关类

spi 操作的相关类位于 system.device.spisystem.device.spi.drivers 命名空间下。

spiconnectionsettings

spiconnectionsettings 类位于 system.device.spi 命名空间下,表示 spi 设备的连接设置。

public sealed class spiconnectionsettings
{
    // 构造函数
    // busid 是 spi 的内部 id
    // chipselectline 是 cs pin 的编号(在 raspberry pi 上,spi-0 对应 0 和 1,spi-1 对应 2)
    public spiconnectionsettings(int busid, int chipselectline);

    // 属性
    // spi 传输模式
    public spimode mode { get; set; }
    // spi 时钟频率
    public int clockfrequency { get; set; }
    // cs 线激活状态(即高电平选中设备还是低电平选中设备)
    public pinvalue chipselectlineactivestate { get; set; }
}

unixspidevice 和 windows10spidevice

unixspidevicewindows10spidevice 类位于 system.device.spi.drivers 命名空间下。两个类均派生自抽象类 spidevice,分别代表 unix 和 windows10 下的 spi 控制器,使用时按照所处的平台有选择的进行实例化。这里以 unixspidevice 类为例说明。

public class unixspidevice : spidevice
{
    // 构造函数
    // 需要传入一个 spiconnectionsettings 对象
    public unixspidevice(spiconnectionsettings settings);

    // 方法
    // 从从设备中读取一段数据,数据长度由 span 的长度决定
    public override void read(span<byte> buffer);
    // 从从设备中读取一个字节的数据
    public override byte readbyte();

    // 全双工传输,即主从设备同时传输
    // writebuffer 为要写入从设备的数据
    // readbuffer 为要从从设备中读取的数据
    // 需要注意的是 writebuffer 和 readbuffer 需要长度一致
    public override void transferfullduplex(readonlyspan<byte> writebuffer, span<byte> readbuffer);
    
    // 向从设备中写入一段数据,通常 span 中的第一个数据为要写入数据的寄存器的地址
    public override void write(readonlyspan<byte> buffer);
    // 向从设备中写入一个字节的数据,通常这个字节为寄存器的地址
    public override void writebyte(byte value);
}

spi 的通信步骤

  1. 初始化 spi 连接设置 spiconnectionsettings

    一般情况下,我们只需要配置 spi 的 id,cs 的编号,时钟频率和 spi 传输模式。其中像时钟频率、传输模式等设置都来自于设备的数据手册。比如要使用 raspberry pi 的 spi-0 去操作一个时钟频率为 5 mhz,spi 传输模式为 mode3 的设备,代码如下:

    spiconnectionsettings settings = new spiconnectionsettings(busid: 0, chipselectline: 0)
    {
        clockfrequency = 5000000,
        mode = spimode.mode3
    };
  2. 读取和写入

    读取和写入与 i2c 类似,这里不再过多赘述,详见上一篇博客,这里只提供一个代码示例。唯一要说明的就是使用全双工通信 transferfullduplex() 时,要求写入的数据和读取的数据长度要一致,并且能否使用也需要看设备是否支持。比如从地址为 0x00 的寄存器中向后连续读取 8 个字节的数据,并且向地址为 0x01 的寄存器写入一个字节的数据,代码如下:

    // 读取
    sensor.writebyte(0x00);
    span<byte> readbuffer = stackalloc byte[8]; 
    sensor.read(readbuffer);
    
    // 写入
    span<byte> writebuffer = stackalloc byte[] { 0x01, 0xff }; 
    sensor.write(writebuffer);
    
    // 全双工读取
    span<byte> writebuffer = stackalloc byte[8]; 
    span<byte> readbuffer = stackalloc byte[8];
    writebuffer[0] = 0x00;
    sensor.transferfullduplex(writebuffer, readbuffer);

加速度传感器读取实验

本实验选用的是三轴加速度传感器 adxl345 ,数据手册地址: 。

传感器图像

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

硬件需求

名称 数量
adxl345 x1
杜邦线 若干

电路

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信

  • vcc - 3.3 v
  • gnd - gnd
  • cs - cs0 (pin24)
  • sdo - spi0 miso (pin21)
  • sda - spi0 mosi (pin19)
  • scl - spi0 sclk (pin23)

代码

  1. 打开 visual studio ,新建一个 .net core 控制台应用程序,项目名称为“adxl345”。
  2. 引入 system.device.gpio nuget 包。
  3. 新建类 adxl345,替换如下代码:

    public class adxl345 : idisposable
    {
        #region 寄存器地址
        private const byte adlx_power_ctl = 0x2d;      // 电源控制地址
        private const byte adlx_data_format = 0x31;     // 范围地址
        private const byte adlx_x0 = 0x32;              // x轴数据地址
        private const byte adlx_y0 = 0x34;              // y轴数据地址
        private const byte adlx_z0 = 0x36;              // z轴数据地址
        #endregion
    
        private spidevice _sensor = null;
    
        private readonly int _range = 16;               // 测量范围(-8,8)
        private const int resolution = 1024;            // 分辨率
    
        #region spisetting
        /// <summary>
        /// adx1345 spi 时钟频率
        /// </summary>
        public const int spiclockfrequency = 5000000;
    
        /// <summary>
        /// adx1345 spi 传输模式
        /// </summary>
        public const spimode spimode = system.device.spi.spimode.mode3;
        #endregion
    
        /// <summary>
        /// 加速度
        /// </summary>
        public vector3 acceleration => readacceleration();
    
        /// <summary>
        /// 实例化一个 adx1345
        /// </summary>
        /// <param name="sensor">spidevice</param>
        public adxl345(spidevice sensor)
        {
            _sensor = sensor;
    
            // 设置 adxl345 测量范围
            // 数据手册 p28,表 21
            span<byte> dataformat = stackalloc byte[] { adlx_data_format, 0b_0000_0010 };
            // 设置 adxl345 为测量模式
            // 数据手册 p24
            span<byte> powercontrol = stackalloc byte[] { adlx_power_ctl, 0b_0000_1000 };
    
            _sensor.write(dataformat);
            _sensor.write(powercontrol);
        }
    
        /// <summary>
        /// 读取加速度
        /// </summary>
        /// <returns>加速度</returns>
        private vector3 readacceleration()
        {
            int units = resolution / _range;
    
            // 7 = 1个地址 + 3轴数据(每轴数据2字节)
            span<byte> writebuffer = stackalloc byte[7];
            span<byte> readbuffer = stackalloc byte[7];
    
            writebuffer[0] = adlx_x0;
            _sensor.transferfullduplex(writebuffer, readbuffer);
            span<byte> readdata = readbuffer.slice(1);      // 切割空白数据
    
            // 将小端数据转换成正常的数据
            short accelerationx = binaryprimitives.readint16littleendian(readdata.slice(0, 2));
            short accelerationy = binaryprimitives.readint16littleendian(readdata.slice(2, 2));
            short accelerationz = binaryprimitives.readint16littleendian(readdata.slice(4, 2));
    
            vector3 accel = new vector3
            {
                x = (float)accelerationx / units,
                y = (float)accelerationy / units,
                z = (float)accelerationz / units
            };
    
            return accel;
        }
    
        /// <summary>
        /// 释放资源
        /// </summary>
        public void dispose()
        {
            _sensor?.dispose();
            _sensor = null;
        }
    }
  4. program.cs 中,将主函数代码替换如下:

    static void main(string[] args)
    {
        spiconnectionsettings settings = new spiconnectionsettings(busid: 0, chipselectline: 0)
        {
            clockfrequency = adxl345.spiclockfrequency,
            mode = adxl345.spimode
        };
        unixspidevice device = new unixspidevice(settings);
    
        using (adxl345 sensor = new adxl345(device))
        {
            while (true)
            {
                vector3 data = sensor.acceleration;
    
                console.writeline($"x: {data.x.tostring("0.00")} g");
                console.writeline($"y: {data.y.tostring("0.00")} g");
                console.writeline($"z: {data.z.tostring("0.00")} g");
                console.writeline();
    
                thread.sleep(500);
            }
        }
    }
  5. 发布、拷贝、更改权限、运行

效果图

张高兴的 .NET Core IoT 入门指南:(四)使用 SPI 进行通信


  备注

下一篇文章将谈谈 pwm 的使用。