برنامه نویسی

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 موجود است اینجا.

علاوه بر موارد فوق، شما باید نوعی ترمینال ارتباطی سریال را بر روی کامپیوتر میزبان خود نصب کنید. برخی از توصیه ها عبارتند از:

برای ویندوز:

برای مک و لینوکس:

برخی از دستورالعمل های نصب برای سیستم عامل های مختلف در کتاب کشف موجود است.

🛠 راه اندازی سخت افزار

مواد

ESP32C3 Devkit

  • سنسور دمای 10k NTC

⚡ اتصالات

  • پین سیگنال سنسور دما به پین ​​gpio1 متصل است. در Wokwi این یک ارتباط مستقیم است. با این حال، اگر جزء NTC جداگانه دارید، باید آن را در پیکربندی تقسیم کننده ولتاژ با یک مقاومت 10K تنظیم کنید (مدار در بخش بعدی).

🔌 آنالیز مدار

سنسور دما مورد استفاده یک سنسور ضریب دمای منفی (NTC) است. این بدان معنی است که مقاومت سنسور با افزایش دما افزایش می یابد. شکل زیر شماتیک مدار سنسور دما را نشان می دهد:

مدار NTC

نشان داده شده است که ترمیستور NTC در یک پیکربندی تقسیم کننده ولتاژ با یک مقاومت 10k متصل است. به این ترتیب، ولتاژ در ترمینال مثبت op-amp


V+V_{+}

برابر ولتاژ ترمینال سیگنال است و به صورت زیر بیان می شود:

V+=Vججآر1آر1+آرNTCV_{\text{+}} = V_{cc}* \frac{R_{1}}{R_{1}} + R_{\text{NTC}}

جایی که

آر1=10کاوهR_1 = 10k\Omega

و مقدار مقاومت از

(آرNTC(R_{\text{NTC}}

چیزی است که برای بدست آوردن دما باید محاسبه شود. این به این معنی است که بعداً در کد، باید مقدار of را بازیابی کنم

آرNTCR_{\text{NTC}}

از

V+V_{\text{+}}

مقداری که توسط ADC خوانده می شود. با کمی دستکاری جبری، می توانیم همه متغیرهای شناخته شده را به سمت راست معادله منتقل کنیم تا به عبارت زیر برسیم:

آرNTC=(VججV+1)آر1R_{\text{NTC}} = \left( \frac{ V_{cc} }{ V_{\text{+}} } -1 \راست) * R_{1}

پس از استخراج مقدار

آرNTCR_{\text{NTC}}

، من باید دما را تعیین کنم. به دنبال معادلات موجود در دیتاشیت، از معادله Steinhart-Hart NTC استفاده می کنم که به صورت زیر ارائه شده است:

ب=لn(آرNTCآر0)(1تی1تی0)\beta = \frac{ln(\frac{R_{\text{NTC}}}{R_0})}{(\frac{1}{T}-\frac{1}{T_0})}

جایی که

ب\بتا

ثابت و برابر با 3950 برای NTC ما همانطور که توسط Wokwi و بیان شده است

تیتی

دمایی است که ما اندازه گیری می کنیم.

تی0T_0

و

آر0R_0

به ترتیب به دمای محیط (معمولاً 25 درجه سانتیگراد) و مقاومت اسمی در دمای محیط اشاره کنید. مقدار مقاومت در 25 درجه سانتیگراد (

تی0T_0

) برابر است با

10کاوه10k\ امگا

(

آر0R_0

). با دستکاری جبری بیشتر حل می کنیم

تیتی

برای بدست آوردن:

تی=11بلn(آرNTCآر0)+1تی0T = \frac{1}{\frac{1}{\beta} * ln(\frac{R_{\text{NTC}}}{R_0}) +\frac{1}{T_0}}

👨‍🎨 طراحی نرم افزار

اکنون که معادلات بخش قبل را می دانیم، یک الگوریتم نیاز به توسعه دارد و در این مورد کاملاً ساده است. پس از پیکربندی دستگاه، مراحل الگوریتمی به شرح زیر است:

  1. ADC را شروع کنید و خواندن/نمونه بگیرید.

  2. دما را بر حسب سلسیوس محاسبه کنید.

  3. مقدار دما را روی ترمینال چاپ کنید.

  4. به مرحله 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، ابتدا باید چند ثابت را تنظیم کنم که در محاسبات خود از آنها استفاده خواهم کرد. این شامل کلید زدن مقادیر ثابت برای است

ب\بتا

و

آر0R_0

به شرح زیر است:

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 معادل برای استفاده کنید

VججV_{cc}

. برای وصل کردن

VججV_{cc}

من به سادگی حداکثر مقدار ممکن LSB (مرجع بالایی) را که می تواند توسط ADC ایجاد شود محاسبه می کنم. به همین دلیل است که من باید رزولوشن را بدانم که 12 بود زیرا

Vجج=212LاسبسV_{cc} = 2^{12} LSB

. دوم، یادآوری از 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 ایجاد شدند. سوالی دارید؟ نظرات خود را در نظرات زیر به اشتراک بگذارید 👇.

اگر این پست را مفید دیدید، و برای به‌روز ماندن از پست‌های مشابه، فهرست کانال‌هایی که می‌توانید دنبال کنید/عضو شوید در اینجا آمده است:

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا