Selamat siang, Khabrovit sayang! Saya ingin memperkenalkan proyek saya kepada publik - papan debug kecil berdasarkan STM32, tetapi dalam faktor bentuk Raspberry Pi. Ini berbeda dari papan debug lain karena memiliki geometri yang kompatibel dengan kasing Raspberry Pi dan modul ESP8266 sebagai modem nirkabel. Dan juga tambahan bagus dalam bentuk konektor untuk kartu micro-SD dan penguat stereo. Untuk memanfaatkan semua kekayaan ini, saya mengembangkan perpustakaan tingkat tinggi dan program demo (dalam C ++ 11). Dalam artikel ini saya ingin menjelaskan secara rinci bagian perangkat keras dan perangkat lunak dari proyek ini.
Siapa yang bisa mendapat manfaat dari proyek ini? Mungkin, hanya untuk mereka yang ingin menyolder papan ini sendiri, karena saya tidak mempertimbangkan opsi apa pun untuk produksi skala kecil. Ini adalah hobi murni. Menurut pendapat saya, papan mencakup berbagai tugas yang mungkin timbul dalam kerangka kerajinan rumah kecil menggunakan WiFi dan suara.
Untuk mulai dengan, saya akan mencoba menjawab pertanyaan mengapa ini semua. Motivator utama proyek ini adalah sebagai berikut:
- Pilihan platform STM32 adalah murni karena pertimbangan estetika - Saya suka rasio harga / kinerja, ditambah berbagai periferal, ditambah ekosistem pengembangan yang besar dan nyaman dari produsen pengontrol (sw4stm, cubeMX, perpustakaan HAL).
- Tentu saja, ada banyak papan debug dari produsen pengontrol itu sendiri (Discovery, Nucleo), serta dari produsen pihak ketiga (misalnya, Olimex). Tetapi untuk mengulangi banyak dari mereka di rumah dalam faktor bentuk mereka adalah masalah bagi saya, setidaknya. Dalam versi saya, kami memiliki topologi dua lapisan sederhana dan komponen yang nyaman untuk penyolderan manual.
- Untuk perangkat mereka, saya ingin memiliki casing yang layak untuk menutupi kualitas rendah dari elektronik di dalamnya. Setidaknya ada dua platform populer yang memiliki sejumlah besar kasus yang paling beragam: Arduino dan Raspberry Pi. Yang kedua menurut saya lebih nyaman dalam hal lokasi guntingan untuk konektor. Karena itu, sebagai donor untuk geometri dewan, saya memilihnya.
- Pengontrol yang saya pilih di papan memiliki USB, SDIO, I2S, jaringan. Di sisi lain, antarmuka yang sama ini juga berguna untuk platform hobi rumah. Itulah sebabnya, selain controller dengan harness standar, saya menambahkan konektor USB, kartu SD, jalur audio (digital-to-analog converter dan amplifier), serta modul nirkabel berbasis ESP8266.
Sirkuit dan komponen
Sepertinya saya bahwa papan yang cukup bagus dengan karakteristik dan komponen berikut telah berubah:
- STM32F405RG controller: ARM 32-bit Cortex-M4 dengan coprocessor matematika, frekuensi hingga 168 MHz, memori 1 Mb flash, RAM 196 Kb.


- Konektor SWD untuk memprogram pengontrol (6 pin).
- Atur ulang tombol untuk reboot.
- LED tiga warna. Di satu sisi, tiga pin pengontrol hilang. Di sisi lain, mereka masih akan hilang karena kontak yang terbatas pada konektor GPIO, dan untuk debugging LED seperti itu, masalahnya sangat berguna.
- HSE frekuensi tinggi (16 MHz untuk clock core) dan LSE frekuensi rendah (32,7680 kHz untuk clock real-time) kuarsa.
- Pin GPIO dengan pitch 2,54 mm kompatibel dengan papan prototyping.
- Di tempat jack audio 3,5 mm dari Raspberry Pi, saya menempatkan konektor daya 5 volt. Sekilas, keputusan itu kontroversial. Tapi ada pro. Daya dari konektor USB secara opsional hadir (detail di bawah), tetapi ini adalah opsi yang buruk untuk men-debug sirkuit, karena waktu sebelum membakar port USB komputer dalam kasus ini bisa sangat singkat.

- Port mini-USB Di satu sisi, itu terhubung melalui chip perlindungan STF203-22.TCT ke port USB-OTG controller. Di sisi lain, pin daya VBUS terhubung ke konektor GPIO. Jika Anda menghubungkannya ke pin + 5V, papan akan diberi daya dari port USB.

- Konektor untuk kartu memori micro-SD dengan harness: resistor pull-up 47 kΩ, transistor manajemen daya ( P-channel MOSFET BSH205 ) dan LED hijau kecil di saluran listrik.

Gerbang transistor terhubung ke pin PA15 pengontrol. Ini adalah kontak sistem dari pengontrol JTDI, yang menarik karena pada posisi awal itu dikonfigurasikan sebagai output dengan tegangan tingkat tinggi (pull-up). Karena SWD digunakan sebagai pengganti JTAG untuk pemrograman, kontak ini tetap bebas dan dapat digunakan untuk tujuan lain, misalnya, mengendalikan transistor. Ini nyaman - ketika daya diterapkan ke papan, kartu memori tidak aktif, untuk menyalakannya, Anda perlu menerapkan tingkat rendah untuk pin PA15.
- Konverter digital-ke-analog berdasarkan UDA1334 . Chip ini tidak memerlukan sinyal clock eksternal, yang memudahkan penggunaannya. Data ditransmisikan melalui bus I2S. Di sisi lain, Datasheet merekomendasikan penggunaan kapasitor polar sebanyak 5 pada 47 μF. Ukuran penting dalam hal ini. Yang terkecil yang ternyata dibeli adalah tantalum dengan ukuran 1411, yang bahkan tidak murah. Namun, saya akan menulis tentang harga secara lebih rinci di bawah ini. Untuk daya analog, digunakan stabilizer liniernya sendiri, kekuatan bagian digital dihidupkan / dimatikan oleh transistor ganda.

- Amplifier dua saluran berdasarkan pada dua chip 31AP2005 . Keuntungan utama mereka adalah sejumlah kecil komponen pengikat (hanya filter daya dan filter input). Output audio - 4 platform dengan pitch 2,54 mm. Bagi saya sendiri, saya belum memutuskan apa yang terbaik - opsi seadanya atau, seperti pada raspberry, colokan 3,5 mm. Sebagai aturan, 3,5 mm dikaitkan dengan headphone, dalam kasus kami, kami berbicara tentang menghubungkan speaker.

- Modul terakhir adalah selendang ESP11 dengan pengikat (daya, soket pemrograman) sebagai modem WiFi. Kesimpulan papan UART terhubung ke controller dan secara bersamaan output ke konektor eksternal (untuk bekerja dengan papan langsung dari terminal dan pemrograman). Ada saklar daya (eksternal eksternal atau kontrol dari mikrokontroler). Ada LED tambahan untuk menunjukkan daya dan konektor "FLASH" untuk meletakkan papan dalam mode pemrograman.

Tentu saja, ESP8266 itu sendiri adalah pengontrol yang baik, tetapi masih kalah dengan STM32F4 baik dalam kinerja maupun periferal. Ya, dan ukuran dengan harga modul ini mengisyaratkan bahwa itu adalah unit modem tumpah untuk kakaknya. Modul ini dikendalikan oleh USRT menggunakan protokol teks AT .
Beberapa foto:


Mempersiapkan Modul ESP11
ESP8266 adalah hal yang terkenal. Saya yakin banyak yang sudah akrab dengannya, jadi panduan terperinci akan berlebihan di sini. Karena fitur skematis menghubungkan modul ESP11 ke papan, saya hanya akan memberikan panduan singkat bagi mereka yang ingin mengubah firmware-nya:

- Nyalakan daya, jalankan esptool dengan parameter berikut
> esptool.py --port /dev/ttyUSB0 flash_id Connecting.... Detecting chip type... ESP8266 Chip is ESP8266EX Uploading stub... Running stub... Stub running... Manufacturer: e0 Device: 4014 Detected flash size: 1MB Hard resetting...
Perangkat lunak
Ada program pengujian di github . Dia melakukan yang berikut:
- menampilkan pengontrol pada frekuensi maksimum (168 MHz)
- mengaktifkan jam waktu nyata
- mengaktifkan kartu SD dan membaca konfigurasi jaringan darinya. Pustaka FatFS digunakan untuk bekerja dengan sistem file.
- membangun koneksi ke WLAN yang ditentukan
- terhubung ke server NTP yang ditentukan dan meminta waktu saat itu darinya. Memimpin waktu.
- memonitor status beberapa port yang ditentukan. Jika statusnya telah berubah, kirim pesan teks ke server TCP yang ditentukan.
- ketika Anda mengklik tombol eksternal, ia membaca file * .wav yang ditentukan dari kartu SD dan memutarnya dalam mode asinkron (I2S menggunakan pengontrol DMA).
- bekerja dengan ESP11 juga diimplementasikan dalam mode asinkron (sejauh ini tanpa DMA, hanya pada interupsi)
- masuk melalui USART1 (pin PB6 / PB7)
- dan, tentu saja, LED berkedip.
Di Habré ada banyak artikel yang ditujukan untuk pemrograman STM32 pada tingkat yang agak rendah (hanya dengan manajemen register atau CMSIS). Misalnya, dari yang relatif terakhir: satu , dua , tiga . Artikel-artikelnya, tentu saja, sangat berkualitas tinggi, tetapi pendapat subjektif saya adalah bahwa untuk pengembangan produk satu kali, pendekatan ini, mungkin, membenarkan dirinya sendiri. Tetapi untuk proyek hobi jangka panjang, ketika Anda ingin semuanya menjadi indah dan dapat dikembangkan, pendekatan ini terlalu rendah. Salah satu alasan popularitas Arduino tepatnya sebagai platform perangkat lunak, menurut pendapat saya, adalah bahwa penulis Arduino telah meninggalkan tingkat yang begitu rendah untuk arsitektur berorientasi objek. Oleh karena itu, saya memutuskan untuk pergi ke arah yang sama dan menambahkan lapisan berorientasi objek tingkat tinggi di perpustakaan HAL.
Dengan demikian, tiga tingkatan program diperoleh:
- Pabrikan Libraries (HAL, FatFS, di masa depan USB-OTG) membentuk yayasan
- Pustaka StmPlusPlus saya didasarkan pada yayasan ini. Ini termasuk satu set kelas dasar (seperti System, IOPort, IOPin, Timer, RealTimeClock, Usart, Spi, I2S), satu set kelas driver perangkat eksternal (seperti SdCard, Esp11, DcfReceiver, Dac_MCP49x1, AudioDac_UDA1334 dan sejenisnya), serta kelas layanan seperti pemutar asinkron WAV.
- Berdasarkan perpustakaan StmPlusPlus, aplikasi itu sendiri sedang dibangun.
Adapun dialek bahasa. Meskipun saya agak kuno, saya tetap di C ++ 11. Standar ini memiliki beberapa fitur yang sangat berguna untuk mengembangkan firmware: kelas enum, memanggil konstruktor dengan kurung kurawal untuk mengontrol jenis parameter yang dilewati, dan wadah statis seperti std :: array. Ngomong-ngomong, di Habré ada artikel bagus tentang hal ini.
Perpustakaan StmPlusPlus
Kode pustaka lengkap dapat dilihat di github . Di sini saya hanya akan memberikan beberapa contoh kecil untuk menunjukkan struktur, ide dan masalah yang dihasilkan oleh ide ini.
Contoh pertama adalah kelas untuk polling secara berkala keadaan pin (misalnya, tombol) dan memanggil pawang ketika keadaan ini berubah:
class Button : IOPin { public: class EventHandler { public: virtual void onButtonPressed (const Button *, uint32_t numOccured) =0; }; Button (PortName name, uint32_t pin, uint32_t pull, const RealTimeClock & _rtc, duration_ms _pressDelay = 50, duration_ms _pressDuration = 300); inline void setHandler (EventHandler * _handler) { handler = _handler; } void periodic (); private: const RealTimeClock & rtc; duration_ms pressDelay, pressDuration; time_ms pressTime; bool currentState; uint32_t numOccured; EventHandler * handler; };
Konstruktor mendefinisikan semua parameter tombol:
Button::Button (PortName name, uint32_t pin, uint32_t pull, const RealTimeClock & _rtc, duration_ms _pressDelay, duration_ms _pressDuration): IOPin{name, pin, GPIO_MODE_INPUT, pull, GPIO_SPEED_LOW}, rtc{_rtc}, pressDelay{_pressDelay}, pressDuration{_pressDuration}, pressTime{INFINITY_TIME}, currentState{false}, numOccured{0}, handler{NULL} {
Jika menangani peristiwa seperti itu bukan prioritas, maka menggunakan interupsi jelas berlebihan. Oleh karena itu, berbagai skenario penekanan (misalnya, satu tekan atau tahan) diimplementasikan dalam prosedur periodik, yang harus secara berkala dipanggil dari kode program utama. secara berkala menganalisis perubahan status dan secara sinkron memanggil pengendali virtual onButtonPressed, yang harus diimplementasikan dalam program utama:
void Button::periodic () { if (handler == NULL) { return; } bool newState = (gpioParameters.Pull == GPIO_PULLUP)? !getBit() : getBit(); if (currentState == newState) {
Keuntungan utama dari pendekatan ini adalah keragaman logika dan kode untuk mendeteksi suatu peristiwa dari prosesnya. Ini bukan HAL_GetTick yang digunakan untuk menghitung waktu, yang, karena tipenya (uint32_t), disetel ulang dengan melimpah setiap 2 ^ 32 milidetik (setiap 49 hari). Saya mengimplementasikan kelas saya sendiri RealTimeClock, yang menghitung milidetik sejak awal program, atau menyalakan pengontrol seperti uint64_t, yang menghasilkan sekitar 5 ^ 8 tahun.
Contoh kedua adalah bekerja dengan antarmuka perangkat keras, yang ada beberapa di controller. Misalnya, SPI. Dari sudut pandang program utama, sangat mudah untuk memilih hanya antarmuka yang diinginkan (SPI1 / SPI2 / SPI3), dan semua parameter lain yang bergantung pada antarmuka ini akan dikonfigurasikan oleh konstruktor kelas.
class Spi { public: const uint32_t TIMEOUT = 5000; enum class DeviceName { SPI_1 = 0, SPI_2 = 1, SPI_3 = 2, }; Spi (DeviceName _device, IOPort::PortName sckPort, uint32_t sckPin, IOPort::PortName misoPort, uint32_t misoPin, IOPort::PortName mosiPort, uint32_t mosiPin, uint32_t pull = GPIO_NOPULL); HAL_StatusTypeDef start (uint32_t direction, uint32_t prescaler, uint32_t dataSize = SPI_DATASIZE_8BIT, uint32_t CLKPhase = SPI_PHASE_1EDGE); HAL_StatusTypeDef stop (); inline HAL_StatusTypeDef writeBuffer (uint8_t *pData, uint16_t pSize) { return HAL_SPI_Transmit(hspi, pData, pSize, TIMEOUT); } private: DeviceName device; IOPin sck, miso, mosi; SPI_HandleTypeDef *hspi; SPI_HandleTypeDef spiParams; void enableClock(); void disableClock(); };
Parameter pin dan parameter antarmuka disimpan secara lokal di kelas. Sayangnya, saya memilih opsi implementasi yang tidak sepenuhnya berhasil, ketika pengaturan parameter tergantung pada antarmuka tertentu diimplementasikan secara langsung:
Spi::Spi (DeviceName _device, IOPort::PortName sckPort, uint32_t sckPin, IOPort::PortName misoPort, uint32_t misoPin, IOPort::PortName mosiPort, uint32_t mosiPin, uint32_t pull): device(_device), sck(sckPort, sckPin, GPIO_MODE_AF_PP, pull, GPIO_SPEED_HIGH, false), miso(misoPort, misoPin, GPIO_MODE_AF_PP, pull, GPIO_SPEED_HIGH, false), mosi(mosiPort, mosiPin, GPIO_MODE_AF_PP, pull, GPIO_SPEED_HIGH, false), hspi(NULL) { switch (device) { case DeviceName::SPI_1: #ifdef SPI1 sck.setAlternate(GPIO_AF5_SPI1); miso.setAlternate(GPIO_AF5_SPI1); mosi.setAlternate(GPIO_AF5_SPI1); spiParams.Instance = SPI1; #endif break; ... case DeviceName::SPI_3: #ifdef SPI3 sck.setAlternate(GPIO_AF6_SPI3); miso.setAlternate(GPIO_AF6_SPI3); mosi.setAlternate(GPIO_AF6_SPI3); spiParams.Instance = SPI3; #endif break; } spiParams.Init.Mode = SPI_MODE_MASTER; spiParams.Init.DataSize = SPI_DATASIZE_8BIT; spiParams.Init.CLKPolarity = SPI_POLARITY_HIGH; spiParams.Init.CLKPhase = SPI_PHASE_1EDGE; spiParams.Init.FirstBit = SPI_FIRSTBIT_MSB; spiParams.Init.TIMode = SPI_TIMODE_DISABLE; spiParams.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; spiParams.Init.CRCPolynomial = 7; spiParams.Init.NSS = SPI_NSS_SOFT; }
Skema yang sama mengimplementasikan prosedur enableClock dan disableClock, yang tidak dapat diperluas dan buruk portabel untuk pengontrol lainnya. Dalam hal ini, lebih baik menggunakan templat di mana parameter templat adalah nama antarmuka HAL (SPI1, SPI2, SPI3), parameter pin (GPIO_AF5_SPI1), dan sesuatu yang mengontrol jam on / off. Ada artikel yang menarik tentang topik ini, meskipun meninjau pengontrol AVR, yang, bagaimanapun, tidak membuat perbedaan mendasar.
Awal dan akhir transfer dikendalikan oleh dua metode start / stop:
HAL_StatusTypeDef Spi::start (uint32_t direction, uint32_t prescaler, uint32_t dataSize, uint32_t CLKPhase) { hspi = &spiParams; enableClock(); spiParams.Init.Direction = direction; spiParams.Init.BaudRatePrescaler = prescaler; spiParams.Init.DataSize = dataSize; spiParams.Init.CLKPhase = CLKPhase; HAL_StatusTypeDef status = HAL_SPI_Init(hspi); if (status != HAL_OK) { USART_DEBUG("Can not initialize SPI " << (size_t)device << ": " << status); return status; } if (spiParams.Init.Direction == SPI_DIRECTION_1LINE) { SPI_1LINE_TX(hspi); } if ((spiParams.Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { __HAL_SPI_ENABLE(hspi); } USART_DEBUG("Started SPI " << (size_t)device << ": BaudRatePrescaler = " << spiParams.Init.BaudRatePrescaler << ", DataSize = " << spiParams.Init.DataSize << ", CLKPhase = " << spiParams.Init.CLKPhase << ", Status = " << status); return status; } HAL_StatusTypeDef Spi::stop () { USART_DEBUG("Stopping SPI " << (size_t)device); HAL_StatusTypeDef retValue = HAL_SPI_DeInit(&spiParams); disableClock(); hspi = NULL; return retValue; }
Bekerja dengan antarmuka perangkat keras menggunakan interupsi . Kelas mengimplementasikan antarmuka I2S menggunakan pengontrol DMA. I2S (Inter-IC Sound) adalah perangkat keras dan perangkat lunak tambahan pada SPI, yang dengan sendirinya, misalnya, memilih frekuensi jam dan mengontrol saluran tergantung pada protokol audio dan bit rate.
Dalam hal ini, kelas I2S diwarisi dari kelas "port", yaitu, I2S adalah port dengan properti khusus. Beberapa data disimpan dalam struktur HAL (plus untuk kenyamanan, dikurangi untuk jumlah data). Beberapa data ditransfer dari kode utama melalui tautan (misalnya, struktur irqPrio).
class I2S : public IOPort { public: const IRQn_Type I2S_IRQ = SPI2_IRQn; const IRQn_Type DMA_TX_IRQ = DMA1_Stream4_IRQn; I2S (PortName name, uint32_t pin, const InterruptPriority & prio); HAL_StatusTypeDef start (uint32_t standard, uint32_t audioFreq, uint32_t dataFormat); void stop (); inline HAL_StatusTypeDef transmit (uint16_t * pData, uint16_t size) { return HAL_I2S_Transmit_DMA(&i2s, pData, size); } inline void processI2SInterrupt () { HAL_I2S_IRQHandler(&i2s); } inline void processDmaTxInterrupt () { HAL_DMA_IRQHandler(&i2sDmaTx); } private: I2S_HandleTypeDef i2s; DMA_HandleTypeDef i2sDmaTx; const InterruptPriority & irqPrio; };
Konstruktornya menetapkan semua parameter statis:
I2S::I2S (PortName name, uint32_t pin, const InterruptPriority & prio): IOPort{name, GPIO_MODE_INPUT, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, pin, false}, irqPrio{prio} { i2s.Instance = SPI2; i2s.Init.Mode = I2S_MODE_MASTER_TX; i2s.Init.Standard = I2S_STANDARD_PHILIPS;
Awal transfer data dikendalikan oleh metode start, yang bertanggung jawab untuk mengonfigurasi parameter port, clocking antarmuka, mengkonfigurasi interupsi, memulai DMA, memulai antarmuka itu sendiri dengan parameter transmisi yang ditentukan.
HAL_StatusTypeDef I2S::start (uint32_t standard, uint32_t audioFreq, uint32_t dataFormat) { i2s.Init.Standard = standard; i2s.Init.AudioFreq = audioFreq; i2s.Init.DataFormat = dataFormat; setMode(GPIO_MODE_AF_PP); setAlternate(GPIO_AF5_SPI2); __HAL_RCC_SPI2_CLK_ENABLE(); HAL_StatusTypeDef status = HAL_I2S_Init(&i2s); if (status != HAL_OK) { USART_DEBUG("Can not start I2S: " << status); return HAL_ERROR; } __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_LINKDMA(&i2s, hdmatx, i2sDmaTx); status = HAL_DMA_Init(&i2sDmaTx); if (status != HAL_OK) { USART_DEBUG("Can not initialize I2S DMA/TX channel: " << status); return HAL_ERROR; } HAL_NVIC_SetPriority(I2S_IRQ, irqPrio.first, irqPrio.second); HAL_NVIC_EnableIRQ(I2S_IRQ); HAL_NVIC_SetPriority(DMA_TX_IRQ, irqPrio.first + 1, irqPrio.second); HAL_NVIC_EnableIRQ(DMA_TX_IRQ); return HAL_OK; }
Prosedur berhenti melakukan yang sebaliknya:
void I2S::stop () { HAL_NVIC_DisableIRQ(I2S_IRQ); HAL_NVIC_DisableIRQ(DMA_TX_IRQ); HAL_DMA_DeInit(&i2sDmaTx); __HAL_RCC_DMA1_CLK_DISABLE(); HAL_I2S_DeInit(&i2s); __HAL_RCC_SPI2_CLK_DISABLE(); setMode(GPIO_MODE_INPUT); }
Ada beberapa fitur menarik di sini:
- Interupsi yang digunakan dalam hal ini didefinisikan sebagai konstanta statis. Ini minus untuk portabilitas ke pengontrol lain.
- Pengaturan kode semacam itu memastikan bahwa pin port selalu dalam status GPIO_MODE_INPUT saat tidak ada transmisi. Ini merupakan nilai tambah.
- Prioritas interupsi ditransfer dari luar, yaitu, ada peluang bagus untuk mengatur peta prioritas interupsi di satu tempat kode utama. Ini juga merupakan nilai tambah.
- Prosedur berhenti menonaktifkan pencatatan jam kerja DMA1. Dalam hal ini, penyederhanaan ini dapat memiliki konsekuensi yang sangat negatif jika orang lain terus menggunakan DMA1. Masalahnya diselesaikan dengan membuat daftar konsumen yang terpusat dari perangkat tersebut, yang akan bertanggung jawab untuk pengaturan waktu.
- Penyederhanaan lain - prosedur mulai tidak membawa antarmuka ke keadaan semula jika terjadi kesalahan (ini adalah minus, tetapi mudah diperbaiki). Pada saat yang sama, kesalahan dicatat lebih detail, yang merupakan nilai tambah.
- Saat menggunakan kelas ini, kode utama harus mencegat SPI2_IRQn dan DMA1_Stream4_IRQn menyela dan memastikan bahwa penangan processI2SInterrupt dan processDmaTxInterrupt yang sesuai dipanggil.
Program utama
Program utama ditulis menggunakan perpustakaan yang dijelaskan di atas dengan cukup sederhana:
int main (void) { HAL_Init(); IOPort defaultPortA(IOPort::PortName::A, GPIO_MODE_INPUT, GPIO_PULLDOWN); IOPort defaultPortB(IOPort::PortName::B, GPIO_MODE_INPUT, GPIO_PULLDOWN); IOPort defaultPortC(IOPort::PortName::C, GPIO_MODE_INPUT, GPIO_PULLDOWN);
Di sini kita menginisialisasi pustaka HAL, mengkonfigurasi semua pin pengontrol dengan input (GPIO_MODE_INPUT / PULLDOWN) secara default. Kami mengatur frekuensi controller, mulai jam (termasuk jam real-time dari kuarsa eksternal). Setelah itu, sedikit bergaya Java, kita membuat instance dari aplikasi kita dan memanggil metode menjalankannya, yang mengimplementasikan semua logika aplikasi.
Di bagian terpisah, kita harus mendefinisikan semua interupsi yang digunakan. Karena kita menulis dalam C ++, dan interupsi adalah hal-hal dari dunia C, kita perlu menutupi mereka sesuai:
extern "C" { void SysTick_Handler (void) { HAL_IncTick(); if (appPtr != NULL) { appPtr->getRtc().onMilliSecondInterrupt(); } } void DMA2_Stream3_IRQHandler (void) { Devices::SdCard::getInstance()->processDmaRxInterrupt(); } void DMA2_Stream6_IRQHandler (void) { Devices::SdCard::getInstance()->processDmaTxInterrupt(); } void SDIO_IRQHandler (void) { Devices::SdCard::getInstance()->processSdIOInterrupt(); } void SPI2_IRQHandler(void) { appPtr->getI2S().processI2SInterrupt(); } void DMA1_Stream4_IRQHandler(void) { appPtr->getI2S().processDmaTxInterrupt(); } void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *channel) { appPtr->processDmaTxCpltCallback(channel); } ... }
Kelas MyApplication menyatakan semua perangkat yang digunakan, memanggil konstruktor untuk semua perangkat ini, dan juga mengimplementasikan penangan peristiwa yang diperlukan:
class MyApplication : public RealTimeClock::EventHandler, class MyApplication : public RealTimeClock::EventHandler, WavStreamer::EventHandler, Devices::Button::EventHandler { public: static const size_t INPUT_PINS = 8;
Faktanya, semua perangkat yang digunakan dinyatakan secara statis, yang berpotensi menyebabkan peningkatan memori yang digunakan, tetapi sangat menyederhanakan akses ke data. Dalam konstruktor kelas MyApplication, perlu memanggil perancang semua perangkat, setelah itu, pada saat prosedur dijalankan dimulai, semua perangkat mikrokontroler yang digunakan akan diinisialisasi:
MyApplication::MyApplication () :
Sebagai contoh, pengendali acara untuk mengklik tombol yang memulai / berhenti memainkan file WAV:
virtual void MyApplication::onButtonPressed (const Devices::Button * b, uint32_t numOccured) { if (b == &playButton) { USART_DEBUG("play button pressed: " << numOccured); if (streamer.isActive()) { USART_DEBUG(" Stopping WAV"); streamer.stop(); } else { USART_DEBUG(" Starting WAV"); streamer.start(AudioDac_UDA1334::SourceType:: STREAM, config.getWavFile()); } } }
Dan akhirnya, metode jalankan utama menyelesaikan konfigurasi perangkat (misalnya, menetapkan MyApplication sebagai pengendali event), dan memulai loop tanpa akhir, di mana ia secara berkala mengakses perangkat yang memerlukan perhatian berkala:
void MyApplication::run () { log.initInstance(); USART_DEBUG("Oscillator frequency: " << System::getExternalOscillatorFreq() << ", MCU frequency: " << System::getMcuFreq()); HAL_StatusTypeDef status = HAL_TIMEOUT; do { status = rtc.start(8 * 2047 + 7, RTC_WAKEUPCLOCK_RTCCLK_DIV2, irqPrioRtc, this); USART_DEBUG("RTC start status: " << status); } while (status != HAL_OK); sdCard.setIrqPrio(irqPrioSd); sdCard.initInstance(); if (sdCard.isCardInserted()) { updateSdCardState(); } USART_DEBUG("Input pins: " << pins.size()); pinsState.fill(true); USART_DEBUG("Pin state: " << fillMessage()); esp.assignSendLed(&ledGreen); streamer.stop(); streamer.setHandler(this); streamer.setVolume(1.0); playButton.setHandler(this); bool reportState = false; while (true) { updateSdCardState(); playButton.periodic(); streamer.periodic(); if (isInputPinsChanged()) { USART_DEBUG("Input pins change detected"); ledBlue.putBit(true); reportState = true; } espSender.periodic(); if (espSender.isOutputMessageSent()) { if (reportState) { espSender.sendMessage(config, "TCP", config.getServerIp(), config.getServerPort(), fillMessage()); reportState = false; } if (!reportState) { ledBlue.putBit(false); } } if (heartbeatEvent.isOccured()) { ledGreen.putBit(heartbeatEvent.occurance() == 1); } } }
Sedikit eksperimen
— . — 168 MHz. , , 172 MHz 180 MHz, , , MCO. , USART I2S, , , HAL.
. github . - , Mouser ( ). 37 . . , STM Olimex, .
. , :
- ( ). , , . : 4 8 . PLL, .
- , . 47 μF . , .
- SWD . - , . .
- . SMD , . 3 .
Dokumentasi
github GPL v3:
Terima kasih atas perhatian anda!