這次用 SPI。BBB 有兩套 SPI 接口可用,兩套都是默認 disable,需要用 overlay 方式啟用,即:
echo BB-SPIDEV0 > /sys/devices/bone_capemgr.9/slots
我的 BBB 當前配置當前配置
/opt/source/Userspace-Arduino/overlay/BB-SPI0-01-00A0.dts
/dts-v1/;
/plugin/;
/ {
compatible = "ti,beaglebone", "ti,beaglebone-black";
/* identification */
part-number = "spi0pinmux";
fragment@0 {
target = <&am33xx_pinmux>;
__overlay__ {
spi0_pins_s0: spi0_pins_s0 {
pinctrl-single,pins = <
0x150 0x30 /* spi0_sclk, INPUT_PULLUP | MODE0 */
0x154 0x30 /* spi0_d0, INPUT_PULLUP | MODE0 */
0x158 0x10 /* spi0_d1, OUTPUT_PULLUP | MODE0 */
0x15c 0x10 /* spi0_cs0, OUTPUT_PULLUP | MODE0 */
>;
};
};
};
fragment@2 {
target = <&spi0>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins_s0>;
spidev@0 {
spi-max-frequency = <24000000>;
reg = <0>;
compatible = "linux,spidev";
};
};
};
};
說明書第三十頁,BMP280 支持 Mode 00 和 11,自動選的。可三線或四線連接。
dts 檔內可見,當前就是 mode 0,可以直接連 BMP280了。
SPI 接線 – BMP280
原理圖在上一篇博文,從圖可以得知,BMP280 的片選是 CSB,NCS 是九軸 MPU9250 的片選引腳。
我那很爛的焊接,錫明顯過多,烙鐵溫度過高,家里沒洗板水,臟兮兮的。SPI0 的接線如下:
| BBB | GY-91 | 說明 |
| P9_1 | GND | 地 |
| P9_3 | 3V3 | 電源 |
| P9_22 | SCL | 時鍾 |
| P9_17 | CSB | 片選 |
| P9_18 | SDA | MOSI |
| P9_21 | SA0 | MISO |
啟用 SPI0 :
echo BB-SPIDEV0 > /sys/devices/bone_capemgr.9/slots
然后在 /dev 就會出現了:
spidev1.0 代表總線號 1 片選號 0。
SPI 讀取 BMP280 的 ID Register 值
注意事項如上圖,圖片截取自 BMP280 Datasheet,BMP280 的 SPI Read 方式從 BBB 發出的一個 byte 是 Control byte,Control byte 的 bit 7 用來控制讀寫,地址是后面余下的 7 個 bits。上圖Control byte 后面的 Data byte 是接收的,比如我四線情況,它在 MISO 出現的,上圖這樣表達比較難理解……。
看一下時序圖,看這個我比較好理解一些:
通訊過程中片選 pull down。
畢竟主角是九軸,BMP280溫度氣壓就快速試一次,試試而已。C 代碼。
有好幾段小插曲,我首先想用 Python,易寫易改,找到了SPIDEV,可是無論如何都無法用雙工連,最終由於時間關系,放棄了。PY-SPIDEV見以此鏈接:
https://pypi.python.org/pypi/spidev 。后來改用 C,各種百度FQ再 Google,多半是 Arduino 和用上 Adafruit 的類庫,看到各種 Digital Write,最后也放棄了。最后直接用 linux/spi/spidev 自己參考一下別人就直接寫。
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
char buf[1];
char buf2[10];
struct spi_ioc_transfer xfer[2];
int spi_init(char filename[40]) {
int file;
__u8 mode = 0;
__u8 lsb = 0;
__u8 bits = 8;
__u32 speed = 10000000;
if ((file = open(filename,O_RDWR)) < 0) {
printf("Failed to open the bus.");
exit(1);
}
if (ioctl(file, SPI_IOC_WR_MODE, &mode)<0) {
perror("can't set spi mode");
return;
}
if (ioctl(file, SPI_IOC_RD_MODE, &mode) < 0) {
perror("SPI rd_mode");
return;
}
if (ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {
perror("SPI rd_lsb_fist");
return;
}
if (ioctl(file, SPI_IOC_WR_BITS_PER_WORD, &bits)<0) {
perror("can't set bits per word");
return;
}
if (ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {
perror("SPI bits_per_word");
return;
}
if (ioctl(file, SPI_IOC_WR_MAX_SPEED_HZ, &speed)<0) {
perror("can't set max speed hz");
return;
}
if (ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {
perror("SPI max_speed_hz");
return;
}
printf("%s: spi mode %d, %d bits %sper word, %d Hz max\n",filename, mode, bits, lsb ? "(lsb first) " : "", speed);
return file;
}
char * spi_read(int addr,int nbytes,int file) {
int status;
memset(buf, 0, sizeof buf);
memset(buf2, 0, sizeof buf2);
buf[0] = addr | 128;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned long) buf2;
xfer[1].len = nbytes;
status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return;
}
return buf2;
}
int main(){
char * buffer;
int file=spi_init("/dev/spidev1.0");
buffer = spi_read(0xD0,1,file);
printf("0x%x\n",*buffer);
}
代碼改自這里:http://linux-sunxi.org/SPIdev,原版是 SUNXI 的,我已修改並去掉些不合理地方。
首先配置, 默認 mode 0 ,8 bit per word 再把速度調到BMP280 的上限 10MHz,然后用 read 方法,兩個 xfer 丟進去,一個是寫 0xD0,代表我想讀取 ID Register,一個是讀取的 buf2,返回 buf2 到 main 再打印出來。read 里面的 bitwise OR 是為了確保 bit 7 是 1,以免我敲錯地址錯誤地操作了寫入。效果:
返回 0x58,ID 正確。
SPI 寫入 BMP280 Register,讀取當前溫度
對 BMP280 的 ctrl_meas 寫入值,連續測量,最高精度,只檢測溫度不管氣壓(地址和值見上一篇),然后讀取氣溫原始數據和溫度補償,再轉換為攝氏 printf 出來,代碼:
#include <stdio.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define DIG_START 0x88
#define TEMP_START 0xFA
#define CTRL_MEAS 0xF4
#define TEMP_ONLY_NORMAL_MODE 0xE3 // 111 000 11
char buf[10];
char buf2[10];
struct spi_ioc_transfer xfer[2];
int spi_init(char filename[40]) {
int file;
__u8 lsb = 0;
__u8 mode = 0;
__u8 bits = 8;
__u32 speed = 10000000;
if ((file = open(filename,O_RDWR)) < 0) {
printf("Failed to open the bus.");
exit(1);
}
if (ioctl(file, SPI_IOC_WR_MODE, &mode)<0) {
perror("can't set spi mode");
return;
}
if (ioctl(file, SPI_IOC_RD_MODE, &mode) < 0) {
perror("SPI rd_mode");
return;
}
if (ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {
perror("SPI rd_lsb_fist");
return;
}
if (ioctl(file, SPI_IOC_WR_BITS_PER_WORD, &bits)<0) {
perror("can't set bits per word");
return;
}
if (ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {
perror("SPI bits_per_word");
return;
}
if (ioctl(file, SPI_IOC_WR_MAX_SPEED_HZ, &speed)<0) {
perror("can't set max speed hz");
return;
}
if (ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {
perror("SPI max_speed_hz");
return;
}
return file;
}
char * spi_read(int addr,int nbytes,int file) {
int status;
memset(buf, 0, sizeof buf);
memset(buf2, 0, sizeof buf2);
buf[0] = addr | 128;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned long) buf2;
xfer[1].len = nbytes;
status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return;
}
return buf2;
}
void spi_write(int addr, char value, int file) {
int status;
memset(buf, 0, sizeof buf);
buf[0] = addr & 127;
buf[1] = value;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 2;
status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
}
}
float myFunc(uint32_t adc_T, unsigned short dig_T1, short dig_T2, short dig_T3){
uint32_t var1, var2;
float T;
var1 = (((double)adc_T)/16384.0-((double)dig_T1)/1024.0)*((double)dig_T2);
var2 = ((((double)adc_T)/131072.0-((double)dig_T1)/8192.0)*(((double)adc_T)/131072.0-((double)dig_T1)/8192.0))*((double)dig_T2);
T = (var1+var2)/5120.0;
return T;
}
int main() {
int i;
char * id;
char * mode;
char dig_buff[6];
char tmp_buff[3];
int file=spi_init("/dev/spidev1.0");
id = spi_read(0xD0,1,file);
printf("ID: 0x%02x\n",*id);
spi_write(CTRL_MEAS,TEMP_ONLY_NORMAL_MODE,file);
mode = spi_read(CTRL_MEAS,1,file);
printf("Mode: 0x%02x\n", *mode);
memcpy(dig_buff, spi_read(DIG_START, 6, file), 6);
memcpy(tmp_buff, spi_read(TEMP_START, 3, file), 3);
printf("Dump:\n");
for (i=0; i<6; i++){
printf("%02x ",dig_buff[i]);
}
printf("\n");
for (i=0; i<3; i++){
printf("%02x ",tmp_buff[i]);
}
printf("\n");
int adc_T = ((tmp_buff[0]<<16)|(tmp_buff[1]<<8)|(tmp_buff[2]))>>4;
unsigned short dig_T1 = (dig_buff[1]<<8)|(dig_buff[0]);
short dig_T2 = (dig_buff[3]<<8)|(dig_buff[2]);
short dig_T3 = (dig_buff[5]<<8)|(dig_buff[4]);
printf("adc_T is : %d \n", adc_T);
printf("dig_T1 is : %d \n", dig_T1);
printf("dig_T2 is : %d \n", dig_T2);
printf("dig_T3 is : %d \n", dig_T3);
printf("Temperature is : %f \n", myFunc(adc_T, dig_T1, dig_T2, dig_T3));
return 0;
}
效果:
關於溫度補償和實際攝氏溫度計算,請看上一篇 I2C 示范。spi_write 里面的 bitwise AND 是為了確保 bit 7 是零,寫入模式。由於我全部用相同的 buf 和 buf2,實際應用 read 時候需要 copy 出來。我還沒很好地理解 linux 的 spidev 庫(比如讀取時兩個 xfer buffer 是否需要大小一致,片選結束拉低的時間點是取決於 xfer[0] 長度還是 xfer[0] 和 xfer[1] 兩者較長的值,初始化后 SPI 參數能否修改,user space 能否改 mode 等等的問題),但以上代碼勉強夠用了,將來有空再慢慢研究。將來的項目里不是 embedded linux 的。
我糾結於嘗試用 Python 做 SPI 雙工浪費了很多時間,最終還是沒結果做不出來。最后放棄那一層層的封裝,直接用 C 的 spidev,頓時春天了,28°C。
下一篇,用 SPI 調用加速傳感和傾斜角。







![117(7$25]QU)IZ5VCSI7]LC 117(7$25]QU)IZ5VCSI7]LC](/image/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTEyODkzLzIwMTYwMy8xMTI4OTMtMjAxNjAzMDkwMTEzNTM4ODItNDU3MDEyNTYzLnBuZw==.png)

