前回(第3回) はデジタルなLED/ボタンでした。今回はいよいよ温度センサーです。ここが連載の山場になります。
Table of contents
Open Table of contents
なぜADC(MCP3208)が必要なのか
Raspberry Pi にはアナログ入力がありません。 GPIOはHigh/Lowのデジタルしか読めないため、電圧が連続的に変わるアナログセンサー LM35DZ(10mV/℃)の出力を直接は読めません。
そこで ADC(アナログ-デジタル変換IC)MCP3208 を挟みます。MCP3208は SPI接続の12bit ADCで、アナログ電圧を 0〜4095 の数値に変換してくれます。
SPIを有効化する
sudo raspi-config
Interface Options → SPI → Enable を選び、再起動します。
配線
MCP3208 は MCP3008 とピン配置が同じ(16ピン)なので、MCP3008の配線図がそのまま使えます。
- VDD → 3.3V
- VREF → 3.3V
- AGND → GND
- CLK → SCLK(GPIO11)
- DOUT → MISO(GPIO9)
- DIN → MOSI(GPIO10)
- CS/SHDN → CE0(GPIO8)
- DGND → GND
- CH0 → LM35DZ の出力ピン
LM35DZ(正面から見て左からVs・出力・GND):
- Vs → 5V(LM35は4V以上が必要。Piの5Vピンを使う)
- 出力 → MCP3208 の CH0
- GND → GND(Piと共通GNDに)

Iot.Device.Bindings を追加
dotnet add package Iot.Device.Bindings
温度を読むサービス
MCP3208をラップしたシングルトンサービスを作ります。
// Services/TemperatureService.cs
using System.Device.Spi;
using Iot.Device.Adc;
public sealed class TemperatureService : IDisposable
{
private const int Channel = 0; // LM35DZ を CH0 に接続
private const double VRef = 3.3; // MCP3208 の VREF
private const int MaxValue = 4096; // 12bit(0〜4095)
private readonly SpiDevice _spi;
private readonly Mcp3208 _adc;
private readonly object _gate = new(); // SPIはスレッドセーフでないため排他
public TemperatureService()
{
var settings = new SpiConnectionSettings(busId: 0, chipSelectLine: 0)
{
ClockFrequency = 1_000_000, // 速すぎると不安定。1MHz程度が無難
Mode = SpiMode.Mode0,
};
_spi = SpiDevice.Create(settings);
_adc = new Mcp3208(_spi);
}
public double ReadCelsius()
{
lock (_gate) // 複数スレッドから同時に読んでも壊れないよう直列化
{
int raw = _adc.Read(Channel); // 0〜4095
double volts = raw * VRef / MaxValue; // 数値 → 電圧
return volts * 100.0; // LM35: 10mV/℃ なので ×100
}
}
public void Dispose()
{
_adc.Dispose();
_spi.Dispose();
}
}
Program.cs に登録します。
builder.Services.AddSingleton<TemperatureService>();
換算のしくみ
- MCP3208は「入力電圧 ÷ VREF」を 0〜4095 で返す → 電圧 = raw ÷ 4096 × 3.3
- LM35DZ は 10mV/℃ → 温度(℃) = 電圧 × 100
- 例:25℃なら出力0.25V → raw ≈ 310 → 0.25V → 25℃
Blazorに温度を表示
@* Components/Pages/Temperature.razor *@
@page "/temperature"
@rendermode InteractiveServer
@implements IDisposable
@inject TemperatureService Temp
<h1>現在の温度</h1>
<p style="font-size:2rem"><b>@temperature.ToString("F1") ℃</b></p>
@code {
private double temperature;
private Timer? _timer;
protected override void OnInitialized()
{
_timer = new Timer(_ =>
{
temperature = Temp.ReadCelsius();
InvokeAsync(StateHasChanged);
}, null, 0, 1000); // 1秒ごとに更新
}
public void Dispose() => _timer?.Dispose();
}
実機で dotnet run --urls "http://0.0.0.0:5000" し、/temperature を開いて室温付近の値が出れば成功です。
💡 値が小刻みに揺れる場合は、数回読んで平均するとノイズが減って安定します。

つまずきポイント
- 正しい値と0が交互に出る/値が飛ぶ:まず疑うのは 配線の接触不良(ADCの挿し込み不足・ジャンパのゆるみ・ブレッドボードの接触)。しっかり挿し直すと安定します。クロックが速すぎる場合もあるので、上のように
ClockFrequencyを明示(500_000も試す) - SPIが有効か:
raspi-configでの有効化+再起動を確認。/dev/spidev0.0が無ければ未有効 - スレッド安全性:
SpiDevice/Mcp3208はスレッドセーフではありません。複数箇所(ページのTimerと第5回のバックグラウンド計測)から読むなら、上のコードのlockで直列化しておくと、同時アクセス時の断続的な不具合を防げます - 値が0や最大に張り付く:配線(CLK/DOUT/DIN/CS)とCH0、共通GNDを確認
- 温度がずれる:VREF=3.3Vと換算式(×100)を確認。LM35のVsは5V
- 開発PCでは動かない:SPI/ADCは実機(Pi)で
まとめ
- Piはアナログ入力を持たないため、LM35DZ は MCP3208(SPI ADC)経由で読む
Iot.Device.BindingsのMcp3208.Read(ch)が 0〜4095 を返す- 温度(℃) = raw ÷ 4096 × 3.3 × 100(LM35は10mV/℃)
- 温度取得もDIサービス化して、Blazorから1秒ごとに表示
次回(第5回)は、この温度をSignalRでリアルタイムのライブグラフにします。ポーリングより滑らかで、Blazor Serverの強みが活きる回です。
