樹莓派的接口:
- 大而簡單的類別:IO口,input和output是相對于主控芯片來說的,是根據MCU和外設之間的關系將IO口的功能分為output和input。當IO作為input使用時外設有:人體傳感器、煙霧傳感器、火焰傳感器、振動傳感器等等;當IO作為output使用時的外設有:繼電器、蜂鳴器等等。
- PWM輸出IO口用來電機調速、燈光調亮度等等、串口、IIC、SPI、IIS和其他特定硬件的接口等等。
- 像我們之前接觸過的C51、STM32、Arduino等等這類的單片機是直接基于裸機的開發,而像樹莓派、Nanopi、Tiny210這些設備是基于操作系統的,裸機的驅動和操作系統的驅動還是有很大的區別的。
- 樹莓派外設開發接口庫:WiringPi庫(就是別人幫你做好的很多的接口很多的API你只需要了解如何調用這些API和了解這些API如何使用就好,他就是特定平臺特定功能的接口組成的庫)至于API是如何驅動硬件工作的,后面會在驅動這一塊詳細講解。
WiringPi庫詳解:
wiringPi是一個很棒的樹莓派IO控制庫,使用C語言開發,提供了豐富的接口:GPIO控制,中斷,多線程,等等。自己的樹莓派可能會有這個庫也有可能沒有,在樹莓派終端輸入指令:gpio -v
查看有沒有裝WiringPi庫,出現下圖所示表示已經有WiringPi庫了(這里的版本是2.50)
如果沒有裝的話,它會提示指令不認識,可以根據以下步驟安裝:
- 進入 wiringPi 下載安裝包。然后進入安裝包所在的目錄執行以下命令:
tar xfz wiringPi-98bcb20.tar.gz //98bcb20為版本標號,可能不同
cd wiringPi-98bcb20
./build
- 當然也可以通過終端命令來安裝WiringPi庫,安裝Git如果你的系統還沒有安裝Git版本控制工具,請先安裝Git:輸入命令:
sudo apt-get install git-core
下載wiringPi庫輸入命令:git clone git://git.drogon.net/wiringPi
安裝輸入命令:cd wiringPi
和./build
安裝完成后,測試是否安裝成功輸入命令:gpio -v
- 接下來就是WringPi庫的使用了,使用
gpio readall
可以查看樹莓派GPIO的功能分布,還有就是使用樹莓派的時候如果用到WringPi庫編譯時要鏈上樹莓派的WringPi庫就像之前的使用多線程編程一樣,-lwiringPi
WringPi庫API大全:
- 硬件初始化函數,使用wiringPi時,你必須在執行任何操作前初始化樹莓派,否則程序不能正常工作。可以調用下表函數之一進行初始化,它們都會返回一個int , 返回 -1 表示初始化失敗。 在使用wiringPi庫時,你需要包含頭文件 #include<wiringPi.h>。凡是寫wiringPi的程序,都包含這個頭文件。
int wiringPiSetup (void)
說明:當使用這個函數初始化樹莓派引腳時,程序使用的是wiringPi 引腳編號表。引腳的編號為 0~16需要root權限返回值:返回:執行狀態,-1表示失敗int wiringPiSetupGpio (void)
說明:當使用這個函數初始化樹莓派引腳時,程序中使用的是BCM GPIO 引腳編號表。需要root權限返回值:返回執行狀態,-1表示失敗wiringPiSetupPhys(void)和wiringPiSetupSys (void)不太常用,不做介紹。
- 通用GPIO控制函數
void pinMode (int pin, int mode)
參數:pin:配置的引腳、mode:指定引腳的IO模式可取的值:INPUT、OUTPUT、PWM_OUTPUT,GPIO_CLOCK。
作用:配置引腳的IO模式。
注意:
只有wiringPi 引腳編號下的1腳(BCM下的18腳) 支持PWM輸出
只有wiringPi編號下的7(BCM下的4號)支持GPIO_CLOCK輸出void digitalWrite (int pin, int value)
參數:pin:控制的引腳、value:引腳輸出的電平值、 可取的值:HIGH,LOW分別代表高低電平。
功能:讓對一個已近配置為輸出模式的 引腳 輸出指定的電平信號。int digitalRead (int pin)
參數:pin:讀取的引腳、返回:引腳上的電平,可以是LOW HIGH 之一
功能:讀取一個引腳的電平值 LOW HIGH ,返回void analogWrite(int pin, int value)
參數:pin:引腳、value:輸出的模擬量
功能:模擬量輸出、樹莓派的引腳本身是不支持AD轉換的,也就是不能使用模擬量的API,需要增加另外的模塊int analogRead (int pin)
參數:pin:引腳、返回:引腳上讀取的模擬量
功能:模擬量輸入、樹莓派的引腳本身是不支持AD轉換的,也就是不能使用模擬量的API,需要增加另外的模塊void pwmWrite (int pin, int value)
參數:pin:引腳、value:寫入到PWM寄存器的值,范圍在0~1024之間。
功能:輸出一個值到PWM寄存器,控制PWM輸出。pin只能是wiringPi 引腳編號下的1腳(BCM下的18腳)void pullUpDnControl (int pin, int pud)
參數:pin:引腳、pud:拉電阻模式,可取的值:PUD_OFF不啟用任何拉電阻,關閉拉電阻。
PUD_DOWN啟用下拉電阻,引腳電平拉到GND。PUD_UP啟用上拉電阻,引腳電平拉到3.3v
功能:對一個設置IO模式為 INPUT 的輸入引腳設置拉電阻模式。與Arduino不同的是,樹莓派支持的拉電阻模式更豐富。樹莓派內部的拉電阻達50K歐姆
- 時間控制函數
unsigned int millis (void)
功能:這個函數返回一個從你的程序執行wiringPiSetup初始化函數(或者wiringPiSetupGpio)到當前時間經過的毫秒數。
返回類型是:unsigned int,最大可記錄 大約49天的毫秒時長。unsigned int micros (void)
功能:這個函數返回 一個從你的程序執行wiringPiSetup初始化函數(或者wiringPiSetupGpio )到當前時間 經過的微秒數。
返回類型是:unsigned int,最大可記錄 大約71分鐘的時長。void delay (unsigned int howLong)
功能:將當前執行流暫停指定的毫秒數。因為Linux本身是多線程的,所以實際暫停時間可能會長一些。
參數是:unsigned int 類型,最大延時時間可達49天void delayMicroseconds (unsigned int howLong)
功能:將執行流暫停指定的微秒數(1000微秒 = 1毫秒 = 0.001秒。因為Linux本身是多線程的,所以實際暫停時間可能會長一些。
參數是:unsigned int 類型,最大延時時間可達71分鐘
- 中斷:wiringPi提供了一個中斷處理注冊函數,它只是一個注冊函數,并不處理中斷。他無需root權限。
int wiringPiISR (int pin, int edgeType, void (*function)(void))
功能:注冊的函數會在中斷發生時執行和51單片機不同的是:這個注冊的中斷處理函數會和main函數并發執行(同時執行,誰也不耽誤誰)當本次中斷函數還未執行完畢,這個時候樹莓派又觸發了一個中斷,那么這個后來的中斷不會被丟棄,它仍然可以被執行。但是wiringPi最多可以跟蹤并記錄后來的僅僅1個中斷,如果不止1個,則他們會被忽略,得不到執行。
參數:pin:接受中斷信號的引腳edgeType:觸發的方式。INT_EDGE_FALLING:下降沿觸發INT_EDGE_RISING:上升沿觸發INT_EDGE_BOTH :上下降都會觸發INT_EDGE_SETUP:編程時用不到。 function:中斷處理函數的指針,它是一個無返回值,無參數的函數。
- 多線程:wiringPi提供了簡單的Linux系統下的通用的 Posix threads線程庫接口來支持并發。因為之前學習過linux多線程的開發,所以一般不用這個WringPi庫里面的多線程函數,他這里面的函數也是機=基于linux線程封裝的
int piThreadCreate(name)
參數:name:被包裝的線程執行函數
返回值:狀態碼。返回0表示成功啟動,反之失敗。
源代碼:
int piThreadCreate (void *(*fn)(void *))
{pthread_t myThread ;return pthread_create (&myThread, NULL, fn, NULL) ;
}
包裝一個用PI_THEEAD定義的函數為一個線程,并啟動這個線程。
首先你需要通過以下方式創建一個特特殊的函數。這個函數中的代碼就是在新的線程中將執行的代碼。myTread是你自己線程的名字,可自定義。
PI_THREAD (myThread)
{//在這里面寫上的代碼會和主線程并發執行。
}
在wiringPi.h中,我發現這樣一個宏定義:#define PI_THREAD(X) void *X (void *dummy)
那么,被預處理后我們寫的線程函數會變成下面這個樣子,請注意返回值,難怪我每次寫都會警告,因為沒有返回一個指針,
那么,以后注意返回NULL,或者 (void*)0
void *myThread (void *dummy)
{//在這里面寫上的代碼會和主線程并發執行。
}piLock(int keyNum)
功能:使能同步鎖。wiringPi只提供了4把鎖,也就是keyNum只能取0~3的值,官方認為有這4把鎖就夠了。
參數:keyNum:0,1,2,3 每一個數字就代表一把鎖。
源代碼:
void piLock (int keyNum)
{pthread_mutex_lock (&piMutexes [keyNum]) ;
}piUnlock(int keyNum)
功能:解鎖,或者說讓出鎖。
參數:keyNum:0-3的值,每一個值代表一把鎖
解鎖,或者說讓出鎖。
源代碼:
void piUnlock (int key)
{pthread_mutex_unlock (&piMutexes [key]) ;
}int piHiPri (int priority)
功能:設定線程的優先級,設定線程的優先級變高,不會使程序運行加快,但會使這個線程獲得相當更多的時間片。priority是相對的。比如你的程序只用到了主線程和另一個線程A,主線程設定優先級為1,A線程設定為2,那也代表A比main線程優先級高。
參數:priority:優先級指數,0~99
返回值:0:成功,-1:失敗。
- softPwm,軟件實現的PWM,樹莓派硬件上支持的PWM輸出的引腳有限,為了突破這個限制,wiringPi提供了軟件實現的PWM輸出API。需要包含頭文件:#include <softPwm.h>編譯時需要添pthread庫鏈接 -lpthread
int softPwmCreate (int pin, int initialValue, int pwmRange)
功能:使用一個指定的pin引腳創建一個模擬的PWM輸出引腳
參數:pin:用來作為軟件PWM輸出的引腳initalValue:引腳輸出的初始值pwmRange:PWM值的范圍上限,建議使用100.
返回:0表示成功。void softPwmWrite (int pin, int value)
功能:更新引腳輸出的PWM值
參數:pin:通過softPwmCreate創建的引腳value:PWM引腳輸出的值
- 串口通信,使用時需要包含頭文件:#include <wiringSerial.h>
int serialOpen (char *device, int baud)
功能:打開并初始串口
參數:device:串口的地址,在Linux中就是設備所在的目錄。默認一般是"/dev/ttyAMA0",我的是這樣的。baud:波特率
返回:正常返回文件描述符,否則返回-1失敗。void serialClose (int fd)
功能:關閉fd關聯的串口
參數:fd:文件描述符void serialPutchar (int fd, unsigned char c)
功能:發送一個字節的數據到串口
參數:fd:文件描述符c:要發送的數據void serialPuts (int fd, char *s)
功能:發送一個字符串到串口
參數:fd:文件描述符s:發送的字符串,字符串要以'\0'結尾void serialPrintf (int fd, char *message, …)
功能:像使用C語言中的printf一樣發送數據到串口
參數:fd:文件描述符message:格式化的字符串 int serialDataAvail (int fd)
功能:獲取串口緩存中可用的字節數。
參數:fd:文件描述符
返回:串口緩存中已經接收的,可讀取的字節數,-1代表錯誤int serialGetchar (int fd)
功能:從串口讀取一個字節數據返回。如果串口緩存中沒有可用的數據,則會等待10秒,如果10后還有沒,返回-1所以,在讀取前,做好通過serialDataAvail判斷下。
參數:fd:文件描述符
返回:讀取到的字符void serialFlush (int fd)
功能:刷新,清空串口緩沖中的所有可用的數據。
參數:fd:文件描述符*size_t write (int fd,const void * buf,size_t count)
功能:這個是Linux下的標準IO庫函數,需要包含頭文件#include <unistd.h>當要發送到的數據量過大時,wiringPi建議使用這個函數。*size_t read(int fd,void * buf ,size_t count);
功能:這個是Linux下的標準IO庫函數,需要包含頭文件#include <unistd.h>當要接收的數據量過大時,wiringPi建議使用這個函數。
參數:fd:文件描述符buf:接受的數據緩存的數組count:接收的字節數.
返回:實際讀取的字符數。
- 初次使用樹莓派串口編程,需要配置。
/* 修改 cmdline.txt文件 */
>cd /boot/
>sudo vim cmdline.txt
刪除【】之間的部分
dwc_otg.lpm_enable=0 【console=ttyAMA0,115200】 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait/*修改 inittab文件 */
>cd /etc/
>sudo vim inittab
注釋掉最后一行內容:,在前面加上 # 號
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
sudo reboot 重啟
- shift移位寄存器芯片API,需要包含頭文件 #include <wiringShift.h>,用過595的都知道還有一個引腳:12腳,Rpin,用于把移位寄存器中的數據更新到存儲寄存器中,然后wiringPi的API中沒有使用這個引腳。我建議使用的時候自己加上。
void shiftOut (uint8_t dPin, uint8_t cPin, uint8_t order, uint8_t val)
功能:將val串化,通過芯片轉化為并行輸出,如常見的74HC595
參數:dPin:移位芯片的串行數據入口引腳,比如74HC595的SER腳cPin:移位芯片的時鐘引腳。如74HC595的11腳order:LSBFIRST 先發送數據的低位MSBFIRST先發送數據的高位
val:要發送的8位數據uint8_t shiftIn (uint8_t dPin, uint8_t cPin, uint8_t order)
功能:將并行數據,通過芯片轉化為串行輸出。
參數同上。
- 樹莓派硬件平臺特有的API,并沒有列全,我只是列出了相對來說有用的,其他的,都基本不會用到。pwmSetMode、pwmSetRange、pwmSetClock函數應該都是硬件PWM的函數。
pwmSetMode (int mode)
功能:設置PWM的運行模式。pwm發生器可以運行在2種模式下,通過參數指定:PWM_MODE_BAL:樹莓派默認的PWM模式PWM_MODE_MS:傳統的pwm模式
參數:mode:PWM運行模式pwmSetRange (unsigned int range)
功能:設置pwm發生器的數值范圍,默認是1024
參數:range,范圍的最大值,0~rangepwmSetClock (int divisor)
功能:這設置了PWM時鐘的除數默認是32分頻,也就是19.2/32 = 600KHZ,因此不調用此函數,pwm的默認工作時鐘為600KHz.piBoardRev (void)
返回:樹莓派板子的版本編號,1或者2。
Wringpi庫部分參考博客:https://ww.cnblogs.com/lulipro/p/5992172.html
- 使用指令
uname -r
查看樹莓派的版本,下載linux內核源碼的時候可以使用。
使用樹莓派控制第一個外設——繼電器模塊:
-
繼電器模塊如下圖,控制端一共有三個針腳,其中使用3.3V供電和連接GND,另一個接口IN連接樹莓派的GPIO進行控制,低電平拉低繼電器另一端導通,高電平繼電器另一端不導通。
-
可以使用指令
gpio readall
來查看GPIO的功能圖
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wiringPi.h>
int main()
{int initReturn;char* cmd;cmd=(char*)malloc(128);initReturn=wiringPiSetup();if(initReturn==-1)printf("init fail\n");pinMode(1,OUTPUT);//初始化IO口功能while(1){memset(cmd,'\0',128);printf("please input cmd\n");scanf("%s",cmd);if(strcmp(cmd,"open")==0)digitalWrite(1,LOW);//繼電器導通else if(strcmp(cmd,"close")==0)digitalWrite(1,HIGH);//繼電器不導通memset(cmd,'\0',128);}return 0;
}
- 上面是控制繼電器的代碼,編譯的時候要使用指令
gcc switch.c -o switch -lwiringPi
注意:要加上wiringPi庫
樹莓派外設——超聲波模塊:
- 超聲波模塊上面通常有兩個超聲波元器件,一個用于發射,一個用于接收。電路板上有四個引腳:VCC(正極)、Trig(觸發)、Echo(接收)、GND(接地)
- 超聲波的主要參數:工作電壓與電流:5V、15mA;測距范圍:2~400cm;感測角度:不大于15°;被測物的面積不要小于50平方厘米并且盡量平整;具備溫度補償電路
- 工作原理:在超聲波的觸發腳位輸入10微妙以上的高電位,即可發射超聲波,發射超聲波以后與接收傳回的超聲波之前,“接收引腳”呈現高電位,因此,程序可以從“接收引腳”的高電位脈沖持續時間,換算出被測物的距離。
#include <stdio.h>
#include <wiringPi.h>
#define Echo 4
#define Trige 5
int main()
{int timeone,timetwo;float distance;wiringPiSetup();pinMode(Trige,OUTPUT);pinMode(Echo,INPUT);while(1){digitalWrite(Trige,LOW);delayMicroseconds(2);digitalWrite(Trige,HIGH);delayMicroseconds(10);digitalWrite(Trige,LOW);while(digitalRead(Echo)!=HIGH);//確保Echo引腳在計時前為低電平timeone=micros();//得到程序運行到此位置的時間while(digitalRead(Echo)==HIGH);timetwo=micros();//得到程序運行到此位置的時間distance=(float)(timetwo-timeone)/58;//timetwo-timeone為Echo高電平持續的時間,單位是us除以58即可得到距離。printf("distance is %0.2f cm\n",distance);delay(100);}return 0;
}
- 上面那個超聲波測距的程序是我自己寫的,下面這個是從網上查找的,感覺比我寫的高大上。
#include <wiringPi.h>
#include <stdio.h>
#include <sys/time.h>
#define Trig 4
#define Echo 5void ultraInit(void)
{pinMode(Echo, INPUT); //設置端口為輸入pinMode(Trig, OUTPUT); //設置端口為輸出
}float disMeasure(void)
{struct timeval tv1; //timeval是time.h中的預定義結構體 其中包含兩個一個是秒,一個是微秒/*struct timeval{time_t tv_sec; //Seconds.suseconds_t tv_usec; //Microseconds.};*/struct timeval tv2;long start, stop;float dis;digitalWrite(Trig, LOW);delayMicroseconds(2);digitalWrite(Trig, HIGH);delayMicroseconds(10); //發出超聲波脈沖digitalWrite(Trig, LOW);while(digitalRead(Echo) != 1));gettimeofday(&tv1, NULL); //獲取當前時間 開始接收到返回信號的時候while(!(digitalRead(Echo) == 0));gettimeofday(&tv2, NULL); //獲取當前時間 最后接收到返回信號的時候/*int gettimeofday(struct timeval *tv, struct timezone *tz);The functions gettimeofday() and settimeofday() can get and set the time as well as a timezone.The use of the timezone structure is obsolete; the tz argument should normally be specified as NULL.*/start = tv1.tv_sec * 1000000 + tv1.tv_usec; //微秒級的時間stop = tv2.tv_sec * 1000000 + tv2.tv_usec;dis = (float)(stop - start) / 1000000 * 34000 / 2; //計算時間差求出距離return dis;
}int main(void)
{float dis;if(wiringPiSetup() == -1){ //如果初始化失敗,就輸出錯誤信息 程序初始化時務必進行printf("setup wiringPi failed !");return 1;}ultraInit();while(1){dis = disMeasure();printf("distance = %0.2f cm\n",dis);delay(1000);}return 0;
}
- linux時間函數如下:
#include "sys/time.h"
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
- 用法很簡單,先定義時間結構體變量,然后使用
gettimeofday(&tv_b,NULL)
函數獲取當前時間,然后就可以計算出來時間差。示例如下:
gettimeofday(&tv_b,NULL);
/* a piece of codes ......*/
gettimeofday(&tv_d,NULL);
timeconsumed = tv_d.tv_sec-tv_b.tv_sec +(tv_d.tv_usev-tv_b.tv_usec)/1000000;//以秒為單位