hardware

【图文】使用C++完成树莓派和ADC的数据交互系列 part3 使用C++进行SPI编程


本文编译自 halherta 的文章,原文地址请点击这里

欢迎大家加我们的开发群共同进一步探讨IoT及可穿戴式设备开发,QQ群: 329401876 

通过 part2 我们已经了解了 SPI 型ADC MCP3008 硬件的时序图,以及从硬件的层面上树莓派如果与ADC之间如果进行数据交换。接下来我们会在树莓派中进行配置,从而完成使用C++程序对 SPI 器件的控制。

  在树莓派中启用Spidev                                                                                                                      

  1. 给树莓派上电,使它连上网络
  2. 通过SSH远程登录到树莓派上
  3. 通过命令 “sudo nano /etc/modprobe.d/raspi-blacklist.conf” 打开 raspi-blacklist.conf 文件
  4. 找到 blacklist spi-bcm2708 这一行,在前边加上”#”(无双引号),将它注释掉,即:#blacklist spi-bcm2708。保存退出
  5. 敲 “sudo reboot” 重启树莓派
  6. Raspberry Pi重启之后,用 SSH 重连,输入命令 “ls /dev/spidev*” 如果你看到如下图的显示,说明 Spidev 已经启动
在树莓派上启用 Spidev

图1 在树莓派上启用 Spidev

在上图中我们可以看到有两个 SPI 设备,设备名后边跟着两个数字,在树莓派上的只有一个SPI设备所以第一个数字都为0。后边一个数字是用来描述片选的,因为树莓派有两个片选管脚 CS0 和 CS1,所以第二位数字分别为0和1,我们下面的例子中只使用 CS0。

 编写C++程序让树莓派控制MCP3008                                                                               

接下来我们写个 C++ 类用来与MCP3008进行通讯。我们把这个类命名为”mcp3008Spi”,该类会依赖于 spidev 接口,虽然我们以 “mcp3008″开头,但是这个类可以很容易地扩展给其它的 SPI 设备使用。下边是 “mcp3008spi” 类的定义,里面包含四个成员变量,2个构造器和2个析构器。spiOpen()函数用来打开和配置 SPI 接口,spiClose()作用正好相反,我们通过 spiWriteRead() 函数来完成主从设备间的数据传输。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/***********************************************************************
 * This header file contains the mcp3008Spi class definition.
 * Its main purpose is to communicate with the MCP3008 chip using
 * the userspace spidev facility.
 * The class contains four variables:
 * mode        -> defines the SPI mode used. In our case it is SPI_MODE_0.
 * bitsPerWord -> defines the bit width of the data transmitted.
 *        This is normally 8. Experimentation with other values
 *        didn't work for me
 * speed       -> Bus speed or SPI clock frequency. According to
 *                https://projects.drogon.net/understanding-spi-on-the-raspberry-pi/
 *            It can be only 0.5, 1, 2, 4, 8, 16, 32 MHz.
 *                Will use 1MHz for now and test it further.
 * spifd       -> file descriptor for the SPI device
 *
 * The class contains two constructors that initialize the above
 * variables and then open the appropriate spidev device using spiOpen().
 * The class contains one destructor that automatically closes the spidev
 * device when object is destroyed by calling spiClose().
 * The spiWriteRead() function sends the data "data" of length "length"
 * to the spidevice and at the same time receives data of the same length.
 * Resulting data is stored in the "data" variable after the function call.
 * ****************************************************************************/
#ifndef MCP3008SPI_H
    #define MCP3008SPI_H
#include 
#include 
#include 
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include 
#include 
#include 
#include 
#include 
 
class mcp3008Spi{
 
public:
    mcp3008Spi();
    mcp3008Spi(std::string devspi, unsigned char spiMode, unsigned int spiSpeed, unsigned char spibitsPerWord);
    ~mcp3008Spi();
    int spiWriteRead( unsigned char *data, int length);
 
private:
    unsigned char mode;
    unsigned char bitsPerWord;
    unsigned int speed;
    int spifd;
 
    int spiOpen(std::string devspi);
    int spiClose();
 
};
 
#endif

构造器初始化成员变量以及调用 spiOpen()函数来初始化 SPI 接口。析购函数在 mcp3008spi 类的对象被销毁之前调用,它里面实际调用 spiClose() 来关闭 SPI 接口。

spiWriteRead() 用于发送或者接收一个或者多个字节,而且不光能用在 mcp3008上,还可以兼容其它的 SPI 设备。成员函数的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "mcp3008Spi.h"
using namespace std;
/**********************************************************
 * spiOpen() :function is called by the constructor.
 * It is responsible for opening the spidev device
 * "devspi" and then setting up the spidev interface.
 * private member variables are used to configure spidev.
 * They must be set appropriately by constructor before calling
 * this function.
 * *********************************************************/
int mcp3008Spi::spiOpen(std::string devspi){
    int statusVal = -1;
    this->spifd = open(devspi.c_str(), O_RDWR);
    if(this->spifd < 0){
        perror("could not open SPI device");
        exit(1);
    }
 
    statusVal = ioctl (this->spifd, SPI_IOC_WR_MODE, &(this->mode));
    if(statusVal < 0){
        perror("Could not set SPIMode (WR)...ioctl fail");
        exit(1);
    }
 
    statusVal = ioctl (this->spifd, SPI_IOC_RD_MODE, &(this->mode));
    if(statusVal < 0) {
      perror("Could not set SPIMode (RD)...ioctl fail");
      exit(1);
    }
 
    statusVal = ioctl (this->spifd, SPI_IOC_WR_BITS_PER_WORD, &(this->bitsPerWord));
    if(statusVal < 0) {
      perror("Could not set SPI bitsPerWord (WR)...ioctl fail");
      exit(1);
    }
 
    statusVal = ioctl (this->spifd, SPI_IOC_RD_BITS_PER_WORD, &(this->bitsPerWord));
    if(statusVal < 0) {
      perror("Could not set SPI bitsPerWord(RD)...ioctl fail");
      exit(1);
    }  
 
    statusVal = ioctl (this->spifd, SPI_IOC_WR_MAX_SPEED_HZ, &(this->speed));    
    if(statusVal < 0) {
      perror("Could not set SPI speed (WR)...ioctl fail");
      exit(1);
    }
 
    statusVal = ioctl (this->spifd, SPI_IOC_RD_MAX_SPEED_HZ, &(this->speed));    
    if(statusVal < 0) {
      perror("Could not set SPI speed (RD)...ioctl fail");
      exit(1);
    }
    return statusVal;
}
 
/***********************************************************
 * spiClose(): Responsible for closing the spidev interface.
 * Called in destructor
 * *********************************************************/
 
int mcp3008Spi::spiClose(){
    int statusVal = -1;
    statusVal = close(this->spifd);
        if(statusVal < 0) {
      perror("Could not close SPI device");
      exit(1);
    }
    return statusVal;
}
 
/********************************************************************
 * This function writes data "data" of length "length" to the spidev
 * device. Data shifted in from the spidev device is saved back into
 * "data".
 * ******************************************************************/
int mcp3008Spi::spiWriteRead( unsigned char *data, int length){
 
  struct spi_ioc_transfer spi[length];
  int i = 0;
  int retVal = -1; 
 
// one spi transfer for each byte
 
  for (i = 0 ; i < length ; i++){
 
    spi[i].tx_buf        = (unsigned long)(data + i); // transmit from "data"
    spi[i].rx_buf        = (unsigned long)(data + i) ; // receive into "data"
    spi[i].len           = sizeof(*(data + i)) ;
    spi[i].delay_usecs   = 0 ;
    spi[i].speed_hz      = this->speed ;
    spi[i].bits_per_word = this->bitsPerWord ;
    spi[i].cs_change = 0;
}
 
 retVal = ioctl (this->spifd, SPI_IOC_MESSAGE(length), &spi) ;
 
 if(retVal < 0){
    perror("Problem transmitting spi data..ioctl");
    exit(1);
 }
 
return retVal;
 
}
 
/*************************************************
 * Default constructor. Set member variables to
 * default values and then call spiOpen()
 * ***********************************************/
 
mcp3008Spi::mcp3008Spi(){
    this->mode = SPI_MODE_0 ;
    this->bitsPerWord = 8;
    this->speed = 1000000;
    this->spifd = -1;
 
    this->spiOpen(std::string("/dev/spidev0.0"));
 
    }
 
/*************************************************
 * overloaded constructor. let user set member variables to
 * and then call spiOpen()
 * ***********************************************/
mcp3008Spi::mcp3008Spi(std::string devspi, unsigned char spiMode, unsigned int spiSpeed, unsigned char spibitsPerWord){
    this->mode = spiMode ;
    this->bitsPerWord = spibitsPerWord;
    this->speed = spiSpeed;
    this->spifd = -1;
 
    this->spiOpen(devspi);
 
}
 
/**********************************************
 * Destructor: calls spiClose()
 * ********************************************/
mcp3008Spi::~mcp3008Spi(){
    this->spiClose();
}
最好我们写个程序来测试 mcp3008spi 类,程序中初始化 mcp3008spi 类的对象,并且通过 spidev 来配置 mcp3008 工作在单端转换方式下以及使用通道0作为输入端,之后向其发送3个字节的数据并同时接收发回的3个字节数据。发回的3个字节数据里最后两个字节包含了ADC转换好的数字量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/***********************************************************************
 * mcp3008SpiTest.cpp. Sample program that tests the mcp3008Spi class.
 * an mcp3008Spi class object (a2d) is created. the a2d object is instantiated
 * using the overloaded constructor. which opens the spidev0.0 device with
 * SPI_MODE_0 (MODE 0) (defined in linux/spi/spidev.h), speed = 1MHz &
 * bitsPerWord=8.
 *
 * call the spiWriteRead function on the a2d object 20 times. Each time make sure
 * that conversion is configured for single ended conversion on CH0
 * i.e. transmit ->  byte1 = 0b00000001 (start bit)
 *                   byte2 = 0b1000000  (SGL/DIF = 1, D2=D1=D0=0)
 *                   byte3 = 0b00000000  (Don't care)
 *      receive  ->  byte1 = junk
 *                   byte2 = junk + b8 + b9
 *                   byte3 = b7 - b0
 *    
 * after conversion must merge data[1] and data[2] to get final result
 *
 *
 *
 * *********************************************************************/
#include "mcp3008Spi.h"
 
using namespace std;
 
int main(void)
{
    mcp3008Spi a2d("/dev/spidev0.0", SPI_MODE_0, 1000000, 8);
    int i = 20;
        int a2dVal = 0;
    int a2dChannel = 0;
        unsigned char data[3];
 
    while(i > 0)
    {
        data[0] = 1;  //  first byte transmitted -> start bit
        data[1] = 0b10000000 |( ((a2dChannel & 7) << 4)); // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0)
        data[2] = 0; // third byte transmitted....don't care
 
        a2d.spiWriteRead(data, sizeof(data) );
 
        a2dVal = 0;
                a2dVal = (data[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result
                a2dVal |=  (data[2] & 0xff);
        sleep(1);
        cout << "The Result is: " << a2dVal << endl;
        i--;
    }
    return 0;
}
把上边的三个文件分别命名为 mcp3008spi.h, mcp3008spi.cpp 和 mcp3008spitest.cpp,并把它们放到树莓派的同一个目录中,然后 build 一下源文件
1
g++ -Wall -o OutBin  mcp3008Spi.cpp mcp3008SpiTest.cpp
之后运行 OutBin 可执行文件
1
sudo ./OutBin
开始扭动电位器,这时可以看到屏幕上会显示mcp3008转换出来的数字量,其值在 0~1023 之间,恭喜你这表示树莓派与ADC之间建立起了联系
显示mcp3008转换出来的数据

显示mcp3008转换出来的数据

好了,基本上MCU 上SPI 总线的操作基本都是如此,每个厂商现在也都会提供比较高层的编程接口调用,只需要改动很小就可以直接使用。欢迎大家加入到我们的开发群来 QQ群:329401876

本系列完.

hardware
Charles Chase 致力于为人类提供廉价持久能源的计划解决全球电力问题
hardware
【图文】Intel Edison C++ 开发之 I2C/IIC(Part3)–深入MRAA开发
General
3D手势控制又添新成员,nimble UX 即将向开发员发布新的 SDK
There are currently no comments.