こんにちは、ENGかぴです。
Arduinoの標準ライブラリで7セグメントLEDに通知を表示することができます。数値の表示はDO制御をパターン化することで実現できます。温度センサーの電圧をAD変換して温度情報を7セグメントLEDに表示する方法をまとめました。
温度センサーにはMCP9700Aを使っています。MCP9700Aの出力電圧を温度情報に変換して7セグメントLEDに表示します。表示できる温度範囲は0~99℃までとします。
Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
7セグメントLEDに温度を表示する
7セグメントLEDは小数点を含めると8つのLED(8セグメント)で構成されています。数値を表示するためには該当する番号のLEDを点灯させる必要があります。7セグメントLEDをの使い方の詳細は下記の記事にまとめています。
温度データを取得する
ArduinoのA0を使ってMCP9700Aの出力電圧をAD変換します。Arduinoの標準ライブラリであるanalogRead()を使用することでAD変換することができます。
#define PIN_A0 A0
uint16_t sensor;
sensor = analogRead(PIN_A0); //A0に入力している電圧をAD変換した結果
ArduinoのAD変換は10ビットになります。AD変換に使用する基準電圧であるVAREFに対して入力された電圧値の計算方法すると、$$\frac{AD変換値}{1023}×V_{AREF}・・(1)$$となります。(1)を使って1デジット分の電圧変化を計算します。
AD変換値に1を代入し、VAREF電圧(テスターで確認した値)の4.86Vを代入して計算すると0.0047Vとなります。MCP9700Aは1℃/10mVで電圧出力するためデジット値が±2変化しただけでも1℃程度の表示が切り替わります。
広告
AD変換した値を平均化する
アナログデータはノイズなどの外部要因で値が頻繁に変化してしまいます。今回使用するMCP9700Aの出力電圧を10ビットのAD変換で表現しようとすると2デジットの差が大きく出てしまうため値を平均化して値のばらつきを押さえます。
リングポインタによる方法で複数回取得したアナログデータの平均を計算して採用する単純な方法ですが、平均をとるとローパスフィルタの効果が得られるためノイズ成分の除去に有効です。
#define ANALOG_SZ 32
struct RING_TYP
{
uint8_t wp;
uint16_t data[ANALOG_SZ];
uint16_t meas;
};
RING_TYP sensor;
void AnalogSet(){
uint32_t sum;
uint8_t i;
sensor.data[sensor.wp]= analogRead(PIN_A0);
sum = 0;
for( i=0; i < ANALOG_SZ; i++ ){
sum += sensor.data[i]; //合計値を出す
}
sensor.meas = sum >> 5; //5回右にシフト(32で割る)
if( ++sensor.wp >= ANALOG_SZ){ //次に格納する場所を更新する
sensor.wp = 0;
}
}
AnalogSet()はアナログデータを取得して平均値を計算する関数として実装しています。平均するデータ数は32個としているため合計値を出した後に32で割る必要があります。
平均を計算するテクニックとしてシフト演算を使用しています。マイコンが演算する際にビットを操作するだけなので計算処理が早くなります。
そのため計算する対象データ数を2の倍数にしておくと計算処理にかかる時間を削減することができます。
アナログデータを温度データを変換する
AD変換した値からMCP9700Aの出力電圧に換算することで温度が分かります。AD変換して平均化した値が150であったことを前提に温度データに換算していきます。式(1)より$$\frac{150}{1023}×4.86=0.713[V]$$になります。
計算すると小数点がでるため浮動小数点があつかえるfloatやdoubleを使えば計算することができますが、計算専用の演算器を持っていないマイコンにおいては処理が重たくなってしまいます。
この対策として小数点以下が発生しないように10・100・1000倍値などディケード(10を底とする対数)をとることが有効です。
MCP9700Aは1℃/10mVで出力電圧が変化するので10mVよりも下の桁まで管理した方が良いため1000倍値とします。またVAREFを測定すると4.86Vだったので100倍値として(1)を計算すると$$\frac{150}{1023}×1000×486=71260$$となります。
これは全体で100,000倍した値になっています。MCP9700Aのデータシートを確認すると0℃のとき0.5Vであり100,000倍した値を引くと21260になります。
int32_t Temp(uint16_t value){
int32_t ret;
ret = (int32_t)value*HOSEI/0x3FF;
ret = ret*AREF_VCC;
ret -= ZERO_TEMP; //基準温度の0℃分を差し引く
ret /= 100; //100倍値した分を戻している
return ret;
}
Temp()関数では計算が終わった場合の100,000倍値のうちVAREF分の100倍を戻して戻り値としています。全体として1000倍値となるため212になります。
void Led7SegMng(){
int32_t tempdata;
uint8_t no;
uint8_t no10;
uint8_t no100;
tempdata = Temp(sensor.meas);
no100 = tempdata / 100; //100の位
no10 = (tempdata % 100 ) / 10; //10の位
no = tempdata % 10; //1の位
}
212から温度を読み取ると1000倍値であることや1℃/10mVである条件から本来の値である0.212を100倍したものが温度データとなるため21.2℃になります。計算のしやすさから1000倍値のまま各桁に表示する数値を計算しています。
no10になっている桁が温度データの1桁目に対応するため7セグメントLEDにおいてDP部分を表示することで小数点を表現できます。
7セグメントで数値を表現する方法(DPを含む)
7セグメントLEDで数値とDOTを表現するDOを8本準備する必要があります。a~gで0~9を表現するパターンを作り最上位ビットがセットされた時DPを使用できるようにするためめ1バイトデータになるようにピンを配置します。数値のパターンの作り方は下記記事を参考にしてください。
ここではDPの点灯する条件を追加した箇所を中心に説明します。
#define LED7SEG_NEGATIVE //負論理の時コメントアウトを外す
#define SEGDOT_MASK 0x80
uint8_t dotmask;
void setup(){
#ifdef LED7SEG_NEGATIVE //負論理の場合
for(uint8_t i=0; i < sizeof(Seg7Set); i++){
Seg7Set[i] = (~Seg7Set[i] & 0x7F) | SEGDOT_MASK;
}
dotmask = 0x7F;
#else
dotmask = 0x80;
#endif
}
負論理と正論理ではビットの扱い方が反対になるためDPを表示する条件も論理が反転します。DP部分のLEDは負論理の場合は最上位ビットが0になるとLEDが点灯し、正論理の場合は1になると点灯します。
void Led7SegMng(){
uint8_t out_off;
uint8_t out_on;
#ifdef LED7SEG_NEGATIVE
out_on = HIGH;
out_off = LOW;
#else
out_on = LOW;
out_off = HIGH
#endif
digitalWrite(PIN_DP3,out_on); //右から2番目の桁を選択
#ifdef LED7SEG_NEGATIVE
Led7SegSet(Seg7Set[no10] & dotmask); //論理積をとって最上ビットを0にして点灯
#else
Led7SegSet(Seg7Set[no10] | dotmask); //論理和をとって最上ビットを1にして点灯
#endif
digitalWrite(PIN_DP3,out_off); //右から2番目の桁の選択を解除
}
7セグメントLEDの右から2番目を選択したときにDPを含めてLEDを点灯させるために負論理の7セグメントLEDの場合は論理積をとって最上位ビットが0になるようにします。
正論理の場合は論理和をとって最上位ビットが1になるようにセットすることで小数点を表現することができます。
動作確認
7セグメントLEDに温度センサーから取得した温度データを表示します。7セグメントLEDは負論理のものだったのでトランジスタをエミッタフォロワとして使用しています。
MCP9700Aは電源電圧が3.3V時のデータが参考として記載されているため電源を3.3Vにしています。
7セグメントLEDに温度データが20.4℃が表示されているのが確認できました。指で温度センサーに触れると温度が上昇して表示されたため測定できていることが分かりました。また室温を見ると20.3℃だったのでそれなりに正確に測定できていました。
表示する温度範囲を0℃~99℃にしていますが、-の温度まで対応されることもできます。その場合は4桁目に-(7セグメントLEDのg)を表示するパターンを作って表示させることで対応できますが、測定方法がないため実装していません。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <MsTimer2.h>
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define CNT_MAX 100
#define ANALOG_MAX 5
#define PIN_DO_A 2
#define PIN_DO_B 3
#define PIN_DO_C 4
#define PIN_DO_D 5
#define PIN_DO_E 6
#define PIN_DO_F 7
#define PIN_DO_G 8
#define PIN_DOT 9
#define PIN_DP4 10
#define PIN_DP3 11
#define PIN_DP2 12
#define LED7SEG_NEGATIVE //負論理の時コメントアウトを外す
#define SEGDOT_MASK 0x80
#define PIN_A0 A0
#define ANALOG_SZ 32
#define HOSEI 1000
#define AREF_VCC 486 //4.86Vの100倍値
#define ZERO_TEMP 50000 //0.5Vの100000倍値
typedef enum{
COL_NO1 = 0,
COL_NO2,
COL_NO3,
COL_NO4,
COL_MAX
}COL_NO;
struct RING_TYP
{
uint8_t wp;
uint16_t data[ANALOG_SZ];
uint16_t meas;
};
byte Seg7Set[10]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x27, 0x7F, 0x6F};
int8_t cnt10ms;
int8_t timanalog = TIME_OFF;
COL_NO col;
RING_TYP sensor;
uint8_t dotmask;
/*** Local function prototypes */
void TimerCnt();
void mainTimer();
void Led7SegClr();
void Led7SegSet(uint8_t no);
void Led7SegMng();
void AnalogSet();
int32_t Temp(uint16_t value);
void setup(){
uint8_t cnt;
Serial.begin(115200);
pinMode(PIN_DP4,OUTPUT); //1桁目の表示(右側)
pinMode(PIN_DP3,OUTPUT); //2桁目の表示(右から2番目)
pinMode(PIN_DP2,OUTPUT); //3桁目の表示(右から3番目)
pinMode(PIN_DO_A,OUTPUT); //7セグメントのa部分のDO
pinMode(PIN_DO_B,OUTPUT); //7セグメントのb部分のDO
pinMode(PIN_DO_C,OUTPUT); //7セグメントのc部分のDO
pinMode(PIN_DO_D,OUTPUT); //7セグメントのd部分のDO
pinMode(PIN_DO_E,OUTPUT); //7セグメントのe部分のDO
pinMode(PIN_DO_F,OUTPUT); //7セグメントのf部分のDO
pinMode(PIN_DO_G,OUTPUT); //7セグメントのg部分のDO
pinMode(PIN_DOT,OUTPUT); //7セグメントのDOT部分のDO
MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
MsTimer2::start();
#ifdef LED7SEG_NEGATIVE
for(uint8_t i=0; i < sizeof(Seg7Set); i++){
Seg7Set[i] = (~Seg7Set[i] & 0x7F) | SEGDOT_MASK;
}
dotmask = ~SEGDOT_MASK;
#else
dotmask = SEGDOT_MASK;
#endif
Led7SegClr();
timanalog = ANALOG_MAX;
while ( cnt <= ANALOG_SZ)
{
mainTimer();
if(timanalog == TIME_UP){
timanalog = ANALOG_MAX;
AnalogSet();
++cnt;
}
}
}
void loop(){
mainTimer();
Led7SegMng();
if(timanalog == TIME_UP){
timanalog = ANALOG_MAX;
AnalogSet();
}
}
/* callback function add */
void TimerCnt(){
++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){
if( cnt10ms >= BASE_CNT ){
cnt10ms -=BASE_CNT; //10msごとにここに遷移する
if( timanalog > TIME_UP){
--timanalog;
}
}
}
void AnalogSet(){
uint32_t sum;
uint8_t i;
sensor.data[sensor.wp]= analogRead(PIN_A0);
sum = 0;
for( i=0; i < ANALOG_SZ; i++ ){
sum += sensor.data[i]; //合計値を出す
}
sensor.meas = sum >> 5; //5回右にシフト(32で割る)
if( ++sensor.wp >= ANALOG_SZ){ //次に格納する場所を更新する
sensor.wp = 0;
}
}
int32_t Temp(uint16_t value){
int32_t ret;
ret = (int32_t)value*HOSEI/0x3FF;
ret = ret*AREF_VCC;
ret -= ZERO_TEMP;
ret /= 100; //100倍値した分を戻している
return ret;
}
/* Led7Seg Management function add */
void Led7SegMng(){
uint8_t no;
uint8_t no10;
uint8_t no100;
uint8_t out_off;
uint8_t out_on;
int32_t tempdata;
bool loopflg=true;
#ifdef LED7SEG_NEGATIVE
out_on = HIGH;
out_off = LOW;
#else
out_on = LOW;
out_off = HIGH
#endif
tempdata = Temp(sensor.meas);
no100 = tempdata / 100; //10の位
no10 = (tempdata % 100 ) / 10; //10の位
no = tempdata % 10; //1の位
do{
switch (col)
{
case COL_NO::COL_NO1:
digitalWrite(PIN_DP4,out_on);
Led7SegSet(Seg7Set[no]);
digitalWrite(PIN_DP4,out_off);
col = COL_NO::COL_NO2;
break;
case COL_NO::COL_NO2:
digitalWrite(PIN_DP3,out_on); //右から2番目の桁を選択
#ifdef LED7SEG_NEGATIVE
Led7SegSet(Seg7Set[no10] & dotmask);
#else
Led7SegSet(Seg7Set[no10] | dotmask);
#endif
digitalWrite(PIN_DP3,out_off); //右から2番目の桁の選択を解除
col = COL_NO::COL_NO3;
break;
case COL_NO::COL_NO3:
digitalWrite(PIN_DP2,out_on); //右から3番目の桁を選択
Led7SegSet(Seg7Set[no100] );
digitalWrite(PIN_DP2,out_off); //右から3番目の桁の選択を解除
col = COL_NO::COL_NO1;
loopflg = false;
break;
default:
break;
}
Led7SegClr();
}while(loopflg);
}
/* Led7Seg Clear function add */
void Led7SegClr(){
uint8_t i;
uint8_t out;
#ifdef LED7SEG_NEGATIVE
out = HIGH;
#else
out = LOW;
#endif
for( i = 0; i < 8; i++){
digitalWrite(i + PIN_DO_A, out);
}
}
/* Led7Seg pattern function add */
void Led7SegSet(uint8_t no){
uint8_t i;
uint8_t bit;
for( i=0; i < 8; i++){
bit = 1 << i;
if( bit & no){
digitalWrite(i + PIN_DO_A, HIGH);
}
else{
digitalWrite(i + PIN_DO_A, LOW);
}
}
}
手持ちの7セグメントLEDが負論理しかないため正論理の場合についても対応できるように作っていますがデバッグできていません。基本的に論理を反対にすると動作しますので参考になると思います。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
最後まで、読んでいただきありがとうございました。
AD変換器が10ビットなので小数点以下の分解能が少し荒くなるため小数点を刻むような正確な温度表示は難しいのですが、小数点以下は目安程度なのでているので満足しています。LCDよりも7セグメントLEDで数値を表現すると迫力があるなと感じました。