首先什么是SDI-12协议,SDI-12协议:SDI-12 Serial Digital Interface 即串行数字接口,是一种基于微处理器的智能化传感器系统。 SDI-12通讯标准是由美国水文组织提出的的一种串行数据通讯接口协议,在SDI-12协会支持下,近年来欧美国家在环境监测中加以推广使用。 此技术广泛应用在工农业多参数测控、江河湖海的水文和气象等地球环境监测、养殖和食品生产中,可以远距离传送数据。
硬件上,SDI-12是用一条数据线、一条电源线、一条底线组成,数据线采用高低电平来表示数据的变化。我这里使用OpenWrt系统,单GPIO来作为数据线。
先看一下串行数据的逻辑和电压值
状态 | 位状态 | 电压范围 |
传号 | 1 | -0.5~1.0v |
空号 | 0 | 3.5~5.5v |
跃变 | 未定义 | 1.0~3.5v |
通信过程:
- 发送中断信号让数据线变为空号状态,持续12ms。
- 发送8.33ms的传号。
- 发送数据
- 数据发送完成在7.5ms内释放数据线
- 在8.33ms后获取数据
大体通信时序就是这样的一个过程,这里理解了,再看一下数据内容。SDI-12的高低信号和电平是相反的,SDI-12中的1表示为低电平。SDI-12上的数据都是可打印字符。
SDI-12一段数据位10位,第1位为起始位(0),后7位为数据位,第9位为校验位,第10位为停止位(1),这里要注意,举个例子发送字符0,字符0的二进制表示为00110000,所以发送的电平信号为0 1100111 1 1,发送字符2,字符2的二进制表示为00110010,所以发送的电平信号为 0 1100110 1 1,这里要注意,校验位为原始数据中1的个数,奇数个则校验位电平为1。
我这里使用的openwrt,下面是读写部分代码
/*写部分的驱动,因为对时间要求较高,所以这部分用了驱动*/
static ssize_t sdi_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
int i,j = 0;
char databuf[50] = {0};
char data[200] = {0};
unsigned char value = 0;
char vs[2] = {0};
char datacs[200] = {0};
int count = 100;
int check = 0;
unsigned char stat;
struct sdi_dev *dev = filp->private_data;
gpio_direction_output(sdi.sdi_gpio, 1);
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
str_to_bin(databuf, data);
gpio_set_value(dev->sdi_gpio, 1);
mdelay(12);
gpio_set_value(dev->sdi_gpio, 0);
mdelay(8.33);
for(i = 0; i < strlen(data)/8; i++)
{
gpio_set_value(dev->sdi_gpio, 1);
mdelay(0.833);
check = 0;
for(j = 7; j > -1; j--)
{
if(j == 0 && check < 0){
gpio_set_value(dev->sdi_gpio, 0);
mdelay(0.833);
break;
}
if(data[i*8+j] == '1')
{
check = ~check;
gpio_set_value(dev->sdi_gpio, 0);
}
else if(data[i*8+j] == '0')
{
gpio_set_value(dev->sdi_gpio, 1);
}
mdelay(0.833);
}
gpio_set_value(dev->sdi_gpio, 0);
mdelay(0.833);
}
gpio_set_value(dev->sdi_gpio, 0);
mdelay(0.833);
/*切换io方向*/
gpio_direction_input(dev->sdi_gpio);
return 0;
}
/*驱动读取数据部分*/
static ssize_t sdi_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
int sta = 0;
int count = 500;
int times = 4000;
char end[] = "110001100101000010";
char value_ch[2] = {0};
char data[600] = {0};
/*io值定义*/
unsigned char value = 0;
unsigned char value_last = 0;
struct sdi_dev *dev = filp->private_data;
//5毫秒过后等待数据
mdelay(5);
value = gpio_get_value(dev->sdi_gpio);
while(times)
{
//记录电平变化
value_last = value;
value = gpio_get_value(dev->sdi_gpio);
//判断是否为起始位
if(value != value_last && value == 1){
break;
}
mdelay(0.833);
times--;
}
if(times == 0)
{
//超时未读到数据
return -EFAULT;
}
//读取数据,写入数组
while(count)
{
value = gpio_get_value(dev->sdi_gpio);
sprintf(value_ch, "%d", value == 1 ? 0 : 1);
strcat(data, value_ch);
mdelay(0.833);
if(end[sta] == value_ch[0])
{
sta++;
}
else
{
sta = 0;
}
if(sta == strlen(end))
{
break;
}
count--;
}
if(strlen(data) != 500){
//返回数据,去掉非数据部分
copy_to_user(buf, data, strlen(data)-strlen(end)-2);
}
return 0;
}
/*应用程序读写部分*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename = "/dev/sdi";
char data[600] = {0};
char ascstr[100] = {0};
char handle_data[10] = {0};
char u8[10] = {0};
char cs[10] = {0};
int count = 200;
int ret = 0;
int flag = 0;
int again_count = 2;
// int count1 = 0;
int check = 0;
int k = 0;
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
/* 打开 sdi 驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
again:
again_count--;
printf("\r \r");
if(again_count < 0){
printf("\rFailed to get data!\n");
return 0;
}
/* 向/dev/sdi 文件写入数据 */
retvalue = write(fd, argv[1], strlen(argv[1]));
ret = read(fd, data, strlen(data));
if(ret < 0){
// printf("Read failed, sleep for one second, try again\r\n");
sleep(2);
goto again;
}
if(strlen(data) > 0)
{
for(int len = 0; len < floor(strlen(data)/10); len++){
//起始位判断
if(strncmp("0",data+len*10,1) == 0)
{
//停止位判断
if(strncmp("1",data+len*10+9,1) == 0){
check = 0;
strncpy(u8, data+len*10+1, 8);
for(int j = 0; j < strlen(u8); j++)
{
handle_data[j] = u8[strlen(u8)-j-1];
if(handle_data[j] == '1'&& j != 0){
check = ~check;
}
}
if(check < 0){
handle_data[0] = '0';
}
//00001101疑是结束标志
if((strcmp("00001101", handle_data) == 0)){
printf(" ");
continue;
}
BinToChar(ascstr,handle_data, 8);
printf("%s",ascstr);
}
else{
if(len < floor(strlen(data)/10) -2){
sleep(2);
goto again;
}
}
}else{
if(len < floor(strlen(data)/10) -2){
sleep(2);
goto again;
}
}
}
}
else
{
sleep(2);
goto again;
}
printf("\r\n");
/* 关闭文件 */
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
代码部分仅供参考,重要的是思路以及理解SDI-12协议,明白数据组成和通信时序,具体的代码需要根据自己的设备修改。
SDI-12协议链接:SDI-12协议 V14.pdf-原创力文档 (book118.com)