水位センサを買うのと一緒にスイッチサイエンスで”GROVE – ADS1115搭載 16bit ADC – 4チャンネル“を購入したので、こちらにも対応します。こちらの商品は Grove やスクリューターミナルがついてて便利なものの1760円とそこそこのお値段がするので、同じチップを搭載したもうちょっと安いものを探してきて使った方が良かったかな~とおもいます。
ADS1115 自体に I2C の機能も搭載されていて、このボードは単にチップと端子を繋いでいるだけのようです。Linuxは標準で ADS1115 に対応しており、VisionFive2 SDK でも VisionFive2/linux/drivers/iio/adc/ti-ads1015.c があります。これとデータシートを参考にしながら作業を進めます。また、”[電子工作]ADS1115 16bit ADCを使ってみた“で ADS1115 の使い方が説明されているのでここのコードも参考にしました。
ちなみに、Linux で標準対応しているならカーネルモジュールを用意すれば Linux 方式で使えるのでは!?!?!?と思ってやってみたのですが、カーネルモジュールのビルドとロードはできたもののこれを使うには device tree への登録が必要で、そのためには u-boot の更新が必要で、オリジナルの u-boot を使うためには自前で用意した sdcard.img を使うようにしなければいけないけど VisionFive2 SDK から作ることができるのは最低限の環境なのでどーする!?!?というところで詰まってしまいました。。。Linux よくわからんちん。。。
コード
でまあ何やかんやと四苦八苦しながら書いたのが以下のコードです。基本は動画で説明されている内容そのままで、VisionFive2 が対応していない Arduino の Wire.h 関連を”水位センサの値を取得する“でも参考にした”LinuxのC言語でI2Cデバイスと通信する“を参考に書いています。細かい説明は。。。精も根も尽き果てたのでご勘弁を_(:3 」∠ )_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
//i2c address
#define I2C_DEVICE "/dev/i2c-0"
#define ADS1115_I2C_ADDRESS 0x48
//BIT[1:0] Register address
#define CONVERSION_REGISTER 0
#define CONFIG_REGISTER 1
#define LO_THRESH_REGISTER 2
#define HI_THRESH_REGISTER 3
//BIT[15]
#define NO_EFFECT (0 << 15)
#define START_SINGLE_CONVERSION (1 << 15)
//BIT[14:12] Input multiplexer configuration
#define P_AIN0_N_AIN1 (0 << 12)
#define P_AIN0_N_AIN3 (1 << 12)
#define P_AIN1_N_AIN1 (2 << 12)
#define P_AIN2_N_AIN1 (3 << 12)
#define P_AIN0_N_GND (4 << 12)
#define P_AIN1_N_GND (5 << 12)
#define P_AIN2_N_GND (6 << 12)
#define P_AIN3_N_GND (7 << 12)
//BIT[11:9] Programmable gain amplifier configuration
#define FSR_6144MV (0 << 9)
#define FSR_4096MV (1 << 9)
#define FSR_2048MV (2 << 9)
#define FSR_1024MV (3 << 9)
#define FSR_0512MV (4 << 9)
#define FSR_0256MV (5 << 9)
//#define FSR_0256MV (6 << 9)
//#define FSR_0256MV (7 << 9)
//BIT[8] Device operating mode
#define CONTINUOUS_CONVERSION (0 << 8)
#define SINGLE_SHOT (1 << 8)
//BIT[7:5] Data rate
#define DATA_RATE_8_SPS (0 << 5)
#define DATA_RATE_16_SPS (1 << 5)
#define DATA_RATE_32_SPS (2 << 5)
#define DATA_RATE_64_SPS (3 << 5)
#define DATA_RATE_128_SPS (4 << 5)
#define DATA_RATE_250_SPS (5 << 5)
#define DATA_RATE_475_SPS (6 << 5)
#define DATA_RATE_860_SPS (7 << 5)
//BIT[4] Comparator mode
#define TRADITIONAL_COMPARATE (0 << 4)
#define WINDOW_COMPARATE (1 << 4)
//BIT[3] Comparator polarity
#define ACTIVE_LOW (0 << 3)
#define ACTIVE_HIGH (1 << 3)
//BIT[2] Latching comparator
#define NONLATCHING_COMPARATOR (0 << 2)
#define LATCHING_COMPARATOR (1 << 2)
//BIT[1:0] Comparator queue and disable
#define ASSERT_AFTER_1_CONVERSION 0
#define ASSERT_AFTER_2_CONVERSION 1
#define ASSERT_AFTER_4_CONVERSION 2
#define DISABLE_COMPARATOR 3
#define LSB_SIZE_6144MV (187.5 * 1E-6)
#define LSB_SIZE_4096MV (125.0 * 1E-6)
#define LSB_SIZE_2048MV ( 62.5 * 1E-6)
#define LSB_SIZE_1024MV ( 31.25 * 1E-6)
#define LSB_SIZE_0512MV ( 15.625 * 1E-6)
#define LSB_SIZE_0256MV ( 5.8125 * 1E-6)
void printb(unsigned int v) {
int buf[16];
for(int i=0;i<16;i++) {
buf[15-i] = (v >> i) & 1;
}
for(int i=0;i<16;i++) {
printf("%d", buf[i]);
}
}
int32_t i2c_open(char *i2c_device) {
int32_t fd = open(I2C_DEVICE, O_RDWR);
if (fd == -1) {
fprintf(stderr, "i2c_read: failed to open: %s\n", strerror(errno));
return -1;
}
return fd;
}
void i2c_close(int32_t i2c_dev) {
close(i2c_dev);
}
int8_t i2c_read(int32_t i2c_dev, uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t length) {
struct i2c_msg messages[] = {
{ dev_addr, 0, 1, ®_addr},
{ dev_addr, I2C_M_RD, length, data },
};
struct i2c_rdwr_ioctl_data ioctl_data = { messages, 2 };
if (ioctl(i2c_dev, I2C_RDWR, &ioctl_data) != 2) {
fprintf(stderr, "i2c_read: failed to ioctl: %s\n", strerror(errno));
return -1;
}
return 0;
}
int8_t i2c_write(int32_t i2c_dev, uint8_t dev_addr, uint8_t reg_addr, const uint8_t* data, uint16_t length) {
uint8_t* buf = (uint8_t*)malloc(length + 1);
if (buf == NULL) {
fprintf(stderr, "i2c_write: failed to memory allocate\n");
return -1;
}
buf[0] = reg_addr;
memcpy(&buf[1], data, length);
struct i2c_msg messages[] = {
{ dev_addr, 0, length+1, buf}
};
struct i2c_rdwr_ioctl_data ioctl_data = { messages, 1 };
if (ioctl(i2c_dev, I2C_RDWR, &ioctl_data) != 1) {
fprintf(stderr, "i2c_write: failed to ioctl: %s\n", strerror(errno));
free(buf);
return -1;
}
free(buf);
return 0;
}
uint16_t ads1115_read_register(int32_t i2c_dev) {
uint8_t buf[2];
int16_t data;
i2c_read(i2c_dev, ADS1115_I2C_ADDRESS, CONVERSION_REGISTER, buf, 2);
data = (buf[0] << 8) | buf[1];
return data;
}
void ads1115_write_register(int32_t i2c_dev, uint16_t data) {
uint8_t buf[] = {
data >> 8 & 0xff,
data >> 0 & 0xff
};
i2c_write(i2c_dev, ADS1115_I2C_ADDRESS, CONFIG_REGISTER, buf, 2);
return;
}
int init(int32_t i2c_dev) {
ads1115_write_register(i2c_dev,
NO_EFFECT |
P_AIN0_N_GND |
FSR_6144MV |
CONTINUOUS_CONVERSION |
DATA_RATE_8_SPS |
WINDOW_COMPARATE |
ACTIVE_LOW |
LATCHING_COMPARATOR |
ASSERT_AFTER_1_CONVERSION);
return 0;
}
int sense(int i2c_dev) {
int16_t sensor_val;
sensor_val = ads1115_read_register(i2c_dev);
printf("sensor value: ");
printb(sensor_val);
printf("\n");
printf("voltage = %f\n", sensor_val * LSB_SIZE_6144MV);
}
int main() {
int32_t i2c_dev;
i2c_dev = i2c_open(I2C_DEVICE);
init(i2c_dev);
while(1) {
sense(i2c_dev);
sleep(1);
}
i2c_close(i2c_dev);
return 0;
}
動作確認
直流安定化電源の出力を入力して、電圧を変えながら様子を見ます。
それでは実行。
chiyama@starfive:~$ sudo ./ADS1115 sudo: unable to resolve host starfive: Name or service not known voltage = 0.000188 sensor value: 0000010011100100 voltage = 0.234750 sensor value: 0001000111001010 voltage = 0.853875 sensor value: 0001110101111011 voltage = 1.415063 sensor value: 0010100100101011 voltage = 1.976063 sensor value: 0010110110000011 voltage = 2.184563 sensor value: 0011000110100101 voltage = 2.382938 sensor value: 0100000101011101 voltage = 3.137437 sensor value: 0100101100111100 voltage = 3.611250 sensor value: 0101000010110011 voltage = 3.873563 sensor value: 0101000011010001 voltage = 3.879188 sensor value: 0101000011010100 voltage = 3.879750 sensor value: 0101000011010101 voltage = 3.879938 sensor value: 0101000011010110 voltage = 3.880125 sensor value: 0101000011011000 voltage = 3.880500 sensor value: 0101000011010100 voltage = 3.879750 ^C chiyama@starfive:~$
直流安定化電源の電圧を4Vまで上げたところ、3.88Vで計測されました。ちょっと低いですね。3.8Vくらいまではかなり正確な値が計測しているのですが、そこから先が誤差が広がっているようです。VDDに近くなるとダメなのか???と思いつつ、よみやさんの動画では4.18Vまで計測しているのでそういうわけでもなさそうです。データシートも目を通してみたものの、それっぽい制限事項を見つけることができなかったです。。。謎な。。。このあたりは、私の電子工作関連のスキルの低さが現れてしまっている感じです(涙
とはいえ、3.8Vまでであればいい感じに使えるようなので一旦はこれで良しとしておきます。