IoT-laitteiden kannettavan C-koodin kirjoittaminen on perustaito sulautettuihin kehittäjiin, joiden on otettava käyttöön sovelluksia eri laitealustojen välillä. IoT-ekosysteemi käsittää mikro-ohjaimet, joissa on ARM Cortex-M, RISIC-V, AVR ja omatoimiset arkkitehtuurit, joissa jokaisella on ainutlaatuinen muistikartta, oheisrekisteri ja kääntäjän omituisuudet. Ilman tahallinen siirrettävyyssuunnittelua yhdellä kohteella toimiva koodi rikkoo usein toista, mikä johtaa kalliisiin uudelleenkirjoituksiin ja ylläpitopainajaisiin. Tämä artikkeli tarjoaa laajennetun oppaan todellisen siirrettävyyden saavuttamiseksi sulautetussa C:ssä, joka kattaa sekä strategiset lähestymistavat että käytännön taktiikka.

Soveltuvuuden ymmärtäminen esineiden internetin kehittämisessä

Soveltuvuus tarkoittaa, että lähdekoodi voidaan koota ja käyttää eri laitteistoarkkitehtuurien kanssa vain vähän tai ei lainkaan muutoksia. IoT-maailmassa siirrettävyys ei ole vain mukavuus. Tuotteen elinkaari on pitkä, toimitusketjut muuttuvat ja uusi pii ilmestyy jatkuvasti. Kannettava koodikanta mahdollistaa olemassa olevien firmware-ohjelmistojen uudelleenkäytön tuotesukupolvissa, nopeasti kääntyä vaihtoehtoisiin komponentteihin pulan aikana ja vähentää johdannaistuotteiden markkinoille tuloa.

Soveltuvuus on olemassa spektrillä. Toisessa päässä täysin alustasta riippumaton koodi (esim. geneeriset lajittelualgoritmit) kokoaa mihin tahansa. Toisessa päässä laitteistorekistereitä suoraan manipuloiva koodi on luonnostaan ei-siirrettävä. IoT:n kannettavan C:n tavoitteena on eristää abstraktiokerrosten takana olevat ei-siirrettävät yksityiskohdat siten, että liiketoiminnan ydinlogiikka ja algoritmikoodi pysyvät uudelleen käytettävissä.

Yhteiset haasteet koodien siirrettävyydelle

Useat matalan tason erot vaivaavat C-siirrettävyys:

  • Endianness.[[] ARM Cortex-M ja AVR ovat vähän endialaisia; jotkin vanhemmat arkkitehtuurit (esim. Freescale HC12) ovat isoja endiaaneja. Suoraan ohjaamalla vinkkejä tai ammattiliittoja tavutilausten läpi johtaa hiljaiseen datakorruptioon.
  • Sanan koko ja tyyppimääritelmät.[] voi olla 16 bittiä 8-bittisellä AVR:lla, 32 bittiä Cortex-M0, ja 64 bittiä RISIC-V 64-bittisellä prosessorilla. Koodi, joka olettaa [ olevan täsmälleen 32 bittiä, särkyy.
  • Rekisteröikää karttaerot.[] Jopa kahdella saman myyjän MCU:lla on usein eri ääreistukikohdat, bittikentät ja konfiguraatiosekvenssit.
  • Komppanialaajennukset ja pragmat.[ GCC, IAR, ARM Compiler 6, ja Keil kullakin on omat syntaksi- ja inline kokoonpanomurteet.
  • Muistin ulkoasu ja linjaus.[] Jotkut alustat vaativat tiukkaa linjausta 32-bittisille liittymille; toiset käsittelevät väärin linjattua pääsyä viankäsittelijän kanssa.
  • Keskeytyskäsittely ja pinokäyttö.[ Keskeytysvektorit, ensisijaiset mallit ja pesimiskäyttäytyminen vaihtelevat suuresti.

Kannettavan C-koodin kirjoittamisen avainstrategiat

Hardware Abstraction Layers (HAL)

Tehokkain työkalu kannettavan koodin arsenaalissa on []-hardware abstraktiokerros[]. Hyvin suunniteltu HAL paljastaa yhtenäisen API-liittymän tavallisille oheislaitteille (GPIO, UART, I2C, SPI, ajastimet) piilotellessaan taustalla olevaa rekisteri-bashing-järjestelmää. Käyttöliittymä olisi määriteltävä otsikossa (esim. ), joka ilmoittaa toiminnot kuten ja . Erilliset lähdetiedostot toteuttavat nämä toiminnot kullekin kohdealustalle. Sovelluskoodi nnkalta [ sisältää sirukohtaisen rekisterin otsakeen suoraan ja .

Tyypillinen HAL-toteutusmalli näyttää tältä:

// hal_gpio.h (common)
typedef uint8_t gpio_pin_t;
typedef uint8_t gpio_port_t;
void hal_gpio_set_output(gpio_port_t port, gpio_pin_t pin);
void hal_gpio_set_high(gpio_port_t port, gpio_pin_t pin);
void hal_gpio_set_low(gpio_port_t port, gpio_pin_t pin);

Alustakohtaiset tiedostot (esim. ) sisältävät varsinaiset rekisterikirjoitukset. Siirryttäessä uuteen MCU:hon vain matalan tason HAL-lähteet tarvitsevat uuden kirjoitustavan, kun taas kaikki korkeammat kerrokset ovat koskemattomia.

Standard-kirjastojen hyväksyminen

C-standardikirjasto tarjoaa kannettavan perustan monille yhteisille toiminnoille. Toiminnot kuten , [, merkkijonolaitokset ja matematiikkatoiminnot ovat saatavilla jokaisessa C-kääntäjässä. Oletusten välttäminen kirjaston sisäisistä toiminnoista on kriittistä.Ei koskaan uudelleenkirjoita suorituskykyä varten, ellei ole varmistanut, että kääntäjän toteutus on riittämätön.

IoT-järjestelmien osalta, joilla on rajallinen muisti, harkitse standardikirjaston osajoukkoa (kuten []newlib-nano[]] GCC:n ekosysteemissä) eikä omien jousirutiiniesi pyörittämistä. Samoin makro ja [] ovat yleisesti saatavilla. Linkki: [GNU C Library -dokumentti on erinomainen viite sen ymmärtämiseen, mitä on taattu kannettavaksi.

Kiinteän tietotyypin käyttäminen

ja ]n tyyppejä, joiden kokonaislukumuuttujat ovat selvästi leveitä: [, [[], [[], [[], [[], [[]], tai [[]], jos niillä on tiedossa oleva koko. Loopunlaskureiden ja pienten indeksejä varten, joissa koko ei ole kriittinen, käytä (joka määritellään täytäntöönpanossa) mieluummin kuin . Tämä käytäntö poistaa monitulkintaisuuden 16-, 32- ja 64-bittisten alustojen osalta.

Kun tarvitset serialisoida tietoja tavusuuntautuneissa kuljetuksissa, yhdistää kiinteäleveysiset tyypit ja selvät tavutilausmuunnokset ([], tai niiden kannettavat vastaavat). Älä koskaan yksinkertaisesti vala a ja lähettää sen verkon yli endianness puree sinua.

Ehdollinen kokoaminen

Esiprosessorin direktiivit ovat laillinen työkalu alustakohtaisen koodin, mutta niitä on käytettävä harkiten. Määrittele pieni joukko konfiguraatio makroja yhdessä keskusotsikossa (esim. ) eikä hajauta jokaisen tiedoston kautta. Esimerkki:

// platform_config.h
#if defined(STM32L4)
 #define PLATFORM_STM32L4
#elif defined(EFM32GG)
 #define PLATFORM_EFM32GG
#else
 #error "Unsupported platform"
#endif

Käytä sitten koodissa geneeristä vain silloin, kun se on ehdottoman välttämätöntä. Muista, että liiallinen tekee koodista vaikean lukea ja ylläpitää. Mieluummin HAL-obstraktioita ehdollisen koosteen sijaan, jos mahdollista.

Minimoidaan ulkoiset riippuvuudet

Ennen kuin lisäät riippuvuussuhteen, varmista, että se tukee kaikkia kohdearkkitehtuurisi ja että se ei vedä sisään ei-siirrettäviä oletuksia. Kirjastot, jotka on kirjoitettu kokonaan kannettavaan C (esim. ]FatFS[] tai ]FreeRTOS[]) ovat turvallisempia kuin ne, jotka perustuvat inline-asennukseen tai kääntäjä-spesifisiin pragmoihin. Silloinkin harkitset kirjaston käärimistä omalla ohuella abstraktiollasi, jotta voit vaihtaa sen myöhemmin koskematta sovelluskoodiin.

Link: Embedded.com-artikkeli reaalimaailmasta kannettavasta C-koodista tarjoaa lisänä näkökulmia riippuvuussuhteiden hallintaan.

Käytännön vinkkejä Portabilityn parantamiseen

Kirjoita modulaarinen koodi

Murra firmwaresi itsenäisiin moduuleihin, joissa on tarkkaan määritellyt rajapinnat. Jokaisen moduulin pitäisi paljastaa toiminnallisuus otsikkotiedoston kautta ja piilottaa sen sisäiset yksityiskohdat. Huolenjako tekee moduulin vaihtamisesta kannettavalla versiolla, kun se siirretään uudelle alustalle. Esimerkiksi moottorin ohjausmoduulin pitäisi puhua PWM-ulostulolle HAL-kortilla, ei suoraan ajastinsivulle.

Asiakirjan laitteiston riippuvuudet

On selvää, että koodia, jossa oletetaan tiettyä laitteistokäyttäytymistä, käytetään kommentteina selittämään, miksi valittu lähestymistapa on ei-siirrettävä, millä alustoilla se toimii ja mitä eri kohdetta varten pitäisi muuttaa. Tämä dokumentaatio on korvaamaton, kun alkuperäinen kehittäjä ei ole käytettävissä ja uuden insinöörin on siirrettävä koodi.

Käytä Cross-Platform-koostamistyökaluja

Rakenna järjestelmät kuten [CMake tai Meson[ voivat hallita useita kohdekokoonpanoja yhdestä projektirakenteesta. CMake esimerkiksi voit määrittää työkaluketjutiedostoja kullekin alustalle ja asettaa kokonaismääritelmät kohteen perusteella. Tämä poistaa tarpeen ylläpitää käsin erillisiä projektitiedostoja IAR:lle, Keil:lle ja GCC:lle. Linkki: CMake-dokumentaatio[ tarjoaa laajoja esimerkkejä ristikomplilaation perustamisesta.

Käytä kannettavia bittimanipulointitekniikoita

Kun bittiä asetetaan tai poistetaan rekistereihin, vältä kirjoittamasta absoluuttisia naamioita, joissa oletetaan bittikenttäpaikkoja. Sen sijaan käytä HAL-järjestelmässä määriteltyjä symbolisia vakioita ja käytä makroja tai inline-toimintoja turvalliseen bittitoimintaan:

#define BIT_SET(reg, bit) ((reg) |= (1u << (bit)))
#define BIT_CLEAR(reg, bit) ((reg) &= ~(1u << (bit)))

Määrittele abstraktiksi parametriksi eikä kirjaimelliseksi kokonaislukuksi. Näin, jos bittiasento muuttuu eri MCU:ssa, vain vakiomääritelmän on muututtava, ei koko koodipohjan käyttöä.

Testaus ja validointi eri alustoilla

Portability-vaatimukset on validoitava. Käytä jatkuvaa integrointia (CI), joka rakentaa projektisi kaikille tuetuille alustoille. CI:ssä suorita staattiset analyysityökalut kuten [PC-lint[ tai ]Coverity[[]], jotta voidaan havaita ei-siirrettävissä olevien rakennelmien väärinkäyttö. Toiminnallista testausta varten käytä emulaattoreita (esim. QEMU ARM- tai Renode RISIC-V-käyttöön) simuloidaksesi teloitusta ilman fyysisiä laitteita. Kun fyysisiä laitteita on saatavilla, ylläpidä pientä ...

Regressiotestit pitäisi käyttää kaikkia HAL API-pilotteja kullakin alustalla saalis yhteensopimattomuus varhaisessa vaiheessa. Testi kuten . Kirjoita tavu UART, lukea se takaisin loopback... paljastaa ajoituksen tai konfiguraatio eroja UART-toteutuksia.

Päätelmä

IoT-laitteiden kannettavan C-koodin kirjoittaminen ei ole jälkiajatus vaan kuri, joka on paistettava arkkitehtuuriin alusta alkaen. Sijoittamalla laitteiston abstraktiokerrokseen, noudattamalla standardityyppejä ja kirjastoja, käyttämällä ehdollista koostetta säästeliäästi ja testaamalla tarkasti eri kohteita, luodaan firmware, joka voi selvitä väistämättömistä muutoksista laitteistomaisemassa. Etukäteen ponnistelut maksavat osingot vähentyneessä kunnossapidossa, nopeammassa siirtämisessä uuteen piiän ja suuremmassa sietokyvyssä toimitusketjuhäiriöihin. Aloita näiden strategioiden soveltaminen tänään tulevaisuudessa -suojaamaan upotetut C-projektisi.