树莓派4B Sense HAT (B) 使用 Ctypes 来实现 传感器间 C 到 Python 的数值传递

本文将记录使用 C 语言编译 .so 库来实现传感器 Sense HAT (B) 的数据传递

环境

  • Raspbian buster
  • GCC 8.3.0 (Raspbian 8.3.0-6+rpi1)

Sense HAT (B) 传感器

  • 板载Raspberry Pi 40pin GPIO接口,适用于Raspberry Pi系列主板
  • 板载ICM20948(3轴加速度、3轴陀螺仪和3轴磁力计),可检测运动姿态、方位和磁场
  • 板载SHTC3数字温湿度传感器,可感知环境的温度和湿度
  • 板载LPS22HB大气压强传感器,可感知环境的大气压强
  • 板载TCS34725颜色识别传感器,可识别周围物体的颜色
  • 板载ADS1015芯片,4通道12位精度ADC,可扩展AD功能以便接入更多传感器
  • 引出I2C控制接口,方便接入STM32等主控板
  • 提供完善的配套资料手册(Raspberry/STM32等示例程序)

微雪地址官网地址

在查阅了官网给出的文档以后,发现SHTC3传感器并没有 Python 的示例程序,这对于类似于魔镜等用途功能来说十分局限。因此需要将 C 语言获取到的传感器信息传递给 Python 等其他语言,在参考了 Boost (编译失败) 以及 Python C API (dynamic module does not define module export function) 后决定调用 .so 库来进行数据传输

开始

官网给出的SHTC3的示例程序如下

SHTC3.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef _SHTC3_H
#define _SHTC3_H

//i2c address
#define SHTC3_I2C_ADDRESS 0x70

//Commands
#define SHTC3_WakeUp 0x3517
#define SHTC3_Sleep 0xB098
#define SHTC3_NM_CE_ReadTH 0x7CA2
#define SHTC3_NM_CE_ReadRH 0x5C24
#define SHTC3_NM_CD_ReadTH 0x7866
#define SHTC3_NM_CD_ReadRH 0x58E0
#define SHTC3_LM_CE_ReadTH 0x6458
#define SHTC3_LM_CE_ReadRH 0x44DE
#define SHTC3_LM_CD_ReadTH 0x609C
#define SHTC3_LM_CD_ReadRH 0x401A
#define SHTC3_Software_RES 0x401A
#define SHTC3_ID 0xEFC8

#define CRC_POLYNOMIAL 0x131 // P(x) = x^8 + x^5 + x^4 + 1 = 100110001

#endif

SHTC3.c

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
#include <bcm2835.h>
#include <stdio.h>
#include <math.h>
#include"SHTC3.h"

float TH_Value, RH_Value;
char checksum;

char SHTC3_CheckCrc(char data[], uint8_t len, uint8_t checksum) {
uint8_t bit; // bit mask
uint8_t crc = 0xFF; // calculated checksum
uint8_t byteCtr; // byte counter
// calculates 8-Bit checksum with given polynomial
for (byteCtr = 0; byteCtr < len; byteCtr++) {
crc ^= (data[byteCtr]);
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ CRC_POLYNOMIAL;
} else {
crc = (crc << 1);
}
}
}

// verify checksum
if (crc != checksum) {
return 1; //Error
} else {
return 0; //No error
}
}

uint8_t SHTC3_WriteCommand(unsigned short cmd) {
uint8_t state;
char buf[] = {(cmd >> 8), cmd};
state = bcm2835_i2c_write(buf, 2);
return state; //1:error 0:No error
}

uint8_t SHTC3_WAKEUP() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_WakeUp); // write wake_up command
bcm2835_delayMicroseconds(300); //Delay 300us
bcm2835_i2c_end();
return state;
}

uint8_t SHTC3_SLEEP() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_Sleep); // Write sleep command
bcm2835_i2c_end();
return state;
}

uint8_t SHTC_SOFT_RESET() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_Software_RES); // Write reset command
bcm2835_delayMicroseconds(300); //Delay 300us
bcm2835_i2c_end();
return state;
}

uint8_t SHTC3_Read_DATA() {
uint8_t state;
unsigned short TH_DATA, RH_DATA;
char buf[3];
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(
SHTC3_NM_CD_ReadTH); //Read temperature first,clock streching disabled (polling)
bcm2835_delay(20);

state |= bcm2835_i2c_read(buf, 3);
checksum = buf[2];
state |= SHTC3_CheckCrc(buf, 2, checksum);
if (!state)
TH_DATA = (buf[0] << 8 | buf[1]);

state |= SHTC3_WriteCommand(
SHTC3_NM_CD_ReadRH); //Read temperature first,clock streching disabled (polling)
bcm2835_delay(20);

state |= bcm2835_i2c_read(buf, 3);
checksum = buf[2];
state |= SHTC3_CheckCrc(buf, 2, checksum);
if (!state)
RH_DATA = (buf[0] << 8 | buf[1]);

if (!state) {
TH_Value = 175 * (float) TH_DATA / 65536.0f - 45.0f; //Calculate temperature value
RH_Value = 100 * (float) RH_DATA / 65536.0f; //Calculate humidity value
} else {
printf("\n SHTC3 Sensor Error\n");
}
bcm2835_i2c_end();
return state;
}

int main() {
uint8_t state;
printf("\n SHTC3 Sensor Test Program ...\n");
if (!bcm2835_init()) return 1;
bcm2835_i2c_setSlaveAddress(SHTC3_I2C_ADDRESS);
bcm2835_i2c_set_baudrate(10000);
SHTC_SOFT_RESET();
while (1) {
state = SHTC3_Read_DATA();
state |= SHTC3_SLEEP();
state |= SHTC3_WAKEUP();
if (state)
printf("\n SHTC3 Sensor Error\n");
else
printf("Temperature = %6.2f°C , Humidity = %6.2f%% \r\n", TH_Value, RH_Value);
}
return 0;
}

在阅读了程序源码后, 我们利用

1
2
3
4
5
6
bcm2835_i2c_setSlaveAddress(SHTC3_I2C_ADDRESS);
bcm2835_i2c_set_baudrate(10000);
SHTC_SOFT_RESET();
SHTC3_Read_DATA();
state |= SHTC3_SLEEP();
state |= SHTC3_WAKEUP();

这段代码中的函数即可完成取值

重新编写 so

我们将 int main() 部分重新编写,在初始化传感器后独立两个函数用来取温度和湿度,那么总体完成后的 SHTC3.c 如下

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
#include <bcm2835.h>
#include <stdio.h>
#include <math.h>
#include "SHTC3.h"

float TH_Value, RH_Value;
char checksum;

char SHTC3_CheckCrc(char data[], uint8_t len, uint8_t checksum) {
uint8_t bit; // bit mask
uint8_t crc = 0xFF; // calculated checksum
uint8_t byteCtr; // byte counter
// calculates 8-Bit checksum with given polynomial
for (byteCtr = 0; byteCtr < len; byteCtr++) {
crc ^= (data[byteCtr]);
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ CRC_POLYNOMIAL;
} else {
crc = (crc << 1);
}
}
}

// verify checksum
if (crc != checksum) {
return 1; //Error
} else {
return 0; //No error
}
}

uint8_t SHTC3_WriteCommand(unsigned short cmd) {
uint8_t state;
char buf[] = {(cmd >> 8), cmd};
state = bcm2835_i2c_write(buf, 2);
return state; //1:error 0:No error
}

uint8_t SHTC3_WAKEUP() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_WakeUp); // write wake_up command
bcm2835_delayMicroseconds(300); //Delay 300us
bcm2835_i2c_end();
return state;
}

uint8_t SHTC3_SLEEP() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_Sleep); // Write sleep command
bcm2835_i2c_end();
return state;
}

uint8_t SHTC_SOFT_RESET() {
uint8_t state;
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(SHTC3_Software_RES); // Write reset command
bcm2835_delayMicroseconds(300); //Delay 300us
bcm2835_i2c_end();
return state;
}

uint8_t SHTC3_Read_DATA() {
uint8_t state;
unsigned short TH_DATA, RH_DATA;
char buf[3];
state = !bcm2835_i2c_begin();
state |= SHTC3_WriteCommand(
SHTC3_NM_CD_ReadTH); //Read temperature first,clock streching disabled (polling)
bcm2835_delay(20);

state |= bcm2835_i2c_read(buf, 3);
checksum = buf[2];
state |= SHTC3_CheckCrc(buf, 2, checksum);
if (!state)
TH_DATA = (buf[0] << 8 | buf[1]);

state |= SHTC3_WriteCommand(
SHTC3_NM_CD_ReadRH); //Read temperature first,clock streching disabled (polling)
bcm2835_delay(20);

state |= bcm2835_i2c_read(buf, 3);
checksum = buf[2];
state |= SHTC3_CheckCrc(buf, 2, checksum);
if (!state)
RH_DATA = (buf[0] << 8 | buf[1]);

if (!state) {
TH_Value = 175 * (float) TH_DATA / 65536.0f - 45.0f; //Calculate temperature value
RH_Value = 100 * (float) RH_DATA / 65536.0f; //Calculate humidity value
} else {
printf("\n SHTC3 Sensor Error\n");
}
bcm2835_i2c_end();
return state;
}

float get_temperature() {
uint8_t state;
state = SHTC3_Read_DATA();
state |= SHTC3_SLEEP();
state |= SHTC3_WAKEUP();
if (state)
return 0.0;
else
return TH_Value;
}

int init() {
if (!bcm2835_init()) return 1;
bcm2835_i2c_setSlaveAddress(SHTC3_I2C_ADDRESS);
bcm2835_i2c_set_baudrate(10000);
SHTC_SOFT_RESET();
return 0;
}

float get_humidity() {
uint8_t state;
state = SHTC3_Read_DATA();
state |= SHTC3_SLEEP();
state |= SHTC3_WAKEUP();
if (state)
return 0.0;
else
return RH_Value;
}

编译

在完成了上面的步骤后,接下来我们需要编译成 so 库,编译前,请确保已经安装了 bcm2835 依赖,随后在SHTC3.hSHTC3.c目录下执行

script
1
gcc -shared -fPIC SHTC3.c -o SHTC3.so -lbcm2835 -lm

完成了以后目录下便会生成一个 SHTC3.so,目录结构为

script
1
2
3
├── SHTC3.c
├── SHTC3.h
└── SHTC3.so

Python 调用获取数据

此后,使用Ctypes模板调用生成的 so 即可完成 C 语言传感器数据与 Python 的传递,在目录下创建 run.py 内容如下

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
import ctypes


class SHTC3:
def __init__(self):
self.dll = ctypes.CDLL("./SHTC3.so")
init = self.dll.init
init.restype = ctypes.c_int
init.argtypes = [ctypes.c_void_p]
init(None)

def get_temperature(self):
temperature = self.dll.get_temperature
temperature.restype = ctypes.c_float
temperature.argtypes = [ctypes.c_void_p]
return temperature(None)

def get_humidity(self):
humidity = self.dll.get_humidity
humidity.restype = ctypes.c_float
humidity.argtypes = [ctypes.c_void_p]
return humidity(None)


if __name__ == "__main__":
SHTC3 = SHTC3()
while True:
print('Temperature = %6.2f°C , Humidity = %6.2f%%' % (SHTC3.get_temperature(), SHTC3.get_humidity()))

然后使用 sudo 运行 run.py (如果不使用 sudo 运行可能会出现 段错误 的报错)

1
sudo python3 run.py

运行截图

本实例的代码已经上传到 GitHub