概述
通过前面课程的学习,我们知道raspberry Pi可以做很多事情,能处理各种数字传感器,但是美中不足的是Raspberry Pi不能直接处理模拟信号,因为Pi没有ADC(analog to digital converters ),这样会限制输出模拟信号的传感器无法在Pi上使用,那如何解决这个问题呢?本课我们将一起探索如何用Pi读取模拟信号。
所需物料
1 * Raspberry Pi
1 * Breadboard
1 * MCP3008
Several jumper wires
工作原理
其实要解决上面的问题并不难,只需要在Pi上扩展一个模数转换器(ADC)就可以了,模数转化器有很多类型。在本课中我们使用Micochip公司的MCP3008,如果你对输出通道数量没有要求,MCP3004也是可以的。MCP3008一个10位8通道的ADC,MCP3004是10位4通道ADC,除了通道数量不一样,其他特性都相同,都是SPI(Serial Peripheral Interface Bus)接口,关于SPI总线更多信息请看这里,工作电压为2.7-5.5 VDC,关于MCP3004/8更多特性请参考芯片数据手册:https://osoyoo.com/driver/MCP3008_datasheet.pdf
MCP3008可以将模拟信号转换成数字信号,这样Pi就能够间接处理模拟信号了,在本课中,用pi的3.3V和GND分别作为MCP3008的模拟通道1和通道2的输入,MCP3008将这个模拟输入信号转换成数字量,Pi通过SPI读取这个数字量,通过计算将其实际电压值输出
MCP3008是SPI接口,所以将其与Raspberry Pi的硬件SPI相应GPIO口连接在一起。图中的NC(Not Connected)表示悬空,啥也不接。
实物连线图
软件
对于C语言用户,请看下面
1 ) MCP3008需要SPI时序来驱动,我们用的是Raspberry Pi的硬件SPI,但是对于有些Raspbian镜像在内核中默认没有加载有些模块,如SPI、IIC等,所以在进行编程之前需要检查你的Raspbian是否已经加载了SPI,如果没有需要手动加载,具体方法前看看面。
用编辑器打开‘/etc/modprobe.d/raspi-blacklist.conf’
sudo nano ‘/etc/modprobe.d/raspi-blacklist.conf’
如果打开什么东西也没有,说明你的Raspbian已经加载了相关模块,请跳过此步;如果看到这样的内容
#blacklist spi-bcm2708
#blacklist i2c-bcm2708
说明你的Raspbian没有加载SPI、IIC设备,你需要将这两句话前面的#去掉
打开SPI接口
sudo nano /boot/config.txt
打开config.txt文件后,找到dtparam=spi=on(或者dtparam=spi=off),将其改成图示样子
按键盘上的Ctrl +X根据提示输入Y保存退出,让后在命令行输入reboot是修改生效。
2) 在/home/pi目录下新建一个.c源文件,名字随意(你开心就好)
cd ~
sudo nano readmcp300x.c
2) 编码
向新建的文件中输入一下代码
#include <unistd.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <wiringPi.h> #include <stdio.h> #include <stdlib.h> #include <wiringPiSPI.h> #define TRUE (1==1) #define FALSE (!TRUE) #define CHAN_CONFIG_SINGLE 8 #define CHAN_CONFIG_DIFF 0 static int myFd ; char *usage = "Usage: mcp3008 all|analogChannel[1-8] [-l] [-ce1] [-d]"; // -l = load SPI driver, default: do not load // -ce1 = spi analogChannel 1, default: 0 // -d = differential analogChannel input, default: single ended void loadSpiDriver() { if (system("gpio load spi") == -1) { fprintf (stderr, "Can't load the SPI driver: %s\n", strerror (errno)) ; exit (EXIT_FAILURE) ; } } void spiSetup (int spiChannel) { if ((myFd = wiringPiSPISetup (spiChannel, 10000)) < 0) { fprintf (stderr, "Can't open the SPI bus: %s\n", strerror (errno)) ; exit (EXIT_FAILURE) ; } } int myAnalogRead(int spiChannel,int channelConfig,int analogChannel) { if(analogChannel<0 || analogChannel>7) return -1; unsigned char buffer[3] = {1}; // start bit buffer[1] = (channelConfig+analogChannel) << 4; wiringPiSPIDataRW(spiChannel, buffer, 3); return ( (buffer[1] & 3 ) << 8 ) + buffer[2]; // get last 10 bits } void print_info() { printf("\n"); printf("|*****************************************|\n"); printf("| Read MCP3008(3004) ADC value |\n"); printf("| ------------------------------ |\n"); printf("| | ADC | | Pi | |\n"); printf("| |-----|-----------|-----| |\n"); printf("| | CS | connect to| CE0 | |\n"); printf("| | Din | connect to| MOSI| |\n"); printf("| | Dout| connect to| MISO| |\n"); printf("| | CLK | connect to| SCLK| |\n"); printf("| | CH0 | connect to| 3.3V| |\n"); printf("| | CH1 | connect to| GND | |\n"); printf("| OSOYOO|\n"); printf("|*****************************************|\n"); printf("\n"); } int main (int argc, char *argv []) { int loadSpi=FALSE; int analogChannel=0; int spiChannel=0; int channelConfig=CHAN_CONFIG_SINGLE; if (argc < 2) { fprintf (stderr, "%s\n", usage) ; return 1 ; } if((strcasecmp (argv [1], "all") == 0) ) argv[1] = "0"; if ( (sscanf (argv[1], "%i", &analogChannel)!=1) || analogChannel < 0 || analogChannel > 8 ) { printf ("%s\n", usage) ; return 1 ; } int i; for(i=2; i<argc; i++) { if (strcasecmp (argv [i], "-l") == 0 || strcasecmp (argv [i], "-load") == 0) loadSpi=TRUE; else if (strcasecmp (argv [i], "-ce1") == 0) spiChannel=1; else if (strcasecmp (argv [i], "-d") == 0 || strcasecmp (argv [i], "-diff") == 0) channelConfig=CHAN_CONFIG_DIFF; } // if(loadSpi==TRUE) loadSpiDriver(); wiringPiSetup () ; spiSetup(spiChannel); print_info(); // if(analogChannel>0) { printf("MCP3008(CE%d,%s): analogChannel %d = %d\n",spiChannel,(channelConfig==CHAN_CONFIG_SINGLE) ?"single-ended":"differential",analogChannel,myAnalogRead(spiChannel,channelConfig,analogChannel-1)); } else { for(i=0; i<8; i++) { printf("MCP3008(CE%d,%s): analogChannel %d = %d\n",spiChannel,(channelConfig==CHAN_CONFIG_SINGLE) ?"single-ended":"differential",i+1,myAnalogRead(spiChannel,channelConfig,i)); } } close (myFd) ; return 0; }
按键盘上的Ctrl +X根据提示输入Y保存退出。
完整的程序源码,可以在命令行运行一下命令获取
sudo wget http://osoyoo.com/driver/pi3_start_learning_kit_lesson_10/readmcp3008.c
在程序开头先包含了程序运行需要的头文件,其中有两个是驱动MCP3008必须的头文件,一个是wiringPi,一个是wiringPiSPI。对于前者在之前的课程中都有用到,就不在赘述;后者是针对树莓派板载SPI开发的一个函数库,利用这个库可以更加容易使用Raspberry Pi板载SPI功能,关于wiringPiSPI更多信息请看这里
#define CHAN_CONFIG_SINGLE 8 #define CHAN_CONFIG_DIFF 0
因为MCP3008支持单端信号输入和(single-ended mode)和差分输入(differential input mode),本课中我采用单端输入模式,CHAN_CONFIG_SINGLE变量表示相应输入端(CHx)配置为单端输出模式(8表示通道0为单端输入模式),CHAN_CONFIG_DIFF表示相应输入端(CHx)配置为差分输入模式(0意思是说通道0为差分输入模式),依据数据手册可以计算每个通道在不同输入模式下的值
static int myFd ;
定义了一个文件描述符( file-descriptor),用于接收wiringPiSPISetup函数的返回值
char *usage = "Usage: mcp3008 all|analogChannel[1-8] [-l] [-ce1] [-d]"; // -l = load SPI driver, default: do not load // -ce1 = spi analogChannel 1, default: 0 // -d = differential analogChannel input, default: single ended
usage指针里面存放了这个程序的用法,如果在运行程序时候输入参数出错,屏幕会打印出usage变量,提示用户正确输入
void spiSetup (int spiChannel) { if ((myFd = wiringPiSPISetup (spiChannel, 10000)) < 0) { fprintf (stderr, "Can't open the SPI bus: %s\n", strerror (errno)) ; exit (EXIT_FAILURE) ; } }
这是一个自定义函数,在这个函数里面调用了wiringPiSPI库里面的wiringPiSPISetup函数,初始化spiChannel号SPI,并SPI通信速率设置成了10KHz,其中spiChannel表示Raspberry Pi第几个SPI,Raspberry Pi一共有2个硬件SPI,它们共用SCLK、MOSI、MISO三条线,通过CE0和CE1区分SPI0和SPI1,在本课中我们将MCP3008的片选脚(Chip Select/Shutdown Input)接到Pi的CE0上,即我们使用的是Pi的SPI0.如果wiringPiSPISetup返回值小于0,说明SPI初始化出错,并在屏幕上打印出错信息。
int myAnalogRead(int spiChannel,int channelConfig,int analogChannel) { if(analogChannel<0 || analogChannel>7) return -1; unsigned char buffer[3] = {1}; // start bit buffer[1] = (channelConfig+analogChannel) << 4; wiringPiSPIDataRW(spiChannel, buffer, 3); return ( (buffer[1] & 3 ) << 8 ) + buffer[2]; // get last 10 bits }
这个函数用于读取MCP3008数据,因为MCP3008只有8个输入端,如果参数analogChannel不在0-7范围内,返回-1;结合MCP3008数据手册知道第一个子节和第二个子节的高6位是无效的,有效数据为是第二个子节的低2位和第三个子节,一共10位。
3) 编译
gcc -Wall -o readmcp3008 readmcp3008 .c -iwiringPi
4) 执行程序
a)在运行程序之前请用gpio readall的命令检查MOSI、MISO、SCLK(B10、B9、B11)这几个口是否工作于ALT0模式(alternative functions),如果这几个口处于ALT0模式,则可以跳到b;如若不是请用下面的shell命令将他们变成ALT0模式
gpio -g mode 9 alt0
gpio -g mode 10 alt0
gpio -g mode 11 alt0
如果你想查看8个通道的值,运行下面的命令:
./readmcp3008 all
如果你只想查看指定通道的值,例如通道1,运行下面的命令
./readmcp3008 1
5) 最终结果
正确输入参数,运行上面的程序,屏幕上会打印出MCP3008主要引脚与Pi的连接关系,也会打印出你想查看的通道的结果。因为我们将CH1接到3.3V,CH2接到0V,所以转换后,输出通道1的值是1024,输出通道2是0
对于Python用户
1) 在/home/pi下新建一个.py脚本文件,文件名随意(你爱咋咋地)
cd ~
sudo nano readmcp3008.py
2) 编码
import time import os import RPi.GPIO as GPIO # change these as desired - they're the pins connected from the # SPI port on the ADC to the Cobbler SPICLK = 11 SPIMISO = 9 SPIMOSI = 10 SPICS = 8 #DEBUG = 1 #setup function for some setup---custom function def setup(): GPIO.setwarnings(False) #set the gpio modes to BCM numbering GPIO.setmode(GPIO.BCM) # set up the SPI interface pins GPIO.setup(SPIMOSI, GPIO.OUT) GPIO.setup(SPIMISO, GPIO.IN) GPIO.setup(SPICLK, GPIO.OUT) GPIO.setup(SPICS, GPIO.OUT) #print message at the begining ---custom function def print_message(): print ('|**********************************|') print ('| Read MCP3008(3004) ADC value |') print ('| ----------------------------- |') print ('| | ADC | | Pi | |') print ('| |-----|-----------|-----| |') print ('| | CS | connect to| CE0 | |') print ('| | Din | connect to| MOSI| |') print ('| | Dout| connect to| MISO| |') print ('| | CLK | connect to| SCLK| |') print ('| | CH0 | connect to| 3.3V| |') print ('| | CH1 | connect to| GND | |') print ('| ----------------------------- |') print ('| OSOYOO|') print ('|**********************************|\n') print ('Program is running...') print ('Please press Ctrl+C to end the program...') print ('please input 0 to 7...') # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) def readadc(adcnum, clockpin, mosipin, misopin, cspin): if ((adcnum > 7) or (adcnum < 0)): return -1 GPIO.output(cspin, True) GPIO.output(clockpin, False) # start clock low GPIO.output(cspin, False) # bring CS low commandout = adcnum commandout |= 0x18 # start bit + single-ended bit commandout <<= 3 # we only need to send 5 bits here for i in range(5): if (commandout & 0x80): GPIO.output(mosipin, True) else: GPIO.output(mosipin, False) commandout <<= 1 GPIO.output(clockpin, True) GPIO.output(clockpin, False) adcout = 0 # read in one empty bit, one null bit and 10 ADC bits for i in range(12): GPIO.output(clockpin, True) GPIO.output(clockpin, False) adcout <<= 1 if (GPIO.input(misopin)): adcout |= 0x1 GPIO.output(cspin, True) adcout >>= 1 # first bit is 'null' so drop it return adcout #main function def main(): #print info print_message() analogChannel = int(input()) if (analogChannel < 0) or (analogChannel > 7): print ('input error analogChannel number!') print ('please input 0 to 7...') else: adc = readadc(analogChannel, SPICLK, SPIMOSI, SPIMISO, SPICS) print ('analogChannel %d = %d'%(analogChannel,adc)) #define a destroy function for clean up everything after the script finished def destroy(): #release resource GPIO.cleanup() # # if run this script directly ,do: if __name__ == '__main__': setup() try: main() #when 'Ctrl+C' is pressed,child program destroy() will be executed. except KeyboardInterrupt: destroy()
按键盘上的Ctrl +X根据提示输入Y保存退出。
完整的程序源码,可以在命令行运行一下命令获取
sudo wget http://osoyoo.com/driver/pi3_start_learning_kit_lesson_10/readmcp3008.py
3) 运行程序
sudo python ./readmcp3008.py
然后输入你想读取的输出通道,例如0,就在SHH终端输入0再回车,就能打印出输出通道0的数据。
4) 最终结果
在SSH终端输入sudo python ./readmcp3008.py后屏幕会打印出MCP3008与Pi的连接信息以及输入参数范围(0-7),如果输入的参数不在正确范围,程序会输入错误提示;如果输入的参数在0-7范围内,那就打印出对应端口的数据。
DownLoad Url osoyoo.com