MBED, atau tentang abstraksi yang bocor

Butuh melihat ke arah tempat tidur. Sekilas terlihat sangat menarik - kerangka kerja bebas-besi, di C ++, dengan dukungan untuk sekelompok mikrokontroler dan papan demo, kompiler online dengan integrasi ke dalam sistem kontrol versi. Banyak contoh yang semakin meyakinkan keanggunan kerangka kerja. Hampir semua antarmuka mikrokontroler dapat diakses langsung dari kotak menggunakan kelas yang sesuai yang sudah diterapkan. Ambil langsung dari kotak dan program dalam C ++ tanpa melihat lembar data dari mikrokontroler - bukankah itu mimpi?

Platform uji adalah STM Nucleo F030 yang telah lama berdiri, didukung oleh platform ini. Ada banyak tutorial bagus tentang cara mendaftar dan memulai proyek pertama, kami tidak akan membicarakannya. Mari kita langsung ke yang menarik.

Papan ini tidak mengandung terlalu banyak periferal "di papan". LED dan tombol - itu saja kekayaan. Nah, proyek pertama - "Hello world" klasik dari dunia mikrokontroler - berkedip dengan LED. Ini kodenya:

#include "mbed.h"
DigitalOut myled(LED1);
int main() {
    while(1)
    {
        wait_ms(500);
        myled = myled ^ 1;
    }
}

Setelah semua bagus, bukan? Anda benar-benar tidak perlu melihat lembar data pengontrol!

Ngomong-ngomong, “Hello world” klasik juga tersedia di luar kotak:

#include "mbed.h"
Serial pc(USBTX, USBRX);
int main() {
    pc.printf("Hello world\n\r");
    while(1)
    {
    }
}

Bukankah itu hebat? “Out of the box” apakah kita memiliki konsol tempat cetak sistem standar berfungsi? Port, omong-omong, juga muncul segera ketika papan terhubung ke komputer, bersama dengan disk virtual, di mana Anda hanya perlu menyalin biner rakitan - dan papan akan reboot sendiri.

Tapi kembali ke LED yang berkedip. Kompilasi, unduh ke papan - berkedip! Tetapi entah bagaimana terlalu cepat, jelas lebih dari sekali per detik, tetapi setidaknya 5 kali ... Ups ...

Saya sedikit terganggu oleh "abstraksi berlubang" yang disebutkan dalam judul. Saya membaca tentang istilah ini dengan Joel Spolsky. Berikut ini kutipannya: "Jika saya mengajar programmer C ++, akan lebih bagus jika saya tidak perlu memberi tahu mereka tentang char * dan pointer aritmatika, tetapi saya bisa langsung ke baris dari pustaka templat standar. Tapi suatu hari mereka akan menulis "foo" + "bar" dan masalah aneh akan muncul, tetapi saya masih harus menjelaskan kepada mereka apa char *. Atau mereka akan mencoba untuk memanggil fungsi Windows dengan parameter tipe LPTSTR dan gagal sampai mereka belajar char * dan pointer dan Unicode dan wchar_t dan file header TCHAR - semua yang bersinar melalui lubang di abstraksi. "

Jadi, paradoks seperti itu, hukum abstraksi berlubang tidak memungkinkan seseorang untuk mengambil dan memulai pemrograman mikrokontroler seperti ini (bahkan jika ini adalah "halo dunia" paling sederhana dari tiga baris), tanpa melihat lembar datanya dan tidak memiliki gagasan bahwa mikrokontroler memiliki bagian dalam yang sama, serta apa yang ada di balik semua kelas ini dalam abstraksi. Bertentangan dengan janji-janji pemasar ...

Jadi, setelah menghela nafas berat, kami mulai mencari. Jika di depan mata saya itu bukan abstraksi, tetapi lingkungan pengembangan yang lengkap untuk controller, itu akan menjadi jelas tempat untuk menggali. Tapi di mana menggali jika kode di atas? Jika bahkan isi file mbed.h tidak memungkinkan abstraksi terlihat?

Untungnya, pustaka mbed itu sendiri adalah open source, dan Anda dapat mengunduhnya dan melihat apa yang ada di dalamnya. Ternyata, sekali lagi, untungnya, melalui abstraksi untuk programmer, semua register mikrokontroler cukup mudah diakses, meskipun tidak dinyatakan secara eksplisit. Sudah lebih baik. Misalnya, Anda dapat memverifikasi bahwa kami memiliki kecepatan clock dengan menjalankan ini (yang saya harus melihat lembar data dan menggali mbed dengan cukup baik):

  RCC_OscInitTypeDef str;
  RCC_ClkInitTypeDef clk;
    pc.printf("SystemCoreClock = %d Hz\n\r", HAL_RCC_GetSysClockFreq());
    
    HAL_RCC_GetOscConfig(&str);
    pc.printf("->HSIState %d\n\r", str.HSIState);
    pc.printf("->PLL.PLLState %d\n\r", str.PLL.PLLState);
    pc.printf("->PLL.PLLSource %d\n\r", str.PLL.PLLSource);
    pc.printf("->PLL.PLLMUL %d\n\r", str.PLL.PLLMUL);
    pc.printf("->PLL.PREDIV %d\n\r", str.PLL.PREDIV);
    pc.printf("\n\r");
    
    HAL_RCC_GetClockConfig(&clk, &flat);
    pc.printf("ClockType %d\n\r", clk.ClockType);
    pc.printf("SYSCLKSource %d\n\r", clk.SYSCLKSource );
    pc.printf("AHBCLKDivider  %d\n\r", clk.AHBCLKDivider );
    pc.printf("APB1CLKDivider %d\n\r", clk.APB1CLKDivider );


Semuanya berjalan baik dengan frekuensi, itu 48 MHz, seperti yang dimaksudkan.
Nah, gali lebih jauh. Kami mencoba menghubungkan penghitung waktu alih-alih wait_ms ():

   Timer timer;
    timer.start();
    while(1) {
        myled ^= 1;
        t1 = timer.read_ms();
        t2 = timer.read_ms();
        while (t2 - t1 < 500)
        {
            t2 = timer.read_ms();
        }
    }


Itu indah, bukan? Tetapi LED masih berkedip 5 kali lebih cepat dari yang diinginkan ...

Oke, kita menggali lebih dalam dan melihat apa yang kita miliki di belakang timer ajaib, yang, seperti yang dijanjikan dokumentasi, dapat dibuat dalam jumlah berapa pun, terlepas dari berapa banyak timer perangkat keras yang ada. mikrokontroler. Keren, ya ...

Dan dalam kasus STM F030, timer perangkat keras pengontrol TIM1 tersembunyi di belakangnya, diprogram untuk menghitung kutu mikrodetik. Dan atas dasar ini, semua yang lain telah dibangun.

Jadi sudah panas. Ingat, saya katakan bahwa semua register tersedia? Kami melihat hal-hal berikut:

pc.printf("PSC: %d\n\r", TIM1->PSC);


Voila! Ini mengakhiri penyelidikan tentang siapa yang harus disalahkan: nomor 7 dikirim ke konsol. Dalam register PSC (juru tulis? Apakah ini benar-benar hanya kebetulan?) Ada nilai, setelah mencapai waktu itu penghitung waktu akan menghasilkan interupsi dan mulai menghitung lagi. Dan pada gangguan ini dan penghitung mikrodetik digantung. Dan sehingga pada frekuensi 48 MHz gangguan terjadi sekali setiap mikrodetik, seharusnya tidak ada 7 sama sekali, tetapi 47. Dan 7 sampai di sana, kemungkinan besar, pada tahap pertama memuat mikrokontroler, karena dimulai pada 8 MHz dan kemudian kembali PLL sehingga frekuensi dikalikan dengan 6, memberikan 48 MHz yang diinginkan. Dan sepertinya timer terlalu cepat diinisialisasi ...
Siapa yang harus disalahkan, tentu saja. Tetapi apa yang harus dilakukan? Penggalian kerangka kerja menyebabkan rantai panggilan berikut:

Pertama: SetSysClock_PLL_HSI () -> HAL_RCC_OscConfig (), HAL_RCC_ClockConfig () -> HAL_InitTick () - saat mengubah frekuensi, panggil fungsi yang mengatur centang mikrodetik.

Kedua: HAL_Init () -> HAL_InitTick () -> HAL_TIM_Base_Init () -> TIM_Base_SetConfig () -> TIMx-> PSC - dipanggil dari salah satu fungsi paling global, HAL_InitTick menulis nilai yang diperlukan untuk register PSC, tergantung pada jam saat ini. frekuensi ...
Apa yang tetap menjadi misteri bagi saya adalah bahwa untuk keluarga STM F0 rantai kedua tidak disebut. Saya tidak menemukan panggilan apa pun ke fungsi HAL_Init ()!

Selain itu, jika Anda melihat implementasi HAL_InitTick (), maka pada awalnya akan ada baris berikut:

    static uint32_t ticker_inited=0;  
    if(ticker_inited)return HAL_OK;
    ticker_inited=1;


Panggil fungsi ini tepat sekali. Artinya, Anda dapat memanggilnya sebanyak yang Anda suka, tetapi dia tidak akan melakukan apa pun untuk semua panggilan berikutnya. Dan fakta bahwa kerangka menyebutnya kalau-kalau perubahan frekuensi clock tidak berguna ...

Saya terlalu malas untuk membangun kembali perpustakaan, jadi koreksi mengambil bentuk ini: baris pertama dalam fungsi utama adalah ini:

TIM1->PSC = (SystemCoreClock / 1000000) - 1;


Setelah itu, LED akhirnya mulai berkedip dengan frekuensi yang benar dari 1 Hz ...

Saya ingin tahu pada tahap apa seseorang akan menghentikan hipotesa yang dipasarkan para pemasar untuk mencoba membuat pemrograman mikrokontroler begitu mudah dari awal? Jika dia belum pernah bertemu mikrokontroler sebelumnya?

Oke, jika saya tidak terlalu lelah, kami akan pindah. Memasang LED bagus, tetapi tidak menarik. Saya menginginkan sesuatu yang lebih. Sensor suhu DS1820 ditemukan dari yang lebih besar, dan perpustakaan yang selesai untuk itu googled. Mudah digunakan, menyembunyikan kerumitan bekerja dengan sensor ini di dalamnya. Yang diperlukan hanyalah menulis sesuatu seperti

#include "DS1820.h"
DS1820 probe[1] = {D4}; // D4 –  ,    
probe[0].convert_temperature(DS1820::all_devices);
float temperature = probe[0].temperature('c');


Menurut Anda apa yang terjadi setelah kompilasi dan peluncuran? Benar. Tidak berhasil :)

Abstraksi yang bocor, ya. Nah, studi menarik lainnya tentang internal mbed menanti kita, tampaknya. Dan detail dari sensor itu sendiri.

Sensornya sangat menarik. Dengan antarmuka digitalnya. Dalam satu baris, mis. bekerja dalam mode setengah dupleks. Pengontrol memulai pertukaran data, sensor mengirimkan urutan bit sebagai tanggapan. Khusus untuk kasus-kasus seperti itu, kerangka kerja mbed memiliki kelas DigitalInOut, yang dengannya Anda dapat mengubah arah pekerjaannya pada pin GPIO on the fly. Sesuatu seperti ini:

bool DS1820::onewire_bit_in(DigitalInOut *pin) {
    bool answer;
    pin->output();
    pin->write(0);
    wait_us(3);                 
    pin->input();
    wait_us(10); 
    answer = pin->read();
    wait_us(45); 
    return answer;
}


Pengontrol mengirimkan pulsa "1 -> 0 -> 1" ke sensor, yang merupakan sinyal untuk mengirim sedikit tanggapan. Apa yang mungkin tidak berfungsi di sini? Berikut adalah bagian dari lembar data sensor:



Seperti yang Anda lihat, setelah kami mengirim impuls ke sensor, untuk membaca bit, kami memiliki sebanyak 15 mikrodetik.

Kami menghubungkan osiloskop dan melihat bahwa sensor mengirimkan bit ke dirinya sendiri, sebagaimana ditunjukkan dalam lembar data. Tapi di sini pengontrol membaca sampah. Apa yang bisa menjadi alasannya?

Saya tidak akan menulis banyak tentang bagaimana saya bisa menjalankan kode ini:

    pin->output();
    t1 = timer.read_us();
    pin->input();
    t4 = timer.read_us();
    pc.printf("Time: %d us\n\r", t4-t1);


Nah, siapa yang menebak, tanpa membaca lebih lanjut, berapa banyak waktu pada mikrokontroler dengan frekuensi clock 48 MHz yang diperlukan untuk mengubah arah satu pin GPIO (pada kenyataannya, itu adalah SATU menulis ke register, jika itu), jika itu dilakukan menggunakan kerangka kerja yang ditulis dalam C ++ menggunakan lapisan independen besi?

13 mikrodetik.

Dan untuk membaca respon sensor, kita memiliki sebanyak 15. Dan bahkan jika kita benar-benar menghapus wait_us (10) dari kode di atas, perintah answer = pin-> read (); juga membutuhkan beberapa waktu lebih lama dari 2 mikrodetik. Apa yang cukup untuk tidak punya waktu untuk membaca jawabannya.

Untungnya, adalah mungkin untuk mengirim impuls ke STM tanpa mengubah arah GPIO. Dalam mode Input, ketika Anda menghubungkan resistor PullDown bawaan, efeknya sama. Dan lagi, untungnya, memanggil mode pin-> (PullUp) alih-alih pin-> input () hanya membutuhkan 6 mikrodetik.

Apa yang cukup punya waktu untuk membaca jawabannya.

Dan akhirnya, lubang lain di abstraksi. Setelah DS1820 bekerja, sensor berikutnya yang muncul adalah DHT21, sebuah sensor yang mengukur suhu dan kelembaban. Juga dengan antarmuka 1-kawat, tetapi kali ini respons pengkodean dengan durasi pulsa. Tampaknya solusi yang jelas adalah menggantung baris ini pada interupsi input GPIO, yang membuatnya mudah untuk mengukur durasi pulsa. Dan bahkan ada kelas di tempat tidur untuk ini. Dan itu bahkan berfungsi seperti yang didokumentasikan.

Tetapi masalahnya adalah bahwa sensor ini juga bekerja dalam setengah dupleks. Dan baginya untuk mulai mentransmisikan data, ia perlu mengirim impuls "1 -> 0 -> 1" seperti pada contoh di atas.

Dan di sini tidur tidak memungkinkan ini. Atau Anda mendeklarasikan port sebagai DigitalInOut, tetapi Anda tidak dapat menggunakan interupsi. Atau Anda mendeklarasikan port sebagai InterruptIn, tetapi kemudian Anda tidak dapat mengirim apa pun ke sana. Sayangnya, trik PullDown tidak berfungsi di sini. sensor memiliki PullUp bawaan, dan mikrokontroler PullDown built-in tidak cukup untuk menarik pin ke 0.

Saya harus membuat siklus polling garis yang jauh kurang indah sebagai hasilnya. Semuanya, tentu saja, berfungsi seperti itu, tetapi tidak berhasil dengan indah. Yang tidak akan menjadi masalah dengan akses langsung ke register ...

Ini dia. Upaya untuk membuat abstraksi sehingga siapa pun dapat segera mulai memprogram mikrokontroler secara layak. Banyak hal yang sangat disederhanakan, dan bahkan berfungsi. Tetapi, seperti abstraksi tingkat tinggi lainnya, abstraksi ini juga penuh dengan lubang. Dan dalam tabrakan dengan banyak lubang harus turun dari surga ke bumi.

Source: https://habr.com/ru/post/id387299/


All Articles