ESP32 Embedded Rust در HAL: سنجش دمای آنالوگ با استفاده از ADC

این پست وبلاگ ششمین از یک سری پست های چند قسمتی است که در آن من به بررسی تجهیزات جانبی مختلف در ESP32C3 با استفاده از Rust تعبیه شده در سطح HAL می پردازم. لطفاً توجه داشته باشید که برخی از مفاهیم در پست های جدیدتر می تواند به مفاهیم پست های قبلی بستگی داشته باشد.
اگر این پست را مفید میدانید و برای بهروز ماندن با پستهای مشابه، لیست کانالهایی را که میتوانید دنبال کنید/در آنها مشترک شوید، آمده است:
معرفی
در این پست، من یک ADC esp32c3-hal را برای اندازه گیری دمای محیط با استفاده از ترمیستور 10k NTC پیکربندی و راه اندازی می کنم. اندازه گیری های دما به طور مداوم جمع آوری و به خروجی ترمینال ارسال می شود. برای خروجی ترمینال، من از جعبه esp-println که در آخرین پست شروع کردم استفاده خواهم کرد. علاوه بر این، من از هیچ وقفه ای استفاده نخواهم کرد و نمونه به عنوان یک سیستم سیمپلکس تنظیم می شود که فقط در یک جهت (به سمت ترمینال/رایانه) ارسال می کند.
📚 پیش نیاز دانش
برای درک محتوای این پست به موارد زیر نیاز دارید:
-
دانش اولیه کد نویسی در Rust.
-
آشنایی با قالب اولیه ساخت اپلیکیشن های جاسازی شده در Rust.
-
آشنایی با اصول کار ترمیستورهای NTC. این صفحه منبع خوبی است.
💾 راه اندازی نرم افزار
تمام کدهای ارائه شده در این پست در سایت موجود است Apollolabs ESP32C3 git repo. توجه داشته باشید که اگر کد موجود در git repo کمی متفاوت باشد، به این معنی است که برای بهبود کیفیت کد یا بهروزرسانیهای HAL/Rust اصلاح شده است.
علاوه بر این، پروژه کامل (کد و شبیه سازی) در Wokwi موجود است اینجا.
علاوه بر موارد فوق، شما باید نوعی ترمینال ارتباطی سریال را بر روی کامپیوتر میزبان خود نصب کنید. برخی از توصیه ها عبارتند از:
برای ویندوز:
برای مک و لینوکس:
برخی از دستورالعمل های نصب برای سیستم عامل های مختلف در کتاب کشف موجود است.
🛠 راه اندازی سخت افزار
مواد
- سنسور دمای 10k NTC
⚡ اتصالات
- پین سیگنال سنسور دما به پین gpio1 متصل است. در Wokwi این یک ارتباط مستقیم است. با این حال، اگر جزء NTC جداگانه دارید، باید آن را در پیکربندی تقسیم کننده ولتاژ با یک مقاومت 10K تنظیم کنید (مدار در بخش بعدی).
🔌 آنالیز مدار
سنسور دما مورد استفاده یک سنسور ضریب دمای منفی (NTC) است. این بدان معنی است که مقاومت سنسور با افزایش دما افزایش می یابد. شکل زیر شماتیک مدار سنسور دما را نشان می دهد:
نشان داده شده است که ترمیستور NTC در یک پیکربندی تقسیم کننده ولتاژ با یک مقاومت 10k متصل است. به این ترتیب، ولتاژ در ترمینال مثبت op-amp
برابر ولتاژ ترمینال سیگنال است و به صورت زیر بیان می شود:
جایی که
و مقدار مقاومت از
چیزی است که برای بدست آوردن دما باید محاسبه شود. این به این معنی است که بعداً در کد، باید مقدار of را بازیابی کنم
از
مقداری که توسط ADC خوانده می شود. با کمی دستکاری جبری، می توانیم همه متغیرهای شناخته شده را به سمت راست معادله منتقل کنیم تا به عبارت زیر برسیم:
پس از استخراج مقدار
، من باید دما را تعیین کنم. به دنبال معادلات موجود در دیتاشیت، از معادله Steinhart-Hart NTC استفاده می کنم که به صورت زیر ارائه شده است:
جایی که
ثابت و برابر با 3950 برای NTC ما همانطور که توسط Wokwi و بیان شده است
دمایی است که ما اندازه گیری می کنیم.
و
به ترتیب به دمای محیط (معمولاً 25 درجه سانتیگراد) و مقاومت اسمی در دمای محیط اشاره کنید. مقدار مقاومت در 25 درجه سانتیگراد (
) برابر است با
(
). با دستکاری جبری بیشتر حل می کنیم
برای بدست آوردن:
👨🎨 طراحی نرم افزار
اکنون که معادلات بخش قبل را می دانیم، یک الگوریتم نیاز به توسعه دارد و در این مورد کاملاً ساده است. پس از پیکربندی دستگاه، مراحل الگوریتمی به شرح زیر است:
-
ADC را شروع کنید و خواندن/نمونه بگیرید.
-
دما را بر حسب سلسیوس محاسبه کنید.
-
مقدار دما را روی ترمینال چاپ کنید.
-
به مرحله 1 برگردید.
👨💻پیاده سازی کد
📥 واردات جعبه
در این اجرا، جعبه های زیر مورد نیاز است:
-
را
esp32c3_hal
جعبه برای وارد کردن انتزاعات سخت افزاری دستگاه ESP32C3. -
را
esp_backtrace
جعبه برای تعریف رفتار هراس. -
را
esp_println
جعبه برای ارائهprintln!
پیاده سازی. -
را
libm
جعبه برای ارائه پیاده سازی برای یک لگاریتم طبیعی.
use esp32c3_hal::{
clock::ClockControl, peripherals::Peripherals, prelude::*, systimer::SystemTimer,
timer::TimerGroup, Delay, Rtc, IO,
};
use esp_backtrace as _;
use esp_println::println;
use libm::log;
🎛 کد اولیه/پیکربندی
⌨️ تنظیمات جانبی GPIO:
1️⃣ یک دسته برای لوازم جانبی دستگاه تهیه کنید: در Rust تعبیه شده، به عنوان بخشی از الگوی طراحی تک تن، ابتدا باید لوازم جانبی دستگاه در سطح PAC را انتخاب کنیم. این کار با استفاده از take()
روش. در اینجا من یک دستگاه کنترل کننده جانبی به نام ایجاد می کنم dp
به شرح زیر است:
let peripherals = Peripherals::take();
2️⃣ Watchdogs را غیرفعال کنید: ESP32C3 دارای Watchdogs به طور پیش فرض فعال است و باید غیرفعال شوند. اگر آنها غیرفعال نباشند، دستگاه به تنظیم مجدد ادامه خواهد داد. برای جلوگیری از این مشکل، کد زیر باید وارد شود:
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Instantiate and Create Handles for the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
3️⃣ Instantate و ایجاد Handle برای IO: باید پین NTC را به عنوان ورودی آنالوگ پیکربندی کنیم و یک هندلر برای پین بگیریم تا بتوانیم آن را کنترل کنیم. این کار در مرحله زیر انجام خواهد شد. اگرچه قبل از اینکه بتوانیم هر دسته ای برای NTC و دکمه ای به دست آوریم، باید یک آن را ایجاد کنیم IO
نمونه ساختار را IO
نمونه struct یک ساختار طراحی شده با HAL را ارائه می دهد که به ما امکان دسترسی به تمام پین های gpio را می دهد و بنابراین ما را قادر می سازد تا برای پین های جداگانه ایجاد کنیم. این شبیه به مفهوم a است split
روش مورد استفاده در سایر HAL ها (جزئیات بیشتر اینجا). ما این کار را با فراخوانی انجام می دهیم new()
روش نمونه بر روی IO
ساختار به صورت زیر است:
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
4️⃣ پیکربندی و ایجاد دسته برای پین آنالوگ: مشابه نحوه پیکربندی پینها قبلاً با gpio، به جای آن یک وجود دارد into_analog()
روشی که پین را به عنوان پین آنالوگ پیکربندی می کند. یک ntc_pin
دسته ایجاد می شود تا gpio1
به و پین آنالوگ به شرح زیر است:
let ntc = io.pins.gpio1.into_analog();
تنظیمات جانبی ADC:
1️⃣ یک دسته برای پیکربندی ADC تهیه کنید: برای پیکربندی یک پین آنالوگ در esp32c3-hal، ابتدا باید یک نمونه پیکربندی ADC ایجاد شود. بعداً از همان نمونه پیکربندی برای فعال کردن پین آنالوگ و ایجاد یک نمونه ADC استفاده میشود. به این ترتیب، یک adc_config
دسته با استفاده از ایجاد می شود AdcConfig
نوع new
روش به شرح زیر است:
// Create handle for ADC configuration parameters
let mut adc_config = AdcConfig::new();
2️⃣ یک دسته بگیرید و پین آنالوگ را فعال کنید: به منظور فعال کردن آنالوگ ntc_pin
پین، AdcConfig
نوع دارای یک enable_pin
روشی که دو آرگومان می گیرد. آرگومان اول پین آنالوگ gpio و دومی an است Attenuation
enum که سطح مورد نظر تضعیف را مشخص می کند:
let mut adc_pin =
adc_config.enable_pin(
ntc,
Attenuation::Attenuation0dB
);
3️⃣ یک دسته را بدست آورید و یک نمونه ADC را پیکربندی کنید: قبل از ایجاد یک نمونه ADC، مشابه برخی از تجهیزات جانبی دیگر، دستگاه جانبی باید به ساختارهای سطح HAL ارتقا یابد. این کار با استفاده از split
روش در نوع جانبی APB_SARADC (اگر آشنا نیستید، این پست قبلی من را در توضیح روش های تقسیم و محدود بخوانید) به شرح زیر است:
// Promote ADC peripheral to HAL-level Struct
let analog = peripherals.APB_SARADC.split();
اکنون که دستگاه جانبی تقسیم شده است، ما به ADC فردی دسترسی داریم تا به یک نمونه ADC منتقل کنیم. در نتیجه، برای ایجاد یک نمونه ADC یک وجود دارد adc
روش به عنوان بخشی از ADC
esp32c3-hal را تایپ کنید. را adc
متد سه آرگومان می گیرد، یک نمونه کنترل کننده ساعت محیطی (که از طریق system
handle)، یک نمونه ADC (دسترسی از طریق analog
دسته)، و یک نمونه پیکربندی ADC (the adc_config
رسیدگی):
let mut adc = ADC::adc(
&mut system.peripheral_clock_control,
analog.adc1,
adc_config,
)
.unwrap();
این برای پیکربندی است! حالا بیایید وارد کد برنامه شویم.
📱کد اپلیکیشن
به دنبال طرحی که قبلا توضیح داده شد، قبل از ورود به من loop
، ابتدا باید چند ثابت را تنظیم کنم که در محاسبات خود از آنها استفاده خواهم کرد. این شامل کلید زدن مقادیر ثابت برای است
و
به شرح زیر است:
const B: f64 = 3950.0; // B value of the thermistor
const R0: f64 = 10000.0; // Nominal NTC Value
پس از ورود به حلقه برنامه، همانطور که در طراحی نرم افزار قبلاً گفته شد، اولین کاری که باید انجام دهم این است که ADC را برای به دست آوردن یک نمونه/خواندن انجام دهم. این کار از طریق انجام می شود read
روشی که یک ارجاع قابل تغییر به adc_pin
نمونه و برمی گرداند a Result
:
let sample: u16 = adc.read(&mut adc_pin).unwrap();
سپس، مقدار نمونه را با اجرای معادلات مشتق شده قبلی به صورت زیر به دما تبدیل می کنم:
let temperature = 1. / (log(1. / (4096. / sample as f64 - 1.)) / B + 1.0 / 298.15) - 273.15;
در اینجا چند نکته قابل ذکر است؛ ابتدا نمونه جمعآوریشده را به مقدار به ولتاژ تبدیل نمیکنم زیرا در اولین محاسبه محاسبه ولتاژ یک نسبت است. این به این معنی است که من را نگه می دارم sample
در LSB ها و از مقدار LSB معادل برای استفاده کنید
. برای وصل کردن
من به سادگی حداکثر مقدار ممکن LSB (مرجع بالایی) را که می تواند توسط ADC ایجاد شود محاسبه می کنم. به همین دلیل است که من باید رزولوشن را بدانم که 12 بود زیرا
. دوم، یادآوری از read
روشی که sample
هست یک u16
، بنابراین مجبور شدم استفاده کنم as f64
آن را به عنوان بازیگران f64
برای محاسبه سوم، log
لگاریتم طبیعی است که از libm
کتابخانه ای که قبلا وارد کردم. چهارم و آخر، دما بر حسب کلوین محاسبه می شود 273.15
چیزی است که آن را به سلسیوس تبدیل می کند.
در نهایت، اکنون که دما در دسترس است، آن را با استفاده از آن به کنسول ارسال می کنم println!
ماکرو به صورت زیر
println!("Temperature {:02} Celcius\r", temperature);
این است!
📱کد برنامه کامل
در اینجا کد کامل پیاده سازی شرح داده شده در این پست است. همچنین میتوانید پروژه کامل و سایر پروژههای موجود را در آن بیابید Apollolabs ESP32C3 git repo. همچنین پروژه Wokwi قابل دسترسی است اینجا.
#![no_std]
#![no_main]
use esp32c3_hal::{
adc::{AdcConfig, Attenuation, ADC},
clock::ClockControl,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Rtc, IO,
};
use esp_backtrace as _;
use esp_println::println;
use libm::log;
#[entry]
fn main() -> ! {
// Take Peripherals, Initialize Clocks, and Create a Handle for Each
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Instantiate and Create Handles for the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
// Disable the RTC and TIMG watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
// Instantiate and Create Handle for IO
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Create ADC Instance
// Create handle for ADC configuration parameters
let mut adc_config = AdcConfig::new();
// Configure ADC pin
let mut adc_pin =
adc_config.enable_pin(io.pins.gpio1.into_analog(), Attenuation::Attenuation0dB);
// Promote ADC peripheral to HAL-level Struct
let analog = peripherals.APB_SARADC.split();
// Create handle for ADC, configuring clock, and passing configuration handle
let mut adc = ADC::adc(
&mut system.peripheral_clock_control,
analog.adc1,
adc_config,
)
.unwrap();
const B: f64 = 3950.0; // B value of the thermistor
const R0: f64 = 10000.0; // Nominal NTC Value
// Algorithm
// 1) Get adc reading
// 2) Convert to temperature
// 3) Send over Serial
// 4) Go Back to step 1
// Application
loop {
// Get ADC reading
let sample: u16 = adc.read(&mut adc_pin).unwrap();
// For blocking read
// let sample: u16 = nb::block!(adc.read(&mut adc_pin)).unwrap();
//Convert to temperature
let temperature = 1. / (log(1. / (4096. / sample as f64 - 1.)) / B + 1.0 / 298.15) - 273.15;
// Print the temperature output
println!("Temperature {:02} Celcius\r", temperature);
}
}
نتیجه
در این پست، یک برنامه اندازه گیری دما آنالوگ با استفاده از ابزار جانبی ADC برای ESP32C3 ایجاد شد. اندازه گیری حاصل نیز به خروجی ترمینال ارسال می شود. همه کدها بر اساس نظرسنجی (بدون وقفه) بود. علاوه بر این، تمام کدها در سطح HAL با استفاده از esp32c3-hal ایجاد شدند. سوالی دارید؟ نظرات خود را در نظرات زیر به اشتراک بگذارید 👇.
اگر این پست را مفید دیدید، و برای بهروز ماندن از پستهای مشابه، فهرست کانالهایی که میتوانید دنبال کنید/عضو شوید در اینجا آمده است: