1 /*
2  * Author: William Penner <william.penner@intel.com>
3  * Copyright (c) 2014 Intel Corporation.
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to deal
7  * in the Software without restriction, including without limitation the rights
8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21  * THE SOFTWARE.
22  */
23 
24 #include <iostream>
25 #include <string>
26 #include <stdexcept>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <pthread.h>
30 #include <sched.h>
31 #include <time.h>
32 
33 #include "am2315.h"
34 
35 using namespace upm;
36 
37 char g_name[] = AM2315_NAME;
38 
AM2315(int bus,int devAddr)39 AM2315::AM2315(int bus, int devAddr) {
40     m_temperature = 0;
41     m_humidity    = 0;
42     m_last_time = 0;
43 
44     m_name = g_name;
45 
46     m_controlAddr = devAddr;
47     m_bus = bus;
48 
49     m_base_priority = sched_getscheduler(0);
50 
51     if ( !(m_i2ControlCtx = mraa_i2c_init(m_bus)) )
52       {
53         throw std::invalid_argument(std::string(__FUNCTION__) +
54                                     ": mraa_i2c_init() failed");
55         return;
56       }
57 
58     mraa_result_t ret = mraa_i2c_address(m_i2ControlCtx, m_controlAddr);
59     if (ret != MRAA_SUCCESS) {
60         throw std::invalid_argument(std::string(__FUNCTION__) +
61                                     ": mraa_i2c_address() failed");
62         return;
63     }
64     m_model = i2cReadReg_16(AM2315_MODEL);
65     m_version = i2cReadReg_8(AM2315_VERSION);
66     m_id = i2cReadReg_32(AM2315_ID);
67 
68     fprintf(stdout,"%s: Model: 0x%04x Version: 0x%02x ID: 0x%08x\n",
69             m_name, m_model, m_version, m_id );
70 }
71 
~AM2315()72 AM2315::~AM2315() {
73     mraa_i2c_stop(m_i2ControlCtx);
74 }
75 
76 void
update_values(void)77 AM2315::update_values(void)
78 {
79     time_t ctime = time(NULL);
80     if ((ctime - m_last_time) >= AM2315_SAMPLE) {
81         uint32_t uival = i2cReadReg_32(AM2315_HUMIDITY);
82         m_humidity = uival >> 16;
83         m_temperature = uival & 0xffff;
84         m_last_time = ctime;
85     }
86     else {
87         // In case the time is changed - backwards
88         if (ctime < m_last_time)
89             m_last_time = ctime;
90     }
91 }
92 
93 float
getTemperature(void)94 AM2315::getTemperature(void)
95 {
96     update_values();
97     return (float)m_temperature / 10;
98 }
99 
100 float
getTemperatureF(void)101 AM2315::getTemperatureF(void)
102 {
103     return getTemperature() * 9 / 5 + 32;
104 }
105 
106 float
getHumidity(void)107 AM2315::getHumidity(void)
108 {
109     update_values();
110     return (float)m_humidity / 10;
111 }
112 
113 /*
114  * Test function: when reading the AM2315 many times rapidly should
115  * result in a temperature increase.  This test will verify that the
116  * value is changing from read to read
117  */
118 
119 int
testSensor(void)120 AM2315::testSensor(void)
121 {
122     int i;
123     int iError = 0;
124     float fTemp, fHum;
125     float fTempMax, fTempMin;
126     float fHumMax, fHumMin;
127 
128     fprintf(stdout, "%s: Executing Sensor Test\n", m_name );
129 
130     fHum  = getHumidity();
131     fTemp = getTemperature();
132     fTempMax = fTempMin = fTemp;
133     fHumMax  = fHumMin  = fHum;
134 
135     // Then sample the sensor a few times
136     for (i=0; i < 10; i++) {
137         fHum  = getHumidity();
138         fTemp = getTemperature();
139         if (fHum  < fHumMin)  fHumMin  = fHum;
140         if (fHum  > fHumMax)  fHumMax  = fHum;
141         if (fTemp < fTempMin) fTempMin = fTemp;
142         if (fTemp > fTempMax) fTempMax = fTemp;
143         usleep(50000);
144     }
145 
146     // Now check the results
147     if (fHumMin == fHumMax && fTempMin == fTempMax) {
148         fprintf(stdout, "%s:  Humidity/Temp reading was unchanged - warning\n",
149                 m_name );
150         iError++;
151     }
152     if (iError == 0) {
153         fprintf(stdout, "%s:  Device appears functional\n", m_name );
154     }
155 
156     fprintf(stdout, "%s: Test complete\n", m_name );
157 
158     return iError;
159 }
160 
161 uint16_t
crc16(uint8_t * ptr,uint8_t len)162 AM2315::crc16(uint8_t* ptr, uint8_t len)
163 {
164     uint16_t crc = 0xffff;
165     uint8_t i;
166 
167     while(len--) {
168         crc ^= *ptr++;
169         for (i=0; i < 8; i++) {
170             if (crc & 0x01) {
171                 crc >>= 1;
172                 crc ^= 0xA001;
173             }
174             else {
175                 crc >>= 1;
176             }
177         }
178     }
179     return crc;
180 }
181 
182 /*
183  * Functions to read and write data to the i2c device in the
184  * special format used by the device.  This is using i2c to
185  * interface to a controller that the AOSONG AM2315 uses to
186  * perform the measurements and manage other registers.
187  */
188 int
i2cWriteReg(uint8_t reg,uint8_t * data,uint8_t ilen)189 AM2315::i2cWriteReg(uint8_t reg, uint8_t* data, uint8_t ilen)
190 {
191     uint8_t tdata[16] = { AM2315_WRITE, reg, ilen };
192     mraa_result_t error;
193 
194     for (int i=0; i < ilen; i++) {
195         tdata[i+3] = data[i];
196     }
197     uint16_t crc = crc16(tdata, ilen+3);
198     // CRC is sent out backwards from other registers (low, high)
199     tdata[ilen+3] = crc;
200     tdata[ilen+4] = (crc >> 8);
201 
202     mraa_result_t ret = mraa_i2c_address(m_i2ControlCtx, m_controlAddr);
203     int iLoops = 5;
204     mraa_set_priority(HIGH_PRIORITY);
205     do {
206         error = mraa_i2c_write(m_i2ControlCtx, tdata, ilen+5);
207         usleep(800);
208     } while(error != MRAA_SUCCESS && --iLoops);
209     mraa_set_priority(m_base_priority);
210 
211     if (error != MRAA_SUCCESS) {
212         fprintf(stdout, "%s: Error, timeout writing sensor.\n", m_name);
213         return -1;
214     }
215     crc = crc16(tdata,3);
216     mraa_i2c_read(m_i2ControlCtx, tdata, 5);
217     if ((tdata[0] != AM2315_WRITE) ||
218         (tdata[1] != reg)          ||
219         (tdata[2] != ilen)         ||
220         (tdata[3] != (crc & 0xff)) ||
221         (tdata[4] != (crc >> 8))) {
222         fprintf(stdout, "%s: CRC error during write verification\n", m_name);
223         return -1;
224     }
225     return 0;
226 }
227 
228 
229 // TODO: Need to patch up function to return only the data that
230 // is needed and not require the various functions that call this
231 // to send it enough buffer to cover the function
232 
233 uint8_t
i2cReadReg(int reg,uint8_t * data,int ilen)234 AM2315::i2cReadReg(int reg, uint8_t* data, int ilen)
235 {
236     uint8_t tdata[16] = { AM2315_READ, reg, ilen };
237 
238     mraa_result_t ret = mraa_i2c_address(m_i2ControlCtx, m_controlAddr);
239     int iLoops = 5;
240     mraa_set_priority(HIGH_PRIORITY);
241     do {
242         ret = mraa_i2c_write(m_i2ControlCtx, tdata, 3);
243         usleep(800);
244     } while(ret != MRAA_SUCCESS && --iLoops);
245     if (ret != MRAA_SUCCESS) {
246         fprintf(stdout, "%s: Error, timeout reading sensor.\n", m_name);
247         mraa_set_priority(m_base_priority);
248         return -1;
249     }
250     usleep(5000);
251     mraa_i2c_read(m_i2ControlCtx, tdata, ilen+4);
252     mraa_set_priority(m_base_priority);
253 
254     uint16_t crc = crc16(tdata, ilen+2);
255     if ((tdata[0] != AM2315_READ)  ||
256         (tdata[1] != ilen)         ||
257         (tdata[ilen+2] != (crc & 0xff)) ||
258         (tdata[ilen+3] != (crc >> 8))) {
259         fprintf(stdout, "%s: Read crc failed.\n", m_name);
260     }
261     for (int i=0; i < ilen; i++)
262         data[i] = tdata[i+2];
263 
264     return 0;
265 }
266 
267 /*
268  * Functions to set up the reads and writes to simplify the process of
269  * formatting data as needed by the microcontroller
270  */
271 
272 int
i2cWriteReg_32(int reg,uint32_t ival)273 AM2315::i2cWriteReg_32(int reg, uint32_t ival) {
274     uint8_t data[4];
275     data[0] = ival >> 24;
276     data[1] = ival >> 16;
277     data[1] = ival >>  8;
278     data[1] = ival & 0xff;
279     return i2cWriteReg(reg, data, 4);
280 }
281 
282 int
i2cWriteReg_16(int reg,uint16_t ival)283 AM2315::i2cWriteReg_16(int reg, uint16_t ival) {
284     uint8_t data[2];
285     data[0] = ival & 0xff;
286     data[1] = ival >> 8;
287     return i2cWriteReg(reg, data, 2);
288 }
289 
290 int
i2cWriteReg_8(int reg,uint8_t ival)291 AM2315::i2cWriteReg_8(int reg, uint8_t ival) {
292     uint8_t data[2];
293     data[0] = ival & 0xff;
294     data[1] = ival >> 8;
295     return i2cWriteReg(reg, data, 2);
296 }
297 
298 uint32_t
i2cReadReg_32(int reg)299 AM2315::i2cReadReg_32 (int reg) {
300     uint8_t data[4];
301     i2cReadReg(reg, data, 4);
302     return ((((((uint32_t)data[0] << 8) | data[1]) << 8) |
303             data[2]) << 8) | data[3];
304 }
305 
306 uint16_t
i2cReadReg_16(int reg)307 AM2315::i2cReadReg_16 (int reg) {
308     uint8_t data[2];
309     i2cReadReg(reg, data, 2);
310     return ((int16_t)data[0] << 8) | (uint16_t)data[1];
311 }
312 
313 uint8_t
i2cReadReg_8(int reg)314 AM2315::i2cReadReg_8 (int reg) {
315     uint8_t data[1];
316     i2cReadReg(reg, data, 1);
317     return data[0];
318 }
319 
320