13.4.MCP9808による温度計測
プロジェクト概要
MCP9808を使用した高精度温度計測プロジェクトです。Picoとの通信はI2Cで、Adafruitのブレイクアウトボードではアドレスは0x18~0x1Fから選べます。初期値はアドレスは0x18です。
調整可能なアドレス ピンを備えた 1 つの I2C バス上に最大 8個可能
-40°C ~ 125°C の範囲で標準精度 0.25°C (-20°C ~ 100°C で最大 0.5°C を保証)
0.0625℃の分解能
2.7V~5.5Vの電源およびロジック電圧範囲
動作電流: 200 μA (標準)
部品リスト
MCP9808HighAccuracyI2CTemperature Senso 1 スイッチサイエンス
GROVEの16 x 2 LCD 1 スイッチサイエンス
配線図

ソースリスト
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/rtc.h"
#include "hardware/adc.h"
//#define PICO_DEFAULT_LED_PIN 25
#define LED_PIN PICO_DEFAULT_LED_PIN
#define I2C_PORT i2c0
#define I2C_SDA 8
#define I2C_SCL 9
//ADc
const float ConversionFactor = 3.3f / (1 << 12);
int WaitTerminalStartup(int timeout_msec) {
int btnin;
uint32_t st = time_us_32();
while(true)
{
if(stdio_usb_connected())
{
return 0;
}
if(timeout_msec != 0){
uint32_t cur = time_us_32();
if((cur - st) > (timeout_msec*1000)) {
return 0;
}
}
gpio_put(LED_PIN, 1);
sleep_ms(200);
gpio_put(LED_PIN, 0);
sleep_ms(200);
}
}
void ScanI2CBus() {
printf("\nI2C Bus Scan\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
printf("%02x ", addr);
}
int ret;
uint8_t rxdata;
ret = i2c_read_blocking(I2C_PORT, addr, &rxdata, 1, false);
sleep_ms(10);
printf(ret < 0 ? "-" : "*");
printf(addr % 16 == 15 ? "\n" : " ");
}
}
void InitAdc()
{
adc_init();
adc_set_temp_sensor_enabled(true);
}
float ReadOnBoardTemperature()
{
adc_select_input(4);
float tempV = (float)adc_read() * ConversionFactor;
float tempC = 27.0f - (tempV - 0.706f) / 0.001721f;
return tempC;
}
void InitRtc()
{
char datetime_buf[256];
char *datetime_str = &datetime_buf[0];
//Start on 2023/5/9 Tuesday 10:24:00
datetime_t t = {
.year = 2023,
.month = 5,
.day = 9,
.dotw = 2, // 0:Sunday - 5:Friday
.hour = 10,
.min = 24,
.sec = 00
};
rtc_init();
rtc_set_datetime(&t);
sleep_ms(1);
}
//MCP9808 --------------------------------
#define MCP9808_ADDR 0x18
//hardware registers
const uint8_t REG_POINTER = 0x00;
const uint8_t REG_CONFIG = 0x01;
const uint8_t REG_TEMP_UPPER = 0x02;
const uint8_t REG_TEMP_LOWER = 0x03;
const uint8_t REG_TEMP_CRIT = 0x04;
const uint8_t REG_TEMP_AMB = 0x05;
const uint8_t REG_RESOLUTION = 0x08;
void mcp9808_check_limits(uint8_t upper_byte) {
// Check flags and raise alerts accordingly
if ((upper_byte & 0x40) == 0x40) { //TA > TUPPER
printf("Temperature is above the upper temperature limit.\n");
}
if ((upper_byte & 0x20) == 0x20) { //TA < TLOWER
printf("Temperature is below the lower temperature limit.\n");
}
if ((upper_byte & 0x80) == 0x80) { //TA > TCRIT
printf("Temperature is above the critical temperature limit.\n");
}
}
float mcp9808_convert_temp(uint8_t upper_byte, uint8_t lower_byte) {
float temperature;
//Check if TA <= 0°C and convert to denary accordingly
if ((upper_byte & 0x10) == 0x10) {
upper_byte = upper_byte & 0x0F;
temperature = 256 - (((float) upper_byte * 16) + ((float) lower_byte / 16));
} else {
temperature = (((float) upper_byte * 16) + ((float) lower_byte / 16));
}
return temperature;
}
void mcp9808_set_limits() {
//Set an upper limit of 30°C for the temperature
uint8_t upper_temp_msb = 0x01;
uint8_t upper_temp_lsb = 0xE0;
//Set a lower limit of 20°C for the temperature
uint8_t lower_temp_msb = 0x01;
uint8_t lower_temp_lsb = 0x40;
//Set a critical limit of 40°C for the temperature
uint8_t crit_temp_msb = 0x02;
uint8_t crit_temp_lsb = 0x80;
uint8_t buf[3];
buf[0] = REG_TEMP_UPPER;
buf[1] = upper_temp_msb;
buf[2] = upper_temp_lsb;
i2c_write_blocking(I2C_PORT, MCP9808_ADDR, buf, 3, false);
buf[0] = REG_TEMP_LOWER;
buf[1] = lower_temp_msb;
buf[2] = lower_temp_lsb;
i2c_write_blocking(I2C_PORT, MCP9808_ADDR, buf, 3, false);
buf[0] = REG_TEMP_CRIT;
buf[1] = crit_temp_msb;
buf[2] = crit_temp_lsb;;
i2c_write_blocking(I2C_PORT, MCP9808_ADDR, buf, 3, false);
}
//lcd -------------------------------
#define LCD_ADDR 0x27
// commands
const int LCD_CLEARDISPLAY = 0x01;
const int LCD_RETURNHOME = 0x02;
const int LCD_ENTRYMODESET = 0x04;
const int LCD_DISPLAYCONTROL = 0x08;
const int LCD_CURSORSHIFT = 0x10;
const int LCD_FUNCTIONSET = 0x20;
const int LCD_SETCGRAMADDR = 0x40;
const int LCD_SETDDRAMADDR = 0x80;
//display entry mode
const int LCD_ENTRYSHIFTINCREMENT = 0x01;
const int LCD_ENTRYLEFT = 0x02;
//display and cursor control
const int LCD_BLINKON = 0x01;
const int LCD_CURSORON = 0x02;
const int LCD_DISPLAYON = 0x04;
//display and cursor shift
const int LCD_MOVERIGHT = 0x04;
const int LCD_DISPLAYMOVE = 0x08;
//function set
const int LCD_5x10DOTS = 0x04;
const int LCD_2LINE = 0x08;
const int LCD_8BITMODE = 0x10;
//backlight control
const int LCD_BACKLIGHT = 0x08;
const int LCD_ENABLE_BIT = 0x04;
#define LCD_CHARACTER 1
#define LCD_COMMAND 0
//#define MAX_LINES 2
//#define MAX_CHARS 16
#define MAX_LINES 2
#define MAX_CHARS 16
#define DELAY_US 600
void i2c_write_byte(uint8_t val) {
i2c_write_blocking(I2C_PORT, LCD_ADDR, &val, 1, false);
}
void lcd_toggle_enable(uint8_t val) {
sleep_us(DELAY_US);
i2c_write_byte(val | LCD_ENABLE_BIT);
sleep_us(DELAY_US);
i2c_write_byte(val & ~LCD_ENABLE_BIT);
sleep_us(DELAY_US);
}
void lcd_send_byte(uint8_t val, int mode) {
uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;
i2c_write_byte(high);
lcd_toggle_enable(high);
i2c_write_byte(low);
lcd_toggle_enable(low);
}
void lcd_clear(void) {
lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
}
void lcd_set_cursor(int line, int position) {
int addr[4] = {0x80, 0xc0, 0x94, 0xd4};
int val = addr[line] + position;
lcd_send_byte(val, LCD_COMMAND);
}
static void inline lcd_char(char val) {
lcd_send_byte(val, LCD_CHARACTER);
}
void lcd_string(const char *s) {
while(true) {
if(*s == 0) {
break;
}
lcd_char(*s);
s++;
}
}
void lcd_init() {
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x03, LCD_COMMAND);
lcd_send_byte(0x02, LCD_COMMAND);
lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
lcd_clear();
}
float ReadMcpTemperature()
{
float temp;
uint8_t buf[2];
uint16_t upper_byte;
uint16_t lower_byte;
i2c_write_blocking(I2C_PORT, MCP9808_ADDR, ®_TEMP_AMB, 1, true);
i2c_read_blocking(I2C_PORT, MCP9808_ADDR, buf, 2, false);
upper_byte = buf[0];
lower_byte = buf[1];
//isolates limit flags in upper byte
mcp9808_check_limits(upper_byte & 0xE0);
//clears flag bits in upper byte
temp = mcp9808_convert_temp(upper_byte & 0x1F, lower_byte);
return temp;
}
int main()
{
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_put(LED_PIN, 0);
i2c_init(I2C_PORT, 400*1000);
gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
gpio_pull_up(I2C_SDA);
gpio_pull_up(I2C_SCL);
WaitTerminalStartup(30*1000);
printf("\nTerminal connected\n");
ScanI2CBus();
printf("I2C Scan completed\n");
lcd_init();
InitAdc();
InitRtc();
mcp9808_set_limits();
char buf[128];
datetime_t nowdt;
int presec = -1;
while (1) {
rtc_get_datetime(&nowdt);
if(presec != nowdt.sec)
{
float bTemp = ReadOnBoardTemperature();
float mcpTemp = ReadMcpTemperature();
sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d", nowdt.year, nowdt.month, nowdt.day, nowdt.hour,nowdt.min, nowdt.sec);
lcd_set_cursor(0, 0);
lcd_string(buf);
//0123456789012345
// MC:23.1 AD:12.5
sprintf(buf, " MC:%4.1f AD:%4.1f", mcpTemp, bTemp);
lcd_set_cursor(1, 0);
lcd_string(buf);
printf("%4d/%02d/%02d %02d:%02d:%02d mcp:%4.1fC adc:%4.1fC\n", nowdt.year, nowdt.month, nowdt.day,
nowdt.hour,nowdt.min, nowdt.sec, mcpTemp, bTemp);
}
presec = nowdt.sec;
sleep_ms(200);
}
return 0;
}

Picoオンボード温度センサー値に比べて、4℃程度高く出ています。変動は少なく、安定しています。
コメントをお書きください