태양광 발전

자작 2014. 1. 4. 15:36

무려 1년이 걸린 '태양광 발전 프로젝트'에 관한 노트. 원래는 뭔가 좀 그럴듯하게 PT 자료를 만들 생각이었으나... 그렇게 한가한 편이 아니다.


1년에 하나씩 프로젝트를 해 보자, 해서, 2012년에는 수경재배, 2013년에는 태양광 발전을 시도했다. 두 프로젝트 모두 마누라에게 큰 호응은 얻지 못했다. 할 말은 좀 있는게, 마누라의 주장은 수경재배가 전기요금이나 소비하는 쓸데없는 짓이라고 생각하지만 아내가 사용하는 헤어 드라이어 여덟 번 정도의 전기 밖에 사용하지 않았다. 돈이 안 든다. 물은? 1년 동안 소비한 물의 양은 샤워 여섯 번 할 정도의 분량이다. 제 남편이 미련한 바보인 줄 안다.


수경재배의 경우 소규모로 하다보니 수확량이 많지 않아 일 년 농사를 지어보니 먹을게 별로 없었다. 2012년에는 진딧물 퇴치로 고생하고(갖은 약이 소용이 없다가 무당 벌레 두 마리 잡아 풀어놓으니 해결되었지만 이미 늦었다) 2013년에는 곰팡이로 키우던 식물이 전멸했다. 두 해 동안 일조량과 일사량 때문에 실내에서 식물 재배가 생각만큼 쉽지 않다는 것을 깨닫게 되었고, LED 광원은 전기를 일정 이상 사용하지 않는 한 생각보다 쓸모가 없다는 것도 알게 되었다. LED 전등은 광속이 낮아 사실 형광등에 비해 별 매릿이 없다. 우리 집 기준으로 계산해 보니 LED 조명으로 교체할 때 드는 비용으로 본전을 뽑으려면 약 12년이 걸렸다. 수경 재배를 통해 참 많은 것을 배웠고, 다음에는 노지 재배를 시도해 볼까 했는데, 노지 재배는 수경재배보다 관리가 까다롭고 손이 많이 가서 망설여진다. 내가 주말에 그렇게 시간이 많은 사람도 아니고 물 보충해 주고 가끔 양액만 보충해 주는 수경재배가 수많은 장점을 지녔다.



이런 풀떼기를 잔뜩 재배했고...



과천 과학관에서 얻은 보리도 키워보고... 집에서 보리를 수경재배해 본 사람 있나? 없을껄? 그런데 내가 한 3개월 놀러가는 바람에 양액 공급이 안되어 쭉정이만 자랐다.



배추도 발아부터 시작해서 몇 포기 수확해 김치를 만들어 먹었다. 거듭 강조하지만 수확량이 보잘 것 없었다. 블로그에 재배해서 뭐 해먹는다는 사람들 말을 대체로 믿지 않는다. 풀떼기 재배해 봤자 서너 번 먹으면 끝인데 농사는 3개월 지어야 한다. 재래 시장에서 천원 짜리 모듬 야채 사먹는게 싸게 먹힌다.


하지만 수경재배는 계속할 것이다.


수경재배 경험을 바탕으로 수경재배 설비에서 소비하는 전기를 생산해 보자는 차원에서 태양광 발전을 기획했다. 때마침 (2012.12월) 와트당 1$ 가량 하는 70W 짜리 태양광 패널을 구할 기회가 생겼다. 패녈은 2개월만에 도착했지만 여하한 사정으로 설치와 실험은 9월로 밀렸다.



패널 설치는 9월에 했다. 




패널 거치용 앵글을 구매하려고 알아보다가 에어컨 실외기 거치대를 오픈마켓에서 구입하여 개조했다. 이 편이 비용도 적게 들고 노가다가 적었다.





별 일 없으면 계산대로,



태양전지에 관해 이런 저런 공부를 틈틈이 하고,



계절에 따른 발전 효율 예측을 했다. 



어딘가 동호회에서 공동구매한 buck converter의 변환 효율 측정을 하고, 



그것을 바탕으로, 발전 효율과 소비 전력량을 산정했다.



아울러 배터리의 데이터시트를 참조해 방전 심도(Depth of Discharge)를 고려하여,



DOD를 대략 40%(충방전 1000회 = 3년)에 맞춰 Deep cycle 배터리 용량을 40A로 계산했다. 40A 짜리 배터리를 구매해서 배송 받은 것을 보니 50A 짜리여서 흐뭇했다. 한 4년은 쓸 수 있겠다.


아래 표는 태양광 발전에 필요한 물품 구매 목록이다. 추가 지출이 있었고 왜 구매했나 후회되는 물건도 있었다. 



eBay에서 MPPT를 구매했다가 2개월이 지나도 물건이 오지 않아 클레임을 걸고 길고 귀찮은 debate 후에 전액 환불 받고 aliexpress에서 다시 구입했다. 그것은 2주 만에 도착했다.


MPPT 모듈을 기다리는 동안 나는 나대로 몇 가지 실험을 진행했다. 예를 들어 배터리 충전 효율이나 실제 부하량을 측정하기 위해서는 MPPT 외에도 몇 가지 추가 회로가 필요했다. 마침 회사에서 굴러다니던 Beagle Bone Black Board를 사용하여 칩 메이커에서 샘플 주문한 칩을 사용하여 회로를 구성하고 실험했다.



MPPT가 먼저 도착해 태양 전지 패널과 MPPT, 배터리만 연결한 상태로 간단한 회로를 구성해 휴대폰 등을 충전했다. USB cable 어셈블리를 사다가 USB DCP 규격대로 만드니 충전 안 되는 휴대폰이나 타블렛은 없었다. USB 스펙을 만든 녀석들은 워낙 늑장을 잘 부리다가 fire wire를 비롯한 몇몇 케이블 규격의 맹 추격에 제정신을 차린 것 같았다. bluetooth SIG도 마찬가지였다. 4.0 규격에 이르러서야 그제서야 쓸만해졌다.


저 회로는 테스트 끝나고 일 때문에 바빠 한 동안 방치했다가 집에서 설치 중에 회로를 실수로 잘못 연결해 다 태워먹었다. 망연자실.


간단한 회로라서 기억에 의존해 만들다보니 기판 뒷면은 점퍼선 투성이고 고치려고 보니 한심한 생각이 들어 당장 PC에 eagle CAD를 설치하고 회로도를 그렸다. PCB도 만들었다. 중국의 샘플 PCB 제작 업체를 통해 배송료 포함 $29 짜리 PCB를 만들었다. 진작에 이렇게 할 껄 그랬다. 오랫만에 eagle cad를 다시 학습하고(2시간) 회로도 그리고(2시간) PCB 만들어서(3시간) 5매의 샘플 PCB를 받기까지 (2주) 15일이 걸렸다. 2시간 동안 납땜하고 30분 정도 테스트했다. 회로가 간단하니 그리 쉬웠다.



PCB를 기다리는 동안 코딩을 했다. Beagle Bone Black 보드가 끝내 주는 점이 $45짜리 치고 상당한 퍼포먼스가 나와서 크로스 툴 체인 없이 넉넉한 eMMC와 SD 메모리에 ssh, gcc, samba를 설치해서 PC에서 소스를 에디팅하고(요즘은 sublime을 주로 쓰게 되더라) 보드에서 바로 컴파일해서 돌려볼 수 있다는 점. 처음에는 nod 따위를 사용하려고 했으나 gpio 성능을 내기 위해서는 nod용 c interface를 만들어야 하고 뭐하러 wear leveling 걱정하게 eMMC나 SD를 스토리지로 사용하고, 부하가 많이 걸리는 웹 서비스와 sql 서버 따위를 임베디드 보드에서 실행하나 회의가 들어, 클라우드 서버에서 돌고 있는 내 도메인에 mysql 최신 버전과 php, 그리고 javascript 등으로 간단한 서비스를 구축했다.


그래서 BBB 보드에서는 샘플링된 데이터를 wget 등의 외부 유틸리티를 사용해 웹 서버에 데이터를 업로드하고, php는 그것을 mysql 서버에 저장하고 웹으로 서비스하게 되었다.


BBB Board에 USB WLAN을 달아서 wifi로 데이터를 전송하려고 했는데, BBB 보드의 커널이 3.8로 업그레이드 되면서 특정 USB WLAN 동글(8192CU)이 작동하지 않았다. 커널 빌드부터 디바이스 드라이버 빌드를 하다가 시간 낭비만 했다. 


그 와중에도 리빙박스 속의 태양광 발전 시스템은 훌륭한 충전 스테이션 역할을 해줬다. 마누라 휴대폰을 제외하고 집안의 모든 휴대용 기기는 이걸로 충전했다.



수경재배용 전력 생산은 미뤘다. 아직 데이터가 축적되지 않아 얼마만한 발전과 로드 용량이 필요한 지 판단이 안 선다. 리빙 박스 안에 NAS도 넣을까 생각했다. 발전량 평가 후 생각해보기로.


코딩 중 발견한 문제는, 태양전지 패널의 발전 효율 계산에 필요한 몇 가지 파라미터, 예를 들자면 운량(cloud cover), 이슬점, 적설량, 기온 등의 정확한 정보를 어디서 구할 데가 없다는 점. 기상청 관측 자료는 아무 쓸모가 없었고 외국 업체에서 얻은 정보는 한국의 공군기지에 설치된 관측 스테이션의 데이터로는 실제 기상과 차이가 있었다. 


살고 있는 도시를 좀 더 자세히 들여다보면 기상 관측 스테이션은 곳곳에서 발견된다. 하지만 한국의 기상청은 이것들을 공개하지 않았다. 그렇다고 기상청에서 실측 데이터를 실시간 제공하지도 않았다. 나도 모르게 기상청을 위시하여 한국의 공공 정보 서비스 수준에 욕이 절로 흘러 나왔다. 뭘 해보려면 걸리적 거리는 한국 정부의 영혼없는 짓거리나 얼어죽을 IT 강국 따위의 공허한 구호들 따위의 등신스러움... 내가 사는 곳의 정보를 오산 미공군기지에서 얻어야 하는데 그게 맞을 리가 있나? http://www.netatmo.com 의 weather station을 사자니 배보다 배꼽이 더 크고... (그러고 보니 몇 년 전에 기상청에 분개한 나머지 나라도 뭐 하나 만들어서 세계 기상 네트웍에 참여하자, 이왕 하는 김에 사람들도 끌어모으고, 뭐 그런 글로발스런 생각을 했던 기억이 남)


어쨌거나 태양광 발전 설비는 실사용 목적 보다는 실험 목적이 강하다. mysql 5.0부터 추가된 event 기능으로(이게 주요 코딩의 전부?) 적산 전력량을 산출하고 그것을 그래프로 표현하는 등의 코딩은 사흘 쯤 걸렸다. 이때가 딱 크리스마스였다. 프로그램은 아직 부족한 것이 많지만 일단은 돌아가고 단순 자료 수집만 하는 중.


http://pyroshot.pe.kr/pm/




통계를 보니 12/25부터 1/4까지 912Wh 발전했다. 마누라가 청소하다가 공유기 전원 플러그를 무심결에 빼버린 바람에 엊그제 발전 로그가 비었다. -_-;


개선할 점

  • 고생해서 기껏 산 MPPT 컨버터가 가짜 같다. 어째 싸더라.
  • 조도 측정 센서에 방수 대책 세워서 설치
  • 온도 계측에 관한 보다 나은 대안
  • Beagle Bone Black 보드 대신 Cortex-M3로 재설계
  • 웹 서비스 개선: 기온, 운량, 조도에 따른 발전효율 평가 (그래프로 실시간 비주얼라이즈), 월간 발전량 리포트 추가


,

Beaglebone Black 보드에서 LM92TMP513 칩을 테스트해 보았다. 이전 센서와는 달리 칩 형태라 납땜 하기 힘들어서 예전에 엘레파츠에서 구입한 SOIC 변환 보드를 사용했다. LM92는 몇 핀 안되어 간단히 테스트 해 볼 수 있지만 TMP513은 배선이 조금 있는 편이고 breadboard에서 하기는 좀 어려운 편.


칩 수급은 LM92와 TMP513은 Texas Instruments에서 샘플 오더한 것을 사용했다. TI는 샘플 오더하면 주문한 칩당 3개씩을 DHL로 배송해 주는데 3-4 business days면 도착. 요새 뭘 돈 주고 산 기억이 없다, 집이나 사무실에 굴러다니는 부속을 긁어모아서... -_-;


LM92는 단순해서 HT11과 마찬가지로 전원과 I2C만 연결하면 되고, TMP513은 내부 온도 센서 외에도 외부에 PNP TR을 다이오드처럼 사용하여 온도 센서로 사용하기에 이글 캐드로 연결 관계를 간단한 회로로 그렸다. 데이터시트에 따르면 TR은 hfe가 50~150인 아무거나 사용하면 될 것 같다(추천하는 것은 자기들이 검증한 2N3904지만).

TMP513은 온도 계측 외에 INA219 칩처럼 current와 bus voltage sensing이 가능해서 태양광 모듈 회로 설계에 넣어 예전에 열심히 테스트 한 적이 있다. INA219나 TMP513 같은 칩들은 전력 회로에서 analog 파트 계측을 매우 간단하게 구현 가능한 참, 좋은 칩들이다.


오늘 목표는 TMP513 및 HT11 칩의 온도 정밀도가 원하는 수준이 나오는 가를 LM92 칩 기준으로 검측하는 것이 목표. 하여튼 회로는 참고용이고, SOIC 변환 보드에 두 칩을 대충 납땜한 사진이 아래.



I2C가 편해서 소스 작성하는데 대충 30분이면 뚝딱. 취미 생활 중 일부는 업무에 활용.


lm92.c source

#include <stdio.h>

#include <unistd.h>

#include <time.h>

#include <sys/ioctl.h>

#include <linux/i2c-dev.h>

#include <fcntl.h>


#define I2C_DEV "/dev/i2c-1" // device driver name for linux i2c interface

#define LM92_ADDR 0x48


// INA219 register address

#define LM92_REG_READ 0x00

#define LM92_REG_CONFIG 0x01

#define LM92_CFG_FQUE (1<<4)

#define LM92_CFG_INTP (1<<3)

#define LM92_CFG_CRITP (1<<2)

#define LM92_CFG_INTMODE (1<<1)

#define LM92_CFG_SHUTDOWN (1<<0)

#define LM92_REG_CRIT 0x02

#define LM92_REG_TLOW 0x03

#define LM92_REG_THIGH 0x04

#define LM92_REG_MFGID 0x05


int lm92_measure()

{

static int fd = -1;

unsigned char buf[10];

int t;

if (fd == -1) {

if ((fd = open(I2C_DEV,O_RDWR)) < 0) {

printf("LM92: open error\n");

return 0;

}

if (ioctl(fd, I2C_SLAVE, LM92_ADDR) < 0) {

printf("LM92: can't set address\n");

return 0;

}

}


buf[0] = LM92_REG_READ;

if (write(fd, buf, 1) != 1) {

printf("LM92: write error\n");

return -9999;

}

if (read(fd, buf, 2) != 2) {

printf("LM92: read error\n");

return -9999;

}

int v = ((buf[0] << 8) | buf[1]) >> 3;

if ((v & 0x1000) != 0) 

t = (0x1fff-v) * -625;

else

t = v * 625;


return t;

}


#if 0

int main(int argc, char* argv[])

{

time_t st = time(NULL);

while(1) {

if (time(NULL) == st) {

usleep(1000);

continue;

}

st = time(NULL);

printf("t=%6.2f\n", lm92_measure()/10000.0);

}

return 0;

}

#endif



tmp513.c source


source에서 주의 사항

  • power measure 때문에 몇 가지 셋업이 추가되었는데, 굳이 제거하지 않았다. 파워 계측할 때는 션트 저항(R_SHUNT) 및 계측하고자 하는 전압의 예상 최대 값(TMP513_VMAX) , 예상 최대 전류(TMP513_AMAX)를 설정해 주면 그에 맞는 파라미터를 설정해 준다.
  • TMP513_VOFS 및 TMP513_AOFS는 계측 장비로 전압 및 전류를 측정한 후 캘리브레이션에 사용하는 값으로 처음에는 0을 지정해 주고, 계측 장비와의 오차만큼을 지정하면 보상하는 형태. 
  • 마찬가지로 TMP513_TEMP_* 시리즈의 define 역시 LM92와의 온도 오차를 측정한 후 측정치를 보상해 주기 위해 사용했다.
  • TMP513을 초기화 하기 위해, tmp513_measure() 함수에서 configuration value를 hard coding 했다. 회로도처럼 내부 온도 센서 및 외부의 두 개 온도 센서를 계측하기 위해 cfg 값의 몇몇 비트가 enable 되어 있다.


#include <errno.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <linux/i2c-dev.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <time.h>


// circuit & measure constant


#define R_SHUNT (0.01) // Rs


#define TMP513_VMAX (15.0)

#define TMP513_AMAX (8.0)

#define TMP513_MAX_TEMP 4


#define TMP513_CAL ((0x7FFF * 4.096) / TMP513_VMAX)


// calibrated values

#define TMP513_VOFS (-0.0)

#define TMP513_AOFS (-0.0)


#define TMP513_TEMP_L_OFS (1.31353)

#define TMP513_TEMP_R1_OFS (1.31353)

#define TMP513_TEMP_R2_OFS (1.31353)

#define TMP513_TEMP_R3_OFS (0.0)


// i2c devices


#define I2C_DEV "/dev/i2c-1" // device driver name

#define TMP513_ADDR 0x5D // TMP513 default address


#define TMP513_REG_CONFIG 0x00

#define TMP513_CONFIG_MODE 0x7 // shunt and bus, continuous

#define TMP513_CONFIG_SADC 0xf // 128 samples for current

#define TMP513_CONFIG_BADC 0xf // 128 samples for bus voltage

#define TMP513_CONFIG_BRNG 0x1 // 32V FSR

#define TMP513_REG_CONFIG2 0x01

#define TMP513_REG_STATUS 0x02

#define TMP513_REG_VOLT 0x05

#define TMP513_REG_CURRENT 0x07

#define TMP513_REG_TEMP_L 0x08

#define TMP513_REG_TEMP_R1 0x09

#define TMP513_REG_TEMP_R2 0x0A

#define TMP513_REG_TEMP_R3 0x0B

#define TMP513_REG_CAL 0x15

#define TMP513_REG_NF1 0x16

#define TMP513_REG_NF2 0x17

#define TMP513_REG_NF3 0x18


// generic register functions for INA219, TMP513


static int rd_reg(int fd, int rn)

{

unsigned char buf[10] = { 0 };

buf[0] = rn;

if (write(fd, buf, 1) != 1) {

printf("write error: %s\n", strerror(errno));

return -1;

}


if (read(fd, buf, 2) != 2) {

printf("read error: %s\n", strerror(errno));

return -1;

}

return ((buf[0] << 8) | buf[1]) & 0xffff;

}

static int wr_reg(int fd, int rn, int v)

{

unsigned char buf[10] = { 0 };


buf[0] = rn;

buf[1] = (v >> 8) & 0xff;

buf[2] = v & 0xff;

if (write(fd, buf, 3) != 3) {

printf("write error: %s\n", strerror(errno));

return -1;

}

return 1;

}


// get program gain for INA219, TMP513


int get_pg(double Vrs)

{

if (Vrs < 0.040) 

return 0;

else if (Vrs < 0.080) 

return 1;

else if (Vrs < 0.160) 

return 2;

else if (Vrs < 0.320)

return 3;

else {

printf("setup: out of range. check R_SHUNT, V_MAX & A_MAX\n");

return 3;

}

}


int tmp513_measure(double* t, double *v, double *a)

{

static int fd = -1;

static int t_reg[TMP513_MAX_TEMP] = { TMP513_REG_TEMP_L, TMP513_REG_TEMP_R1, TMP513_REG_TEMP_R2, -1 };

static double t_ofs[TMP513_MAX_TEMP] = { TMP513_TEMP_L_OFS, TMP513_TEMP_R1_OFS, TMP513_TEMP_R1_OFS };

int temp, i, iv, ia;

if (fd == -1) {

if ((fd = open(I2C_DEV,O_RDWR)) < 0) {

printf("TMP513: open error\n");

return 0;

}

if (ioctl(fd, I2C_SLAVE, TMP513_ADDR) < 0) {

printf("TMP513: set address failed\n");

return 0;

}


int cfg;

double RL = TMP513_VMAX / TMP513_AMAX;

double Vrs = (R_SHUNT / (R_SHUNT + RL)) * TMP513_VMAX;

printf("TMP513: Vmax=%.2f, Amax=%.2f, Rl=%.2f, Rs=%.3f ohm, Vrs=%.3fV, PG=%d\n", TMP513_VMAX, TMP513_AMAX, RL, R_SHUNT, Vrs, get_pg(Vrs));

cfg = (TMP513_CONFIG_BRNG << 13) | (get_pg(Vrs) << 11) | (TMP513_CONFIG_BADC << 7) | (TMP513_CONFIG_SADC << 3) | TMP513_CONFIG_MODE;

wr_reg(fd, TMP513_REG_CONFIG, cfg);

wr_reg(fd, TMP513_REG_CAL, (int)TMP513_CAL);

cfg = (1 << 15) | (0 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (0x7 << 7);

// 15: continous conversion

// 14: remote channel 3 enable

// 13: remote channel 2 enable

// 12: remote channel 1 enable

// 11: local temp enable

// 10: resistance correction

// 9:7: conversion rate. 0=0.0625, 1=0.123, 2=0.25, 3=0.5, 4=1, 5=2, 6=4, 7=8

wr_reg(fd, TMP513_REG_CONFIG2, cfg);

wr_reg(fd, TMP513_REG_NF2, 0<<8);

printf("CONFIG: %04X (%d)\n", cfg, cfg);

printf("CAL   : %04X (%d)\n", (int)TMP513_CAL, (int)TMP513_CAL);

printf("\n");

}

iv = rd_reg(fd, TMP513_REG_VOLT);

if (iv == -1) 

printf("TMP513: voltage read error\n");

else 

*v = (iv >>3) * 0.004 + TMP513_VOFS;

ia = rd_reg(fd, TMP513_REG_CURRENT);

if (ia == -1) 

printf("TMP513: current read error\n");

else

*a =  ia / (TMP513_CAL / 4.096) + TMP513_AOFS;


for (i = 0; t_reg[i] != -1; i++) {

temp = rd_reg(fd, t_reg[i]);

if (temp == -1) {

printf("TMP513: temp%d read error\n", i);

continue;

}

if ((temp & 2) != 0) 

printf("temp%d: PVLD error\n", i);

if ((temp & 1) != 0)

printf("temp%d: diode open error\n", i);


double m = 0.0625;

if ((temp & 0x8000) != 0) {

temp = (~temp + 1) & 0x7fff;

m = -m;

}


t[i] = (temp >> 3) * m + t_ofs[i];


}


return 1;

}


#if 0

int main(int argc, char* argv[])

{

time_t st = time(NULL);

double temp[TMP513_MAX_TEMP];

double load_v, load_a;

while(1) {


if (time(NULL) == st) {

usleep(1000);

continue;

}

st = time(NULL);


tmp513_measure(temp, &load_v, &load_a);

printf("%.2f %.2f\n", temp[0], temp[1], temp[2]);

}

return 1;

}

#endif



HT11, LM92, TMP513의 온도 계측치를 logging 했다 (TMP513은 calibration을 한 값이 적용된 상태)


2013-12-17 21:29:28,1,0.43,0.10,34,25,25.56,25.56,25.31,25.25

2013-12-17 21:29:29,0,0.43,0.10,34,25,25.56,25.56,25.25,25.06

2013-12-17 21:29:30,2,0.43,0.10,34,25,25.62,25.56,25.25,25.25

2013-12-17 21:29:31,2,0.43,0.10,34,25,25.56,25.56,25.31,25.25

2013-12-17 21:29:32,2,0.43,0.10,34,25,25.62,25.56,25.44,24.94

2013-12-17 21:29:33,2,0.44,0.10,34,25,25.62,25.56,25.44,25.31

...

2013-12-17 21:30:01,2,0.43,0.10,34,25,25.62,25.56,25.31,25.06

2013-12-17 21:30:02,1,0.43,0.10,34,25,25.56,25.56,25.25,25.13

2013-12-17 21:30:03,2,0.43,0.10,34,25,25.62,25.56,25.19,25.38

2013-12-17 21:30:04,1,0.43,0.10,34,25,25.62,25.56,25.25,25.31

2013-12-17 21:30:05,1,0.43,0.10,34,25,25.62,25.56,25.25,25.06


측정 결과의 각 컬럼은 datetime, HT11 error, HT11 bit transfer error rate, HT11 checksum error, HT11 humidity, HT11 temp, LM92 temp, TMP513 local sensor temp., TMP513 ch#1 temp., TMP513 ch#2 temp. 순서. 정리하면,

  • HT11의 온도 값은 스펙에 의하면 ±1℃로 LM92와 대체로 일치.
  • TMP513의 내부 센서 역시 온도 오차가 ±1℃인데 calibration value를 적용하니 비교적 정밀
  • TMP513의 두 외부 센서는 약간의 오차가 보임
25℃ 에서는 LM92 대비 두 센서의 온도 정밀도가 매우 실용적인 수준이나, -40~100℃에서는 온도 드리프트가 꽤 있을 것으로 짐작됨. 하지만 다양한 환경에서 테스트를 해 볼 수 없는 형편이다. 

지금부터 내일 아침까지 대략 10시간 동안 logging을 해서 온도 변화에 따른 계측치가 선형으로 나오는 가를 검사할 것이다.

2013-12-18 30초당 한 번씩 logging을 해서 10시간 동안 모은 데이터로 그래프를 그렸다.





,

이전 글에서 처럼 TSL2561 조도 센서를 테스트 했다. 직원에게 얻었기에 가격은 모르겠고,  adafru.it에서 구매한 것 같다.


사진의 우하단에 있는 모듈로 T-package type. 




TSL2561 모듈에서 BBB 보드로 연결:

  • Pin 1(GND) --> P9.1 (GND)
  • Pin 2(ADDR) --> open
  • Pin 4(SCL) --> P9.19 (I2C2_SCL) with pull-up 10Kohm
  • Pin 5(SDA) --> P9.20(I2C2_SDA) with pull-up 10Kohm
  • Pin 6(VCC) --> P9.3 (3.3V)


결선 후 칩이 정상 작동하는지 확인하는 과정


# apt-get install i2c-tools 

...

# i2cdetect -y -r 1

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f

00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 

10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- -- 

40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- -- 

60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

70: -- -- -- -- -- -- -- --                         


i2cdetect로 확인한 결과 0x39에 장치가 발견되었다. TSL2561 datasheet를 확인해 보니 slave address가 일치한다.


TSL2561 datasheet에는 조도 계산하는 소스가 포함되어 있다. 그것을 참조로 프로그래밍.


실행결과

# lux

2013-12-13 16:17:46 lux=298

2013-12-13 16:17:47 lux=296

2013-12-13 16:17:48 lux=298

2013-12-13 16:17:49 lux=297

2013-12-13 16:17:50 lux=297

2013-12-13 16:17:51 lux=57

2013-12-13 16:17:52 lux=38

2013-12-13 16:17:53 lux=36

2013-12-13 16:17:54 lux=39

2013-12-13 16:17:55 lux=288

2013-12-13 16:17:56 lux=299

2013-12-13 16:17:57 lux=297

2013-12-13 16:17:58 lux=297

^C


중간에 조도가 바뀌는 부분은 손바닥으로 센서를 가린 것이다. 반도체의 제조 공정에 따른 칩의 편차는 크지 않을 것으로 보이며, 이 조도 값은 믿을만 해 보인다.


아래는 작업한 소스


Makefile

.SILENT: 


CC = gcc

STRIP = strip

INCLUDES = 

LIBS = 

CFLAGS = -Wall -O2 

LFLAGS = 


COMPILE = $(CC) $(INCLUDES) $(CFLAGS)

LINK = $(CC) $(LFLAGS)


BIN = lux


all: $(BIN)


clean:

rm -fr *.o $(BIN)


.c.o:

@echo compiling $< ...

$(COMPILE) -c -o $@ $<


lux: lux.o

@echo link $@

$(LINK) -o $@ lux.o

$(STRIP) $@

lux.o: lux.c



lux.c

#include <errno.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <linux/i2c-dev.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <time.h>


#define I2C_DEV "/dev/i2c-1" // device driver name

#define TSL2561_ADDR 0x39


#define LUX_SCALE   14    // scale by 2^14

#define RATIO_SCALE 9     // scale ratio by 2^9


#define CH_SCALE          10    // scale channel values by 2^10

#define CHSCALE_TINT0     0x7517 // 322/11 * 2^CH_SCALE

#define CHSCALE_TINT1     0x0fe7 // 322/81 * 2^CH_SCALE


//---------------------------------------------------

// T Package coefficients

//---------------------------------------------------

// For Ch1/Ch0=0.00 to 0.50

// Lux/Ch0=0.0304-0.062*((Ch1/Ch0)^1.4)

// piecewise approximation

// For Ch1/Ch0=0.00 to 0.125: Lux/Ch0=0.0304-0.0272*(Ch1/Ch0)

// For Ch1/Ch0=0.125 to 0.250: Lux/Ch0=0.0325-0.0440*(Ch1/Ch0)

// For Ch1/Ch0=0.250 to 0.375: Lux/Ch0=0.0351-0.0544*(Ch1/Ch0)

// For Ch1/Ch0=0.375 to 0.50: Lux/Ch0=0.0381-0.0624*(Ch1/Ch0)

//

// For Ch1/Ch0=0.50 to 0.61: Lux/Ch0=0.0224-0.031*(Ch1/Ch0)

// For Ch1/Ch0=0.61 to 0.80: Lux/Ch0=0.0128-0.0153*(Ch1/Ch0)

// For Ch1/Ch0=0.80 to 1.30: Lux/Ch0=0.00146-0.00112*(Ch1/Ch0)

// For Ch1/Ch0>1.3: Lux/Ch0=0

//---------------------------------------------------


#define K1T  0x0040       // 0.125 * 2^RATIO_SCALE

#define B1T  0x01f2       // 0.0304 * 2^LUX_SCALE

#define M1T  0x01be       // 0.0272 * 2^LUX_SCALE

#define K2T  0x0080       // 0.250 * 2^RATIO_SCALETSL2560, TSL2561

#define B2T  0x0214       // 0.0325 * 2^LUX_SCALE

#define M2T  0x02d1       // 0.0440 * 2^LUX_SCALE

#define K3T  0x00c0       // 0.375 * 2^RATIO_SCALE

#define B3T  0x023f       // 0.0351 * 2^LUX_SCALE

#define M3T  0x037b       // 0.0544 * 2^LUX_SCALE

#define K4T  0x0100       // 0.50 * 2^RATIO_SCALE

#define B4T  0x0270       // 0.0381 * 2^LUX_SCALE

#define M4T  0x03fe       // 0.0624 * 2^LUX_SCALE

#define K5T  0x0138       // 0.61 * 2^RATIO_SCALE

#define B5T  0x016f       // 0.0224 * 2^LUX_SCALE

#define M5T  0x01fc       // 0.0310 * 2^LUX_SCALE

#define K6T  0x019a       // 0.80 * 2^RATIO_SCALE

#define B6T  0x00d2       // 0.0128 * 2^LUX_SCALE

#define M6T  0x00fb       // 0.0153 * 2^LUX_SCALE

#define K7T  0x029a       // 1.3 * 2^RATIO_SCALE

#define B7T  0x0018       // 0.00146 * 2^LUX_SCALE

#define M7T  0x0012       // 0.00112 * 2^LUX_SCALE

#define K8T  0x029a       // 1.3 * 2^RATIO_SCALE

#define B8T  0x0000       // 0.000 * 2^LUX_SCALE

#define M8T  0x0000       // 0.000 * 2^LUX_SCALE

//---------------------------------------------------

// CS package coefficients

//---------------------------------------------------

// For 0 <= Ch1/Ch0 <= 0.52

// Lux/Ch0 = 0.0315-0.0593*((Ch1/Ch0)^1.4)

// piecewise approximation

// For 0 <= Ch1/Ch0 <= 0.13 : Lux/Ch0 = 0.0315-0.0262*(Ch1/Ch0)

// For 0.13 <= Ch1/Ch0 <= 0.26 : Lux/Ch0 = 0.0337-0.0430*(Ch1/Ch0)

// For 0.26 <= Ch1/Ch0 <= 0.39: Lux/Ch0 = 0.0363-0.0529*(Ch1/Ch0)

// For 0.39 <= Ch1/Ch0 <= 0.52: Lux/Ch0 = 0.0392-0.0605*(Ch1/Ch0)

// For 0.52 < Ch1/Ch0 <= 0.65: Lux/Ch0 = 0.0229-0.0291*(Ch1/Ch0)

// For 0.65 < Ch1/Ch0 <= 0.80: Lux/Ch0 = 0.00157-0.00180*(Ch1/Ch0)

// For 0.80 < Ch1/Ch0 <= 1.30: Lux/Ch0 = 0.00338-0.00260*(Ch1/Ch0)

// For Ch1/Ch0 > 1.30: Lux = 0

//---------------------------------------------------

#define K1C  0x0043 // 0.130 * 2^RATIO_SCALE

#define B1C  0x0204 // 0.0315 * 2^LUX_SCALE

#define M1C  0x01ad // 0.0262 * 2^LUX_SCALE

#define K2C  0x0085 // 0.260 * 2^RATIO_SCALE

#define B2C  0x0228 // 0.0337 * 2^LUX_SCALE

#define M2C  0x02c1 // 0.0430 * 2^LUX_SCALE

#define K3C  0x00c8 // 0.390 * 2^RATIO_SCALE

#define B3C  0x0253 // 0.0363 * 2^LUX_SCALE

#define M3C  0x0363 // 0.0529 * 2^LUX_SCALE

#define K4C  0x010a // 0.520 * 2^RATIO_SCALE

#define B4C  0x0282 // 0.0392 * 2^LUX_SCALE

#define M4C  0x03df // 0.0605 * 2^LUX_SCALE

#define K5C  0x014d // 0.65 * 2^RATIO_SCALE

#define B5C  0x0177 // 0.0229 * 2^LUX_SCALE

#define M5C  0x01dd // 0.0291 * 2^LUX_SCALE

#define K6C  0x019a // 0.80 * 2^RATIO_SCALE

#define B6C  0x0101 // 0.0157 * 2^LUX_SCALE

#define M6C  0x0127 // 0.0180 * 2^LUX_SCALE

#define K7C  0x029a // 1.3 * 2^RATIO_SCALE

#define B7C  0x0037 // 0.00338 * 2^LUX_SCALE

#define M7C  0x002b // 0.00260 * 2^LUX_SCALE

#define K8C  0x029a // 1.3 * 2^RATIO_SCALE

#define B8C  0x0000 // 0.000 * 2^LUX_SCALE

#define M8C  0x0000 // 0.000 * 2^LUX_SCALE


typedef unsigned int uint;


// lux equation approximation without floating point calculations

//////////////////////////////////////////////////////////////////////////////

// Routine:     uint CalculateLux(uint ch0, uint ch0, int iType)

//

// Description: Calculate the approximate illuminance (lux) given the raw

// channel values of the TSL2560. The equation if implemented

// as a piece-wise linear approximation.

//

// Arguments:   uint iGain - gain, where 0:1X, 1:16X

// uint tInt - integration time, where 0:13.7mS, 1:100mS, 2:402mS, 3:Manual

// uint ch0 - raw channel value from channel 0 of TSL2560

// uint ch1 - raw channel value from channel 1 of TSL2560

// uint iType - package type (T or CS)

//

//    Return:   uint - the approximate illuminance (lux)

//

//////////////////////////////////////////////////////////////////////////////


uint CalculateLux(uint iGain, uint tInt, uint ch0, uint ch1, int iType)

{

// first, scale the channel values depending on the gain and integration time 16X, 402mS is nominal.

// scale if integration time is NOT 402 msec

uint chScale = (1 << CH_SCALE);

uint channel1;

uint channel0;

uint ratio, ratio1 = 0, b = 0, m = 0, temp;

uint lux;


if (tInt == 0) // 13.7ms

chScale = CHSCALE_TINT0;

else if (tInt == 1) // 101ms

chScale = CHSCALE_TINT1;

// scale if gain is NOT 16X

chScale = (!iGain) ? chScale << 4 : chScale;  // scale 1X to 16X


// scale the channel values

channel0 = (ch0 * chScale) >> CH_SCALE;

channel1 = (ch1 * chScale) >> CH_SCALE;

// find the ratio of the channel values (Channel1/Channel0)

// protect against divide by zero


if (channel0 != 0) 

ratio1 = (channel1 << (RATIO_SCALE+1)) / channel0;


// round the ratio value

ratio = (ratio1 + 1) >> 1;

switch (iType)

{

case 0: // T package

if ((ratio >= 0) && (ratio <= K1T)) {b=B1T; m=M1T;}

else if (ratio <= K2T) {b=B2T; m=M2T;}

else if (ratio <= K3T) {b=B3T; m=M3T;}

else if (ratio <= K4T) {b=B4T; m=M4T;}

else if (ratio <= K5T) {b=B5T; m=M5T;}

else if (ratio <= K6T) {b=B6T; m=M6T;}

else if (ratio <= K7T) {b=B7T; m=M7T;}

else if (ratio > K8T) {b=B8T; m=M8T;}

break;

case 1:// CS package

if ((ratio >= 0) && (ratio <= K1C)) {b=B1C; m=M1C;}

else if (ratio <= K2C) {b=B2C; m=M2C;}

else if (ratio <= K3C) {b=B3C; m=M3C;}

else if (ratio <= K4C) {b=B4C; m=M4C;}

else if (ratio <= K5C) {b=B5C; m=M5C;}

else if (ratio <= K6C) {b=B6C; m=M6C;}

else if (ratio <= K7C) {b=B7C; m=M7C;}

break;

}

temp = ((channel0 * b) - (channel1 * m));


// do not allow negative lux value

if (temp < 0) 

temp = 0;

// round lsb (2^(LUX_SCALE-1))

temp += (1 << (LUX_SCALE-1));


// strip off fractional portion

lux = temp >> LUX_SCALE;

return(lux);

}


void msleep(int n)

{

usleep(n*1000);

}


int tsl2561_write(int fd, unsigned char adr, unsigned char data)

{

unsigned char buf[10] = { 0, };

buf[0] = (1<<7) | adr;

buf[1] = data;


if (write(fd, buf, 2) != 2) {

printf("TSL2561: write error\n");

return 0;

}

return 1;

}


int tsl2561_measure(void)

{

static int fd = -1;

unsigned char buf[10] = { 0 };

unsigned int ch0, ch1;

if (fd == -1) {

if ((fd = open(I2C_DEV,O_RDWR)) < 0) 

return 0;

if (ioctl(fd, I2C_SLAVE, TSL2561_ADDR) < 0) {

printf("TSL2561: can't set address\n");

return 0;

}


tsl2561_write(fd, 0x00, 0x03); // power up

tsl2561_write(fd, 0x01, 0x02); // low gain(1x), integration time of 402ms

}


buf[0] = 0xAC;

if (write(fd, buf, 1) != 1) {

printf("TSL2561: write error: %s\n\n", strerror(errno));

}


if (read(fd, buf, 2) != 2) {

printf("TSL2561: read error: %s\n\n", strerror(errno));

return -1;

}

ch0 = (buf[1] << 8) | buf[0];

buf[0] = 0xAE;

if (write(fd, buf, 1) != 1) {

printf("TSL2561: write error: %s\n\n", strerror(errno));

}


if (read(fd, buf, 2) != 2) {

printf("TSL2561: read error: %s\n\n", strerror(errno));

return -1;

}

ch1 = (buf[1] << 8) | buf[0];


return CalculateLux(0, 2, ch0, ch1, 0);

}


int main(int argc, char* argv[])

{

char s[256];

struct tm* tp;

time_t st = time(NULL);

while(1) {


if (time(NULL) == st) {

msleep(1);

continue;

}

st = time(NULL);


tp = localtime(&st);

strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", tp);

printf("%s lux=%d\n", s, tsl2561_measure());

}

}







,

Beaglebone Black Board ($45)에서 HT11 ($0.5)를 간단하게 테스트 해 보았다.

HT11은 Aliexpress에서 10개 떨이로 무료 배송하는 것을 구입. 배송에 대략 2주가 걸렸다.


BBB 보드에 debian-7.2-console-armhf-2013-10-25.tar.xz 파일을 설치. emmc에 자동으로 플래싱을 해주는데, X windows 등을 사용하지 않아 원래 설치되어 있던 angstrom distribution보다 가벼워 선택. ubuntu를 설치할까 하다가 말았다. debian이 낫다. ssh는 기본적으로 설치되어 있고, develop 환경을 구축하기 위해 필요한 패키지를 설치하고 samba도 설치. 


samba만 셋업하여 PC와 연결. PC에서 소스를 에디팅.


HT11의 vcc는 P9.3번 핀, gnd는 P9.1번 핀에 각각 연결. 

HT11의 data pin은 cape의 P9 커넥터 16번 핀(GPIO1_19)에 연결. vcc와의 pull-up 저항 5Kohm을 연결.  사진의 중앙 상단의 파란색 4pin 짜리. 우하단은 조도센서로 다음에...



HT11의 온도 정밀도나 습도 정밀도가 낮으나 막 쓰기에는 좋아 보여 샀는데, measure 중에 갖가지 에러가 뜨더라. 정밀도는 정말로 의심스럽고. 데이터 전송 에러는 주로 bit 전송 중 중단되는 것과(대략 40% 가량), checksum error(대략 10% 미만), 합쳐서 50% 가량 나온다. 그래서 마지막에 읽은 값을 보전하고 있다가 재 사용하는 것이 바람직. 


문제점

  • 커널의 usleep() 함수는 정확한 usec 단위로 작동하지 않는다. 따라서 타이밍에 사용하기에는 부적합하다. 리눅스 커널보다는 아무래도 arduino 등의 MCU 보드가 차라리 낫겠다. 
  • 데이터 전송 에러의 원인은 아무래도 kernel의 switch latency 때문에 input data capture 타이밍을 놓쳐서 인 것 같다. 이 때문에 소스에서 1과 0을 판정하는 부분은 loop counter를 관찰해서 임의로 정했다.
  • HT11의 one-wire connection은 SPI나 I2C 인터페이스에 비해 다루기가 귀찮고 에러에서 자유롭지 않다. 
  • HT11의 습도 계측은 정전식인데, 이게 아무래도 미덥지가 않다. 마침 눈이 내리는 날에 측정을 했는데 이때의 RH(relative humidity)는 거의 100%에 가깝게 나와야 할 것 같다. 어쩌면 실내 환경이 눈 내리는 바깥보다는 건조한 탓인지도 모르겠다. 


kernel에서 export한 /sys/class/gpio는 간단한 IO질에는 괜찮아 뵈지만, 실용적으로 매우 속도가 느리고 거추장스러워서 AM3359 AP datasheet를 참조하고 kernel의 /dev/mem을 이용하여 gpio direct access를 구현했다(gpio.c 및 gpio.h).


실행 결과 (50회 계측)

bits=39: 21 32 3A -> 53  : 2: h=0% t=0 (bit error:1.00, chksum error:0.00)

bits=39: 22 32 3B -> 54  : 2: h=0% t=0 (bit error:1.00, chksum error:0.00)

bits=40: 22 19 3B -> 3B  : 1: h=34% t=25 (bit error:0.67, chksum error:0.00)

bits=40: 22 19 3B -> 3B  : 1: h=34% t=25 (bit error:0.50, chksum error:0.00)

bits=40: 91 8C 9D -> 1D  : 0: h=34% t=25 (bit error:0.40, chksum error:0.20)

bits=39: 22 32 3B -> 54  : 2: h=34% t=25 (bit error:0.50, chksum error:0.17)

bits=40: 21 09 3A -> 2A  : 0: h=34% t=25 (bit error:0.43, chksum error:0.29)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.38, chksum error:0.25)

bits=34: 40 00 02 -> 40  : 2: h=33% t=25 (bit error:0.44, chksum error:0.22)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.40, chksum error:0.20)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.36, chksum error:0.18)

bits=39: 42 32 3A -> 74  : 2: h=33% t=25 (bit error:0.42, chksum error:0.17)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.38, chksum error:0.15)

bits=28: 22 32 00 -> 54  : 2: h=33% t=25 (bit error:0.43, chksum error:0.14)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.40, chksum error:0.13)

bits=40: 21 19 2A -> 3A  : 0: h=33% t=25 (bit error:0.38, chksum error:0.19)

bits=40: 21 11 3A -> 32  : 0: h=33% t=25 (bit error:0.35, chksum error:0.24)

bits=37: 20 48 1A -> 68  : 2: h=33% t=25 (bit error:0.39, chksum error:0.22)

bits=37: 20 64 1A -> 84  : 2: h=33% t=25 (bit error:0.42, chksum error:0.21)

bits=38: 20 32 3A -> 52  : 2: h=33% t=25 (bit error:0.45, chksum error:0.20)

bits=39: 21 34 3B -> 55  : 2: h=33% t=25 (bit error:0.48, chksum error:0.19)

bits=39: 21 34 3B -> 55  : 2: h=33% t=25 (bit error:0.50, chksum error:0.18)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.48, chksum error:0.17)

bits=39: 21 12 3A -> 33  : 2: h=33% t=25 (bit error:0.50, chksum error:0.17)

bits=40: 21 09 3A -> 2A  : 0: h=33% t=25 (bit error:0.48, chksum error:0.20)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.46, chksum error:0.19)

bits=40: 21 19 3A -> 3A  : 1: h=33% t=25 (bit error:0.44, chksum error:0.19)

bits=39: 21 32 3A -> 53  : 2: h=33% t=25 (bit error:0.46, chksum error:0.18)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.45, chksum error:0.17)

bits=38: 22 34 3B -> 56  : 2: h=33% t=26 (bit error:0.47, chksum error:0.17)

bits=38: 22 32 3A -> 54  : 2: h=33% t=26 (bit error:0.48, chksum error:0.16)

bits=40: 20 1A 3B -> 3A  : 0: h=33% t=26 (bit error:0.47, chksum error:0.19)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.45, chksum error:0.18)

bits=39: 21 1C 3B -> 3D  : 2: h=33% t=26 (bit error:0.47, chksum error:0.18)

bits=39: 42 32 3A -> 74  : 2: h=33% t=26 (bit error:0.49, chksum error:0.17)

bits=40: 90 8D 9D -> 1D  : 0: h=33% t=26 (bit error:0.47, chksum error:0.19)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.46, chksum error:0.19)

bits=39: 21 34 3B -> 55  : 2: h=33% t=26 (bit error:0.47, chksum error:0.18)

bits=40: 90 8D 9D -> 1D  : 0: h=33% t=26 (bit error:0.46, chksum error:0.21)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.45, chksum error:0.20)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.44, chksum error:0.20)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.43, chksum error:0.19)

bits=37: 24 68 1B -> 8C  : 2: h=33% t=26 (bit error:0.44, chksum error:0.19)

bits=39: 22 34 3B -> 56  : 2: h=33% t=26 (bit error:0.45, chksum error:0.18)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.44, chksum error:0.18)

bits=40: 20 1A 3B -> 3A  : 0: h=33% t=26 (bit error:0.43, chksum error:0.20)

bits=39: 21 34 39 -> 55  : 2: h=33% t=26 (bit error:0.45, chksum error:0.19)

bits=40: 90 8D 9D -> 1D  : 0: h=33% t=26 (bit error:0.44, chksum error:0.21)

bits=39: 42 34 3B -> 76  : 2: h=33% t=26 (bit error:0.45, chksum error:0.20)

bits=40: 21 1A 3B -> 3B  : 1: h=33% t=26 (bit error:0.44, chksum error:0.20)



Makefile

.SILENT: 


CC = gcc

STRIP = strip

INCLUDES = 

LIBS = 

CFLAGS = -Wall -O2 

LFLAGS = 


COMPILE = $(CC) $(INCLUDES) $(CFLAGS)

LINK = $(CC) $(LFLAGS)


BIN =  ht11


all: $(BIN)


clean:

rm -fr *.o $(BIN)


.c.o:

@echo compiling $< ...

$(COMPILE) -c -o $@ $<


ht11: ht11.o gpio.o

@echo link $@ with $?

$(LINK) -o $@ $?

$(STRIP) $@

ht11.o: ht11.c gpio.h

gpio.o: gpio.c gpio.h


ht11.c

#include <stdio.h>

#include <unistd.h>

#include "gpio.h"


// #define HT11_DEBUG


#define HT11_PORT 1 // GPIO1_19 (P9.16)

#define HT11_BIT 19 // GPIO1_19 (P9.16)


#define BIT0 82 // experimental value


#define HT11_INPUT() GPIO_SET_DIR_IN(HT11_PORT, HT11_BIT)

#define HT11_OUTPUT() GPIO_SET_DIR_OUT(HT11_PORT, HT11_BIT)

#define HT11_SET() GPIO_SET(HT11_PORT, HT11_BIT)

#define HT11_CLEAR() GPIO_CLEAR(HT11_PORT, HT11_BIT)

#define HT11_GET() GPIO_GET(HT11_PORT, HT11_BIT)


int dht_measure(int* temp, int* humi)

{

unsigned char data[8] = { 0, };

int counter, bit, chksum;

#ifdef HT11_DEBUG

int i, n = 0, ca[100] = { 0, };

#endif


// start measure. condition: force high 250ms, and then low ~20ms


HT11_OUTPUT();

HT11_SET();

usleep(250*1000);

HT11_CLEAR();

usleep(10*1000);

HT11_SET();

usleep(1); // small sleep for transition

// wait for beginning of HT11 response


HT11_INPUT();

for (counter = 0; HT11_GET() == 1 && counter < 10000; counter++) ;

// wait 80us for start condition low from HT11

for (counter = 0; HT11_GET() == 0 && counter < 10000; counter++) ;

// wait 80us for start condition high from HT11

for (counter = 0; HT11_GET() == 1 && counter < 10000; counter++) ;


// read 40bits

for (bit = 0; bit < 40; bit++) {

// check start transfer condition

for (counter = 0; HT11_GET() == 0 && counter < 10000; counter++) ;

if (counter >= 10000)

break;

// measure high or low

for (counter = 0; HT11_GET() == 1 && counter < 10000; counter++) ;

#ifdef HT11_DEBUG

ca[n++] = counter;

#endif

if (counter >= 10000)

break;

data[bit/8] <<= 1;

if (counter > BIT0)

data[bit/8] |= 1;

}


chksum = ((data[0] + data[2]) & 0xff);

printf("bits=%02d: %02X %02X %02X -> %02X  : ", bit, data[0], data[2], data[4], chksum);

#ifdef HT11_DEBUG

for (i = 0; i < n; i++) 

printf("\t%02d : %d %d\n", i, ca[i] > BIT0 ? 1 : 0, ca[i]);

#endif

if (bit == 40) {

if (data[4] == chksum) {

*temp = data[2];

*humi = data[0];

return 1;

}

return 0;

}

else

return 2;

return 0;

}


int main(int argc, char*argv[])

{

int temp = 0, humi = 0;

int e;

int tries = 0;

int ebit = 0;

int echk = 0;

gpio_init();

while (1) {

switch ((e = dht_measure(&temp, &humi))) {

case 0: echk++; break;

case 2: ebit++; break;

default: break;

}

tries++;

printf("%d: h=%d%% t=%d (bit error:%.2f, chksum error:%.2f)\n", e, humi, temp, (double)ebit/tries, (double)echk/tries);

if (tries == 50)

break;

}

return 0;

}


gpio.h

#ifndef __GPIO_H__

#define __GPIO_H__


extern volatile unsigned int* gpio_base[];

extern int gpio_init();


#define GPIO_REG_OE 0x134 // output enable 0=output, 1=input

#define GPIO_REG_IN 0x138 // input

#define GPIO_REG_SET 0x194 // write '1' set

#define GPIO_REG_CLEAR 0x190 // write '1' clear


#define GPIO_PTR(port, ofs) ((volatile unsigned int*)(((unsigned char*)gpio_base[port])+ofs))


#define GPIO_SET_DIR_IN(port, bit) (*GPIO_PTR(port, GPIO_REG_OE) |= (1<<bit))

#define GPIO_SET_DIR_OUT(port, bit) (*GPIO_PTR(port, GPIO_REG_OE) &= ~(1<<bit))

#define GPIO_SET(port, bit) (*GPIO_PTR(port, GPIO_REG_SET) = (1<<bit))

#define GPIO_CLEAR(port, bit) (*GPIO_PTR(port, GPIO_REG_CLEAR) = (1<<bit))

#define GPIO_GET(port, bit) ((*GPIO_PTR(port, GPIO_REG_IN) >> bit) & 1)



#endif


gpio.c

#include <stdio.h>

#include <fcntl.h> 

#include <unistd.h>

#include <sys/mman.h>


#define MAX_PORT 4


#define GPIO0_BASE 0x44E07000

#define GPIO1_BASE 0x4804C000

#define GPIO2_BASE 0x481AC000

#define GPIO3_BASE 0x481AE000

#define GPIO_IO_SIZE (4096) // 4KB


static int gpio_fd = -1;

static int gpios[MAX_PORT] = { GPIO0_BASE, GPIO1_BASE, GPIO2_BASE, GPIO3_BASE };

volatile unsigned int* gpio_base[MAX_PORT];


int gpio_init() 

{

int i;

if ((gpio_fd = open("/dev/mem", O_RDWR)) == -1) {

printf("can't open /dev/mem\n");

return 0;

}

for (i = 0; i < MAX_PORT; i++) {

gpio_base[i] = mmap(0, GPIO_IO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, gpio_fd, gpios[i]);

if (gpio_base[i] == MAP_FAILED) {

printf("mmap gpio%d failed\n", i);

return 0;

}

}

return 1;

}



,