Si të konfiguroni telefonat inteligjentë dhe PC. Portali informativ
  • në shtëpi
  • Lajme
  • STM32F407(STM32F4-Zbulim) - Qasje jo standarde - Pjesa 1 e bibliotekës standarde.

STM32F407(STM32F4-Zbulim) - Qasje jo standarde - Pjesa 1 e bibliotekës standarde.

Software i nevojshëm për zhvillim. Në këtë artikull do t'ju tregoj se si ta konfiguroni dhe lidhni saktë. Të gjitha mjediset komerciale si IAR EWARM ose Keil uVision zakonisht e kryejnë vetë këtë integrim, por në rastin tonë gjithçka do të duhet të konfigurohet manualisht, duke shpenzuar shumë kohë për të. Avantazhi është se ju keni mundësinë të kuptoni se si funksionon gjithçka nga brenda, dhe në të ardhmen, të personalizoni në mënyrë fleksibël gjithçka për veten tuaj. Para fillimit të konfigurimit, le të shohim strukturën e mjedisit në të cilin do të punojmë:

Eclipse do të përdoret për të modifikuar me lehtësi skedarët e zbatimit të funksionit ( .c), skedarët e kokës (.h), si dhe skedarët e montimit ( .S). Me "i përshtatshëm" nënkuptoj përdorimin e plotësimit të kodit, theksimit të sintaksës, rifaktorimit, navigimit përmes funksioneve dhe prototipeve të tyre. Skedarët ushqehen automatikisht te përpiluesit e nevojshëm, të cilët gjenerojnë kodin e objektit (në skedarë .o). Deri tani ky kod nuk përmban adresat absolute variabla dhe funksione dhe për këtë arsye nuk janë të përshtatshme për ekzekutim. Skedarët e objekteve që rezultojnë mblidhen së bashku nga një lidhës. Për të ditur se cilat pjesë të hapësirës së adresave të përdorë, koleksionisti përdor një skedar të veçantë ( .ld), i cili quhet skript lidhës. Zakonisht përmban një përkufizim të adresave të seksioneve dhe madhësive të tyre (seksioni i kodit i përcaktuar në flash, seksioni i ndryshueshëm i hartuar në RAM, etj.).

Në fund të fundit, lidhësi gjeneron një skedar .elf (Format i ekzekutueshëm dhe i lidhur), i cili përmban, përveç udhëzimeve dhe të dhënave, informacionin e korrigjimit të përdorur nga korrigjuesi. Ky format nuk është i përshtatshëm për ndezjen e rregullt të firmuerit me programin vsprog, pasi kjo kërkon një skedar imazhi memorie më primitive (për shembull, Intel HEX - .hex). Për ta gjeneruar atë, ekziston gjithashtu një mjet nga grupi Sourcery CodeBench (arm-none-eabi-objcopy), dhe ai integrohet në mënyrë të përsosur në eklips duke përdorur shtojcën e instaluar ARM.

Për të kryer vetë korrigjimin, përdoren tre programe:

  1. vetë eklipsi, i cili i lejon programuesit të përdorë "vizualisht" korrigjimin, të ecë nëpër linja, të qëndrojë pezull mbi variabla për të parë vlerat e tyre dhe lehtësi të tjera
  2. arm-none-eabi-gdb - Klienti GDB është një korrigjues që kontrollohet fshehurazi nga eklipset (nëpërmjet stdin) në përgjigje të veprimeve të specifikuara në hapin 1. Nga ana tjetër, GDB lidhet me serverin OpenOCD Debug dhe të gjitha komandat hyrëse përkthehen nga korrigjuesi GDB në komanda të kuptueshme për OpenOCD. Kanali GDB<->OpenOCD zbatohet duke përdorur protokollin TCP.
  3. OpenOCD është një server korrigjimi që mund të komunikojë drejtpërdrejt me programuesin. Ai funksionon përpara klientit dhe pret për një lidhje TCP.

Kjo skemë mund t'ju duket mjaft e padobishme: pse të përdorni klientin dhe serverin veçmas dhe të kryeni përkthim të panevojshëm të komandave, nëse e gjithë kjo mund të bëhet me një korrigjues? Fakti është se një arkitekturë e tillë teorikisht lejon një shkëmbim të përshtatshëm të klientit dhe serverit. Për shembull, nëse duhet të përdorni një programues tjetër në vend të versaloon, i cili nuk do të mbështesë OpenOCD, por do të mbështesë një server tjetër special Debug (për shembull, texane/stlink për programuesin stlink - i cili ndodhet në bordin e korrigjimit STM32VLDiscovery), atëherë ju thjesht do të ekzekutoni OpenOCD në vend që të hapni serverin e dëshiruar dhe gjithçka duhet të funksionojë, pa asnjë hap shtesë. Në të njëjtën kohë, situata e kundërt është e mundur: le të themi se dëshironi të përdorni mjedisin IAR EWARM së bashku me versaloon në vend të kombinimit Eclipse + CodeBench. IAR ka klientin e vet të integruar Debug, i cili do të kontaktojë me sukses OpenOCD dhe do ta menaxhojë atë, si dhe do të marrë të dhënat e nevojshme si përgjigje. Sidoqoftë, e gjithë kjo ndonjëherë mbetet vetëm në teori, pasi standardet për komunikimin midis klientit dhe serverit nuk janë të rregulluara rreptësisht dhe mund të ndryshojnë në disa vende, por konfigurimet që specifikova me st-link+eclipse dhe IAR+versaloon funksionuan për mua.

Zakonisht klienti dhe serveri funksionojnë në të njëjtën makinë dhe lidhja me serverin ndodh në localhost:3333(Për openocd), ose localhost:4242(për texane/stlink st-util). Por askush nuk po ju ndalon të hapni portin 3333 ose 4242 (dhe ta përcillni këtë port në ruter në rrjetin e jashtëm) dhe kolegët tuaj nga një qytet tjetër do të jenë në gjendje të lidhen dhe të korrigjojnë harduerin tuaj. Ky truk përdoret shpesh nga ndërtuesit që punojnë në vende të largëta ku qasja është e kufizuar.

Le të fillojmë

Nisni eclipse dhe zgjidhni File->New->C Project, zgjidhni llojin e projektit ARM Linux GCC (Sorcery G++ Lite) dhe emrin "stm32_ld_vl" (Nëse keni STV32VLDdiscovery, atëherë do të ishte më logjike ta emërtoni "stm32_md_vl") :

Klikoni Finish dhe minimizoni ose mbyllni dritaren e mirëseardhjes. Pra, projekti është krijuar dhe dosja stm32_ld_vl duhet të shfaqet në hapësirën tuaj të punës. Tani ajo duhet të mbushet me bibliotekat e nevojshme.

Siç e kuptoni nga emri i projektit, unë do të krijoj një projekt për pamjen e sunduesit linjë vlerash me densitet të ulët(LD_VL). Për të krijuar një projekt për mikrokontrollues të tjerë, duhet të zëvendësoni të gjithë skedarët dhe të përcaktoni në emrin e të cilëve ka _LD_VL (ose_ld_vl) atyre që ju nevojiten, në përputhje me tabelën:

Lloji i vizores Emërtimi Mikrokontrolluesit (x mund të ndryshojë)
Linja e vlerës me densitet të ulët _LD_VL STM32F100x4 STM32F100x6
Me densitet të ulët _LD STM32F101x4 STM32F101x6
STM32F102x4 STM32F102x6
STM32F103x4 STM32F103x6
Linja e vlerës me densitet të mesëm _MD_VL STM32F100x8 STM32F100xB
Dendësi mesatare
_MD
STM32F101x8 STM32F101xB
STM32F102x8 STM32F102xB
STM32F103x8 STM32F103xB
Linja e vlerës me densitet të lartë _HD_VL STM32F100xC STM32F100xD STM32F100xE
Densitet i lartë _HD STM32F101xC STM32F101xD STM32F101xE
STM32F103xC STM32F103xD STM32F103xE
XL-densitet _XL STM32F101xF STM32F101xG
STM32F103xF STM32F103xG
Linja e lidhjes _CL STM32F105xx dhe STM32F107xx

Për të kuptuar logjikën pas tabelës, duhet të jeni të njohur me etiketimin STM32. Kjo do të thotë, nëse keni VLDiscovery, atëherë do të duhet të zëvendësoni gjithçka që lidhet me _LD_VL me _MD_VL, pasi çipi STM32F100RB, i cili i përket linjës së vlerës me densitet të mesëm, është ngjitur në zbulim.

Shtimi i bibliotekës standarde periferike CMSIS dhe STM32F10x në projekt

CMSIS(Cortex Microcontroller Software Interface Standard) është një bibliotekë e standardizuar për të punuar me mikrokontrolluesit Cortex që zbaton nivelin HAL (Hardware Abstraction Layer), domethënë ju lejon të abstraktoni nga detajet e punës me regjistrat, duke kërkuar adresat e regjistrave duke përdorur fletë të dhënash, etj. Biblioteka është një grup kodesh burimore në C dhe Asm. Pjesa kryesore e bibliotekës është e njëjtë për të gjithë Cortex (qoftë ST, NXP, ATMEL, TI apo kushdo tjetër), dhe është zhvilluar nga ARM. Pjesa tjetër e bibliotekës është përgjegjëse për pajisjet periferike, të cilat natyrisht ndryshojnë nga prodhuesi në prodhues. Prandaj, në fund, biblioteka e plotë ende shpërndahet nga prodhuesi, megjithëse pjesa e kernelit mund të shkarkohet ndaras nga faqja e internetit e ARM. Biblioteka përmban përkufizimet e adresave, kodin e inicializimit të gjeneratorit të orës (i përshtatshëm sipas përcaktimeve) dhe çdo gjë tjetër që e kursen programuesin të prezantojë manualisht në projektet e tij përcaktimin e adresave të të gjitha llojeve të regjistrave periferikë dhe të përcaktojë bitet e vlerave të këto regjistra.

Por djemtë nga ST shkuan më tej. Përveç mbështetjes CMSIS, ata ofrojnë një bibliotekë tjetër për STM32F10x të quajtur Biblioteka standarde e pajisjeve periferike(SPL), i cili mund të përdoret përveç CMSIS. Biblioteka ofron akses më të shpejtë dhe më të përshtatshëm në pajisjet periferike, dhe gjithashtu kontrollon (në disa raste) funksionimin e saktë të pajisjeve periferike. Kjo është arsyeja pse të dhënat e bibliotekës shpesh quhet një grup drejtues për modulet periferike. Ai shoqërohet me një paketë shembujsh, të ndarë në kategori për pajisje të ndryshme periferike. Biblioteka është gjithashtu e disponueshme jo vetëm për STM32F10x, por edhe për seri të tjera.

Mund ta shkarkoni të gjithë versionin 3.5 të SPL+CMSIS këtu: STM32F10x_StdPeriph_Lib_V3.5.0 ose në faqen e internetit të ST. Zbërtheni arkivin. Krijoni dosjet CMSIS dhe SPL në dosjen e projektit dhe filloni të kopjoni skedarët në projektin tuaj:

Çfarë të kopjoni

Ku të kopjoni (duke marrë parasysh
se dosja e projektit është stm32_ld_vl)

Përshkrimi i skedarit
Bibliotekat/CMSIS/CM3/
Mbështetja kryesore/ bërthamë_cm3.c
stm32_ld_vl/CMSIS/ bërthamë_cm3.c Përshkrimi i bërthamës Cortex M3
Bibliotekat/CMSIS/CM3/
Mbështetja kryesore/ bërthamë_cm3.h
stm32_ld_vl/CMSIS/core_cm3.h Headerët e përshkrimit të kernelit

ST/STM32F10x/ system_stm32f10x.c
stm32_ld_vl/CMSIS/system_stm32f10x.c Funksionet e inicializimit dhe
kontrolli i orës
Bibliotekat/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/ system_stm32f10x.h
stm32_ld_vl/CMSIS/system_stm32f10x.h Titujt për këto funksione
Bibliotekat/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/ stm32f10x.h
stm32_ld_vl/CMSIS/stm32f10x.h Përshkrimi bazë i pajisjeve periferike
Bibliotekat/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/startup/gcc_ride7/
startup_stm32f10x_ld_vl.s
stm32_ld_vl/CMSIS/startup_stm32f10x_ld_vl.S
(!!! Vëmendje shtesë e skedarit CAPITAL S)
Skedari i tabelës vektoriale
ndërpret dhe init-s në asm
Projekt/STM32F10x_StdPeriph_Template/
stm32f10x_conf.h
stm32_ld_vl/CMSIS/ stm32f10x_conf.h Model për personalizim
modulet periferike

inc/ *
stm32_ld_vl/SPL/inc/ * Skedarët e kokës SPL
Bibliotekat/STM32F10x_StdPeriph_Driver/
src/ *
stm32_ld_vl/SPL/src/ * Zbatimi i SPL

Pas kopjimit, shkoni te Eclipse dhe bëni Refresh në menynë e kontekstit të projektit. Si rezultat, në Project Explorer duhet të merrni të njëjtën strukturë si në foton në të djathtë.

Ju mund të keni vënë re se në dosjen Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/ ka dosje për IDE të ndryshme (IDE të ndryshme përdorin përpilues të ndryshëm). Zgjodha Ride7 IDE sepse përdor përpiluesin GNU Tools for ARM Embedded, i cili është i pajtueshëm me Sourcery CodeBench.

E gjithë biblioteka është konfiguruar duke përdorur një paraprocesor (duke përdorur defines), kjo do t'ju lejojë të zgjidhni të gjitha degët e nevojshme në fazën e përpilimit (ose më mirë, edhe para tij) dhe të shmangni ngarkesën në funksionimin e vetë kontrolluesit (që do të ishte vërehet nëse konfigurimi është kryer në RunTime). Për shembull, të gjitha pajisjet janë të ndryshme për linja të ndryshme, dhe për këtë arsye në mënyrë që biblioteka të "dijë" se cilën linjë dëshironi të përdorni, ju kërkohet të hiqni komentin në skedar stm32f10x.h një nga përcakton (që korrespondon me linjën tuaj):

/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */

Dhe kështu me radhë...

Por unë nuk e rekomandoj ta bëni këtë. Ne nuk do t'i prekim skedarët e bibliotekës për momentin dhe do ta përcaktojmë më vonë duke përdorur cilësimet e përpiluesit në Eclipse. Dhe pastaj Eсlipse do të thërrasë përpiluesin me çelësin -D STM32F10X_LD_VL, e cila për paraprocesorin është absolutisht ekuivalente me situatën nëse nuk komentoni "#define STM32F10X_LD_VL". Kështu, ne nuk do ta ndryshojmë kodin; si rezultat, nëse dëshironi, një ditë do të jeni në gjendje ta zhvendosni bibliotekën në një drejtori të veçantë dhe të mos e kopjoni atë në dosjen e çdo projekti të ri.

Skript lidhës

Në menynë e kontekstit të projektit, zgjidhni New->File->Tjetër->Të përgjithshme->Skedari, Next. Zgjidhni dosjen rrënjë të projektit (stm32_ld_vl). Futni emrin e skedarit "stm32f100c4.ld" (ose "stm32f100rb.ld" për zbulim). Tani kopjoni dhe ngjisni në eklips:

HYRJA (Reset_Handler) MEMORY ( FLASH (rx) : ORIGJINA = 0x08000000, GJATHËSIA = 16K RAM (xrw) : ORIGJINA = 0x20000000, GJATHËSIA = 4K ) _estack = ORIGINRATH (M) ORIGIN(MTH) ); MIN_HEAP_SIZE = 0; MIN_STACK_SIZE = 256; SECTIONS ( /* Tabela e vektorit të ndërprerjes */ .isr_vector: ( . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); ) >FLASH /* Kodi i programit dhe të dhënat e tjera hyjnë në FLASH * / .text: ( . = ALIGN(4); /* Kodi */ *(.tekst) *(.tekst*) /* Konstantet */ *(.rodata) *(.rodata*) /* ARM->Thumb dhe Thumb-> Kodi i ngjitësit ARM */ *(.glue_7) *(.glue_7t) KEEP (*(.init)) KEEP (*(.fini)) . = ALIGN(4); _etext = .; ) >FLASH . ARM.extab: ( *(.ARM.extab* .gnu.linkonce.armextab.*) ) >FLASH .ARM: ( __exidx_start = .; *(.ARM.exidx*) __exidx_end = .; ) >FLASH .ARM. atributet: ( *(.ARM.atributet) ) > FLASH .preinit_array: ( PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array*)) PROVIDE_HIDDEN (__preinit_array_end = .:inHID_ENPROVIS_FL = .:HID_EN PROVIT_FL filloni = .); KEEP (*(SORT(.init_array.*))) KEEP (*(.init_array*)) PROVIDE_HIDDEN (__init_array_end = .); ) >FLASH .array_fini: ( PROVIDE_HIDDEN (__fini_array_KEEP = ); (.array_fini*)) KEEP (*(SORT(.array_fini.*))) PROVIDE_HIDDEN (__fundi_array_fini = .); ) >FLASH_sidata = .; /* Të dhënat e inicuara */ .data: AT (_sidata) ( . = ALIGN(4); _sdata = .; /* krijoni një simbol global në fillimin e të dhënave */ *(.data) *(.data*) . = ALIGN (4); _edata = .; /* përcaktoni një simbol global në fundin e të dhënave */ ) >RAM /* Të dhëna të pa inicializuara */. = ALIGN(4); .bss: ( /* Kjo përdoret nga startup-i për të inicializuar seksionin .bss */ _sbss = .; /* përcaktojë një simbol global në fillimin bss */ __bss_start__ = _sbss; *(.bss) *(.bss *) *(ZAKON) .= ALIGN (4); _ebss = .; /* përcakto një simbol global në fundin bss */ __bss_end__ = _ebss; PROVIDE (_fund = _ebss); PROVIDE (__HEAP_START = _ebss); /* Seksioni User_heap_stack, përdoret për të kontrolluar nëse ka mbetur mjaftueshëm RAM */ ._user_heap_stack: ( . = ALIGN(4); . = . + MIN_HEAP_SIZE; . = . + MIN_STACK_SIZE; . = ALIGN(4); ) >RAM / HIQ/ : ( libc.a(*) libm.a(*) libgcc.a(*) ) )

Kjo l Skripti i bojës do të synohet posaçërisht për kontrolluesin STM32F100C4 (i cili ka 16 KB flash dhe 4 KB RAM), nëse keni një tjetër, do të duhet të ndryshoni parametrat LENGTH të zonave FLASH dhe RAM në fillim të skedarin (për STM32F100RB, i cili në Discovery: Flash 128K dhe RAM 8K).

Ruani skedarin.

Konfigurimi i ndërtimit (C/C++ Build)

Shkoni te Projekti->Vetitë->C/C++ Build->Cilësimet->Cilësimet e veglave dhe filloni të konfiguroni veglat e ndërtimit:

1) Pararendësi i synuar

Ne zgjedhim se për cilën bërthamë Cortex do të punojë përpiluesi.

  • Procesori: cortex-m3

2) ARM Sourcery Linux GCC C Compiler -> Preprocessor

Shtojmë dy definicione duke i kaluar përmes çelësit -D te kompajleri.

  • STM32F10X_LD_VL - përcakton vizoren (kam shkruar për këtë përkufizim më lart)
  • USE_STDPERIPH_DRIVER - i tregon bibliotekës CMSIS se duhet të përdorë drejtuesin SPL

3) ARM Sourcery Linux GCC C Compiler -> Drejtoritë

Shto shtigje në bibliotekë përfshirë.

  • "$(workspace_loc:/$(ProjName)/CMSIS)"
  • "$(workspace_loc:/$(ProjName)/SPL/inc)"

Tani, për shembull, nëse shkruajmë:

#include "stm32f10x.h

Pastaj përpiluesi duhet së pari të kërkojë skedarin stm32f10x.h në drejtorinë e projektit (ai e bën gjithmonë këtë), ai nuk do ta gjejë atë atje dhe do të fillojë të kërkojë në dosjen CMSIS, shtegun për të cilin kemi treguar dhe do ta gjejë atë.

4) ARM Sourcery Linux GCC C Compiler -> Optimization

Le të aktivizojmë funksionin dhe optimizimin e të dhënave

  • -funksioni-seksionet
  • -fdata-seksione

Si rezultat, të gjitha funksionet dhe elementët e të dhënave do të vendosen në seksione të veçanta, dhe mbledhësi do të jetë në gjendje të kuptojë se cilat seksione nuk përdoren dhe thjesht t'i hedhë ato.

5) ARM Sourcery Linux GCC C Compiler -> General

Shtoni shtegun në skriptin tonë lidhës: "$(workspace_loc:/$(ProjName)/stm32f100c4.ld)" (ose sido që ta quani).

Dhe vendosni opsionet:

  • Mos përdorni skedarë standardë të fillimit - mos përdorni skedarë standardë Nisja.
  • Hiqni seksionet e papërdorura - hiqni seksionet e papërdorura

Kjo është e gjitha, konfigurimi ka përfunduar. NE RREGULL.

Ne kemi bërë shumë që nga krijimi i projektit, dhe ka disa gjëra që Eclipse mund të ketë humbur, kështu që ne duhet t'i themi që të rishqyrtojë strukturën e skedarit të projektit. Për ta bërë këtë, nga menyja e kontekstit të projektit duhet të bëni Indeksi -> rindërtimi.

Përshëndetje LED në STM32

Është koha për të krijuar skedarin kryesor të projektit: Skedari -> I ri -> C/C++ -> Skedari burimor. Tjetra. Emri i skedarit Skedari burimor: main.c.

Kopjoni dhe ngjisni sa vijon në skedar:

#include "stm32f10x.h" uint8_t i=0; int main(void) ( RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Aktivizo orën periferike PORTB RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Aktivizo orën periferike TIM2 // Çaktivizo JTAG për lëshimin e PIN LED RCCAFR2_APREN->APBCCAFREN-> | AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // Pastro bitet e regjistrit të kontrollit PB4 dhe PB5 GPIOB->CRL &= ~(GPIO_CRL_MODE4 | GPIO_CRL_CNF4 | GPIO_CRL_MODE5 | GPIO_CRL_MODE5 si. GPIONF, si dhe. Push Pull dalje në maksimum 10 Mhz GPIOB->CRL |= GPIO_CRL_MODE4_0 | GPIO_CRL_MODE5_0; TIM2->PSC = SystemCoreClock / 1000 - 1; // 1000 tik-tak/sek TIM2->ARR = 1000; // 1 Ndërprerje/1 sek TIM2->DIER |= TIM_DIER_UIE; // Aktivizo ndërprerjen tim2 TIM2->CR1 |= TIM_CR1_CEN; // Fillimi i numërimit NVIC_EnableIRQ(TIM2_IRQn); // Aktivizo IRQ while(1); // Cikli i pafundësisë ) i pavlefshëm TIM2_IRQHandler(i pavlefshëm) ( TIM2->SR &= ~TIM_SR_UIF; //Pastro flamurin UIF nëse (1 == (i++ & 0x1)) ( GPIOB->BSRR = GPIO_BSRR_BS4; // Cakto GPIOB4 bit ->BSRR = GPIO_BSRR_BR5; // Rivendos bit PB5 ) tjetër (GPIOB->BSRR = GPIO_BSRR_BS5; // Cakto bit PB5 GPIOB->BSRR = GPIO_BSRR_BR4; // Rivendos bit PB4 ) )

Edhe pse kemi përfshirë bibliotekën SPL, ajo nuk është përdorur këtu. Të gjitha thirrjet në fusha si RCC->APB2ENR përshkruhen plotësisht në CMSIS.

Ju mund të bëni Project -> Build All. Nëse gjithçka funksionoi, atëherë skedari stm32_ld_vl.hex duhet të shfaqet në dosjen Debug të projektit. Ai u krijua automatikisht nga kukudh nga mjetet e integruara. Ne ndezim skedarin dhe shohim se si LED pulsojnë me një frekuencë prej një herë në sekondë:

Vsprog -sstm32f1 -ms -oe -owf -I /home/user/workspace/stm32_ld_vl/Debug/stm32_ld_vl.hex -V "tvcc.set 3300"

Natyrisht, në vend të /home/user/workspace/ ju duhet të futni rrugën tuaj për në hapësirën e punës.

Për zbulimin STM32VLD

Kodi është paksa i ndryshëm nga ai që dhashë më lart për bordin tim të korrigjimit. Dallimi qëndron në kunjat në të cilat "varen" LED. Nëse në bordin tim ishte PB4 dhe PB5, atëherë në Discovery ishte PC8 dhe PC9.

#include "stm32f10x.h" uint8_t i=0; int main(void) ( RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Aktivizo orën PORTC Periph RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Aktivizo orën periferike TIM2 // Pastro bitet e regjistrit të kontrollit PC8 dhe PCOC &=GPI (GPIO_CRH_MODE8 | GPIO_CRH_CNF8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF9); // Konfiguro PC8 dhe PC9 si dalje Push Pull në maksimum 10Mhz GPIOC->CRH |= GPIO_CRH_MODE_CRH |= GPIO_CRH_MODE8_00; 000 - 1; // 1000 shenjë/sek TIM2->ARR = 1000; // 1 Ndërprerje/sek (1000/100) TIM2->DIER |= TIM_DIER_UIE; // Aktivizo ndërprerjen tim2 TIM2->CR1 |= TIM_CR1_CEN; // Nis numërimin NVIC_EnableIRQn(TIM); //2_ Aktivizo IRQ while(1); // Cikli i pafundësisë ) void TIM2_IRQHandler(void) ( TIM2->SR &= ~TIM_SR_UIF; //Pastro flamurin UIF nëse (1 == (i++ & 0x1)) ( GPIOC->BSRR = GPIO_BSRR_BS ; // Cakto PC8 bit GPIOC->BSRR = GPIO_BSRR_BR9; // Rivendos bit PC9 ) tjetër (GPIOC->BSRR = GPIO_BSRR_BS9; // Cakto PC9 bit GPIOC->BSRR = GPIO_BSRR_BR8; // Rivendos bit PC8))

Në Windows, mund të ndezni hex (/workspace/stm32_md_vl/Debug/stm32_md_vl.hex) që rezulton duke përdorur programin nga ST.

Epo, nën utility linux st-flash. POR!!! Shërbimi nuk përdor formatin Intel HEX hex (i cili gjenerohet si parazgjedhje), prandaj është jashtëzakonisht e rëndësishme të zgjidhni formatin binar në cilësimet e krijimit të imazhit Flash:

Shtesa e skedarit nuk do të ndryshojë (do të mbetet heks siç ishte), por formati i skedarit do të ndryshojë. Dhe vetëm pas kësaj mund të bëni:

Shkruaj St-flash v1 /home/user/workspace/stm32_md_vl/Debug/stm32_md_vl.hex 0x08000000

Meqë ra fjala, sa i përket zgjerimit dhe formatit: zakonisht skedarët binar shënohen me ekstensionin .bin, ndërsa skedarët në formatin Intel HEX quhen ekstensioni .hex. Dallimi në këto dy formate është më shumë teknik sesa funksional: formati binar thjesht përmban byte instruksionesh dhe të dhënash që thjesht do t'i shkruhen kontrolluesit nga programuesi "siç është". IntelHEX, nga ana tjetër, nuk ka një format binar, por një tekst: saktësisht të njëjtat bajtë ndahen në 4 bit dhe paraqiten karakter për karakter në formatin ASCII, dhe përdoren vetëm karakteret 0-9, A-F (bin dhe hex janë sisteme numrash me baza të shumëfishta, domethënë 4 bit për kosh mund të përfaqësohen si një shifër e vetme gjashtëkëndore). Pra, formati ihex është më shumë se 2 herë më i madh se ai i zakonshëm skedar binar(çdo 4 bit zëvendësohen nga një bajt + ndërprerje rreshtash për lexim të lehtë), por mund të lexohet në një redaktues teksti të rregullt. Prandaj, nëse do t'ia dërgoni këtë skedar dikujt, ose do ta përdorni në programe të tjera programimi, atëherë këshillohet ta riemërtoni atë stm32_md_vl.bin në mënyrë që të mos mashtroni ata që shikojnë emrin e tij.

Kështu që ne konfiguruam ndërtimin e firmuerit për stm32. Herën tjetër do t'ju tregoj se si

Kur krijoni aplikacionin tuaj të parë në një mikrokontrollues STM32, mund të shkoni në disa mënyra. E para, klasike, marrim përshkrimin e saktë të kontrolluesit në faqen e internetit www.st.com, i cili shfaqet nën emrin "Manuali i referencës" dhe lexojmë përshkrimin e regjistrave periferikë. Pastaj ne përpiqemi t'i regjistrojmë ato dhe të shohim se si funksionojnë pajisjet periferike. Leximi i këtij dokumenti është shumë i dobishëm, por në fazën e parë të zotërimit të një mikrokontrollues, mund ta refuzoni këtë, sado e çuditshme që mund të duket. Inxhinierët e STMicroelectronics kanë shkruar një bibliotekë drejtuese për pajisjet periferike standarde. Për më tepër, ata kanë shkruar shumë shembuj të përdorimit të këtyre drejtuesve, të cilët mund të zvogëlojnë programimin e aplikacionit tuaj në shtypjen e tasteve Ctrl+C dhe Ctrl+V, të ndjekur nga një modifikim i lehtë i shembullit të përdorimit të drejtuesit për t'iu përshtatur nevojave tuaja. Kështu, lidhja e një biblioteke drejtuese periferike me projektin tuaj është metoda e dytë e ndërtimit të një aplikacioni. Përveç shpejtësisë së shkrimit, ka edhe avantazhe të tjera të kësaj metode: universaliteti i kodit dhe përdorimi i bibliotekave të tjera pronësore, si USB, Ethernet, kontrolli i diskut etj., të cilat ofrohen në kodin burimor dhe përdorim. një drejtues standard periferik. Të metat këtë metodë ka gjithashtu: Aty ku mund të arrini me një rresht kodi, drejtuesi standard periferik STM32 do të shkruajë 10. Vetë biblioteka periferike ofrohet gjithashtu në formën e skedarëve burim, kështu që ju mund të gjurmoni se cili bit i cilit regjistër është ndryshuar nga këtë apo atë funksion. Nëse dëshironi, mund të kaloni nga metoda e dytë e shkrimit të një programi në të parën duke komentuar një pjesë të kodit që përdor bibliotekën standarde me tuajën, e cila kontrollon drejtpërdrejt regjistrin periferik. Si rezultat i këtij veprimi, ju do të fitoni shpejtësinë e kontrollit, sasinë e RAM-it dhe ROM-it, por do të humbni në shkathtësinë e kodit. Në çdo rast, inxhinierët e Promelektronikës rekomandojnë përdorimin e bibliotekës së pajisjeve periferike standarde të paktën në fazën e parë.

Vështirësitë më të mëdha e presin zhvilluesin kur lidh bibliotekën me projektin e tij. Nëse nuk dini si ta bëni këtë, mund të shpenzoni shumë kohë në këtë aktivitet, gjë që bie në kundërshtim me vetë idenë e përdorimit të një drejtuesi të gatshëm. Materiali i kushtohet lidhjes së bibliotekës standarde me çdo familje STM32.

Çdo familje STM32 ka bibliotekën e saj të pajisjeve periferike standarde. Kjo për faktin se vetë periferia është e ndryshme. Për shembull, periferia e kontrollorëve STM32L ka një funksion të kursimit të energjisë si një nga detyrat e tij, i cili përfshin shtimin e funksioneve të kontrollit. Një shembull klasik është ADC, i cili në STM32L ka aftësinë për të fikur harduerin në rast të mungesës së gjatë të një komande konvertimi - një nga pasojat e detyrës së kursimit të energjisë. ADC-të e kontrollorëve të familjeve STM32F nuk e kanë një funksion të tillë. Në fakt, për shkak të pranisë së dallimeve harduerike në pajisjet periferike, ne kemi biblioteka të ndryshme drejtuese. Përveç ndryshimit të dukshëm në funksionet e kontrolluesit, ka një përmirësim në pajisjet periferike. Kështu, pajisjet periferike të familjeve të kontrolluesve që u lëshuan më vonë mund të jenë më të menduara dhe më të përshtatshme. Për shembull, pajisjet periferike të kontrollorëve STM32F1 dhe STM32F2 kanë dallime në kontroll. Sipas mendimit të autorit, menaxhimi i pajisjeve periferike STM32F2 është më i përshtatshëm. Dhe është e qartë pse: familja STM32F2 u lëshua më vonë dhe kjo i lejoi zhvilluesit të merrnin parasysh disa nuanca. Prandaj, për këto familje ekzistojnë biblioteka individuale të kontrollit periferik. Ideja pas sa më sipër është e thjeshtë: në faqen e mikrokontrolluesit që do të përdorni, ekziston një bibliotekë periferike e përshtatshme për të.

Pavarësisht dallimeve në pajisjet periferike në familje, shoferët fshehin 90% të dallimeve brenda vetes. Për shembull, funksioni i konfigurimit të ADC-së i përmendur më sipër duket i njëjtë për të gjitha familjet:

void ADC_Init (ADC_Nom, ADC_Param),

ku ADC_Nom është numri ADC në formën ADC1, ADC2, ADC3, etj.

ADC_Param – tregues i strukturës së të dhënave, si duhet të konfigurohet ADC (nga çfarë të fillohet, sa kanale të dixhitalizohen, nëse duhet bërë në mënyrë ciklike, etj.)

10% dallime ndërmjet familjeve, në në këtë shembull, të cilat do të duhet të korrigjohen kur lëvizni nga një familje STM32 në tjetrën, janë të fshehura në strukturën ADC_Param. Në varësi të familjes, numri i fushave në këtë strukturë mund të ndryshojë. Pjesa e përgjithshme ka të njëjtën sintaksë. Kështu, transferimi i një aplikacioni për një familje STM32, i shkruar në bazë të bibliotekave standarde periferike, në një tjetër është shumë i thjeshtë. Për sa i përket universalizimit të zgjidhjeve në mikrokontrolluesit, STMicroelectronics është e parezistueshme!

Pra, ne shkarkuam bibliotekën për STM32 që po përdorim. Ç'pritet më tej? Më pas, ne duhet të krijojmë një projekt dhe të lidhim skedarët e kërkuar me të. Le të shohim krijimin e një projekti duke përdorur si shembull mjedisin e zhvillimit IAR Embedded Workbench. Hapni mjedisin e zhvillimit dhe shkoni te skeda "Project", zgjidhni artikullin "Krijo projekt" për të krijuar një projekt:

Në projektin e ri që shfaqet, futni cilësimet duke lëvizur kursorin mbi emrin e projektit, duke klikuar me të djathtën dhe duke zgjedhur "Opsionet" nga menyja rënëse:

Zonat e memories RAM dhe ROM:

Kur klikoni butonin "Ruaj", mjedisi do të ofrojë për të shkruar një skedar të ri përshkrimi të kontrolluesit në dosjen e projektit. Autori rekomandon krijimin e një skedari individual *.icp për çdo projekt dhe ruajtjen e tij në dosjen e projektit.

Nëse do të korrigjoni projektin tuaj në qark, gjë që rekomandohet, atëherë shkruani llojin e korrigjuesit të përdorur:

Në skedën e korrigjuesit të zgjedhur, ne tregojmë ndërfaqen për lidhjen e korrigjuesit (në rastin tonë, ST-Link është zgjedhur) me kontrolluesin:



Nga kjo pikë e tutje, projekti ynë pa biblioteka është gati për tu kompiluar dhe ngarkuar në kontrollues. Mjedise të tjera si Keil uVision4, Resonance Ride7, etj. do të kërkojnë të njëjtat hapa.

Nëse shkruani rreshtin në skedarin main.c:

#include "stm32f10x.h" ose

#include "stm32f2xx.h" ose

#include "stm32f4xx.h" ose

#include "stm32l15x.h" ose

#include "stm32l10x.h" ose

#include "stm32f05x.h"

duke treguar vendndodhjen këtë skedar, ose duke e kopjuar këtë skedar në dosjen e projektit, atëherë disa zona memorie do të lidhen me regjistrat periferikë të familjes përkatëse. Vetë skedari ndodhet në dosjen standarde të bibliotekës së pajisjeve periferike në seksionin: \CMSIS\CM3\DeviceSupport\ST\STM32F10x (ose një emër i ngjashëm për familjet e tjera). Tani e tutje, ju zëvendësoni adresën e regjistrit periferik në formën e një numri me emrin e tij. Edhe nëse nuk keni ndërmend të përdorni funksionet standarde të bibliotekës, rekomandohet të bëni një lidhje të tillë.

Nëse do të përdorni ndërprerje në projektin tuaj, rekomandohet të përfshini skedarin e fillimit me shtesën *.s, i cili ndodhet në shtegun \CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\iar, ose të ngjashme për familjet e tjera. Është e rëndësishme të theksohet se çdo mjedis ka skedarin e vet. Prandaj, nëse përdorim IAR EWB, duhet të marrim skedarin nga dosja IAR. Kjo është për shkak të ndryshimeve të vogla në sintaksën e mjediseve. Prandaj, në mënyrë që projekti të fillojë menjëherë, inxhinierët e STMicroelectronics shkruan disa versione të skedarëve fillestarë për disa nga mjediset më të njohura të zhvillimit. Shumica e familjeve STM32 kanë një skedar. Familja STM32F1 ka disa skedarë nxitës:

  • startup_stm32f10x_cl.s – për mikrokontrolluesit STM32F105/107
  • startup_stm32f10x_xl.s - për mikrokontrolluesit STM32F101/STM32F103 768kb dhe më shumë
  • startup_stm32f10x_hd.s - për mikrokontrolluesit STM32F101/STM32F103 c Flash memorie 256-512 kb
  • startup_stm32f10x_md.s - për mikrokontrolluesit STM32F101/ STM32F102/STM32F103 me memorie flash 64-128 kB
  • startup_stm32f10x_ld.s - për mikrokontrolluesit STM32F101/ STM32F102/STM32F103 me memorie flash më pak se 64 kB
  • startup_stm32f10x_hd_vl.s për mikrokontrolluesit STM32F100 me memorie flash 256-512 kB
  • startup_stm32f10x_md_vl.s për mikrokontrolluesit STM32F100 me memorie flash 64-128 kB
  • startup_stm32f10x_ld_vl.s për mikrokontrolluesit STM32F100 me memorie flash 32 kb ose më pak

Pra, në varësi të familjes, nënfamiljes dhe mjedisit të zhvillimit, ne shtojmë skedarin e nisjes në projekt:

Këtu përfundon mikrokontrolluesi kur fillon programi. Ndërprerja thërret SystemInit() dhe më pas __iar_program_start në sekuencë. Funksioni i dytë rivendoset ose shkruan paraprakisht vendos vlerat variablat globale, pas së cilës shkon te programi i përdoruesit main(). Funksioni SystemInit() konfiguron orën e mikrokontrolluesit. Është ajo që u përgjigjet pyetjeve:

  • A duhet të kaloj në kristal të jashtëm (HSE)?
  • Si të shumëzoni frekuencën nga HSI/HSE?
  • A është e nevojshme të lidhni një radhë të ngarkesës së komandës?
  • Sa është vonesa e kërkuar gjatë ngarkimit të një komande (për shkak të shpejtësisë së ulët Flash funksionon kujtesa)
  • Si të ndani kronologjinë e autobusëve periferikë?
  • A duhet të vendoset kodi në RAM të jashtëm?

Funksioni SystemInit() mund të shkruhet manualisht në projektin tuaj. Nëse e projektoni këtë funksion si bosh, atëherë kontrolluesi do të funksionojë në një oshilator të brendshëm RC me një frekuencë prej rreth 8 MHz (në varësi të llojit të familjes). Opsioni 2 - lidhni me projektin skedarin system_stm32f10x.c (ose një emër të ngjashëm në varësi të llojit të familjes së përdorur), i cili ndodhet në bibliotekë përgjatë shtegut: Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x. Ky skedar përmban funksionin SystemInit(). Kushtojini vëmendje frekuencës së kristalit të jashtëm HSE_VALUE. Ky parametërështë vendosur në skedarin e kokës stm32f10x.h. Vlera standarde është 8 dhe 25 MHz, në varësi të familjes STM32. Detyra kryesore e funksionit SystemInit() është të kalojë klockimin në kuarc të jashtëm dhe të shumëzojë në një mënyrë të caktuar frekuenca e dhënë. Çfarë ndodh nëse vlera HSE_VALUE është specifikuar si 8 MHz, bërthama duhet të jetë e klockuar në 72 MHz, por në fakt bordi ka kristal 16 MHz? Si rezultat i veprimeve të tilla të pasakta, bërthama do të marrë një orë prej 144 MHz, e cila mund të jetë jashtë funksionimit të garantuar të sistemit në STM32. Ato. kur lidhni skedarin system_stm32f10x.c, do t'ju duhet të specifikoni vlerën HSE_VALUE. E gjithë kjo do të thotë që skedarët system_stm32f10x.c, system_stm32f10x.h dhe stm32f10x.h (ose emra të ngjashëm për familjet e tjera) duhet të jenë individuale për çdo projekt. DHE

Inxhinierët e STMicroelectronics kanë krijuar mjetin e konfigurimit të orës, i cili ju lejon të konfiguroni saktë orën e sistemit. Kjo skedar Excel, i cili gjeneron skedarin system_stm32xxx.c (i ngjashëm në emër për një familje të caktuar familjesh), pasi të specifikojë parametrat e hyrjes dhe daljes së sistemit. Le të shohim funksionimin e tij duke përdorur familjen STM32F4 si shembull.

Opsionet: oshilator i brendshëm RC, oshilator i brendshëm RC me shumëzim frekuence, ose kuarc i jashtëm me shumëzim frekuence. Pas zgjedhjes së burimit të orës, futim parametrat e konfigurimit të dëshiruar të sistemit, të tilla si frekuenca e hyrjes (kur përdoret një kuarc i jashtëm), frekuenca e orës bërthamore, ndarësit e frekuencës së orës së autobusit periferik, funksionimi i tamponit për marrjen e komandës dhe të tjera. Duke klikuar në butonin "Generate", ne kemi një dritare


Përfshirja e skedarit system_stm32f4xx.c dhe analogëve të tij do të kërkojë lidhjen e një skedari tjetër standard të bibliotekës periferike. Për të kontrolluar akordimin, ekziston një grup i tërë funksionesh që thirren nga skedari system_stm32xxxxxx.c. Këto funksione ndodhen në skedarin stm32f10x_rcc.c dhe kokën e tij. Prandaj, kur lidhni skedarin system_stm32xxxxxx.c me projektin, është e nevojshme të përfshini stm32f10x_rcc.c, përndryshe lidhësi i mjedisit do të raportojë mungesën e një përshkrimi të funksioneve me emrin RCC_xxxxxxx. Skedari i specifikuar ndodhet në bibliotekën periferike në shtegun: Libraries\STM32F10x_StdPeriph_Driver\src dhe titulli i tij është \Libraries\STM32F10x_StdPeriph_Driver\inc.

Skedarët e kokës së drejtuesit periferik përfshihen në skedarin stm32f10x_conf.h, i cili referohet nga stm32f10x.h. Skedari stm32f10x_conf.h është thjesht një grup skedarësh kokash për drejtuesit për pajisje periferike të kontrolluesit të veçantë që do të përfshihen në projekt. Fillimisht, të gjitha titujt "#include" janë shënuar si komente. Lidhja e një skedari të kokës periferike përfshin moskomentimin e emrit të skedarit përkatës. Në rastin tonë, kjo është linja #include "stm32f10x_rcc.h". Natyrisht, skedari stm32f10x_conf.h është individual për çdo projekt, sepse projekte të ndryshme përdorin pajisje të ndryshme periferike.

Dhe një gjë të fundit. Ju duhet të specifikoni disa direktiva për paraprocesorin e përpiluesit dhe shtigjet drejt skedarëve të kokës.



Rrugët drejt skedarëve të kokës mund të jenë të ndryshme, në varësi të vendndodhjes së bibliotekës periferike në lidhje me dosjen e projektit, por prania e "USE_STDPERIPH_DRIVER" është e detyrueshme kur lidhni drejtuesit standardë periferikë të bibliotekës.

Pra, ne kemi lidhur bibliotekën standarde me projektin. Për më tepër, ne lidhëm një nga drejtuesit standardë periferikë me projektin, i cili kontrollon orën e sistemit.

Mësuam se si duket struktura e bibliotekës nga brenda, tani disa fjalë se si duket nga jashtë.



Kështu, lidhja e skedarit të kokës stm32f10x.h në aplikacion kërkon lidhjen e skedarëve të tjerë të kokës dhe skedarëve të kodit. Disa nga ato të paraqitura në figurë janë përshkruar më sipër. Disa fjalë për pjesën tjetër. Skedarët STM32F10x_PPP.x janë skedarë drejtues periferikë. Një shembull i lidhjes së një skedari të tillë është treguar më lart; ky është RCC - periferia e kontrollit të orës së sistemit. Nëse duam të lidhim drejtuesit e pajisjeve të tjera periferike, atëherë emri i skedarëve të lidhur merret duke zëvendësuar "PPP" me emrin e pajisjes periferike, për shembull, ADC - STM32F10x_ADC.c, ose portet I/O STM32F10x_GPIO.c, ose DAC - STM32F10x_DAC.c. Në përgjithësi, është intuitivisht e qartë se cili skedar duhet të lidhet kur lidhni një pajisje periferike të caktuar. Skedarët "misc.c", "misc.h" janë në përgjithësi i njëjti STM32F10x_PPP.x, vetëm ata kontrollojnë kernelin. Për shembull, vendosja e vektorëve të ndërprerjes, e cila është e integruar në kernel, ose menaxhimi i kohëmatësit SysTick, i cili është pjesë e kernelit. Skedarët xxxxxxx_it.c përshkruajnë vektorët e ndërprerjeve që nuk mund të maskohen nga kontrolluesi. Ato mund të plotësohen me vektorë të ndërprerjeve periferike. Skedari core_m3.h përshkruan bërthamën CortexM3. Kjo bërthamë është e standardizuar dhe mund të gjendet në mikrokontrolluesit e prodhuesve të tjerë. Për universalizimin ndër-platformë, STMicroelectronics punoi për të krijuar një bibliotekë të veçantë bërthamore CortexM, pas së cilës ARM e standardizoi atë dhe e shpërndau te prodhuesit e tjerë të mikrokontrolluesve. Kështu që kalimi në STM32 nga kontrollorët nga prodhuesit e tjerë me një bërthamë CortexM do të jetë pak më i lehtë.

Pra, ne mund të lidhim bibliotekën e pajisjeve periferike standarde me çdo familje STM32. Ai që mëson se si ta bëjë këtë do të marrë një çmim: programim shumë i thjeshtë i mikrokontrolluesve. Përveç drejtuesve në formën e skedarëve burimor, biblioteka përmban shumë shembuj të përdorimit të pajisjeve periferike. Për shembull, le të shqyrtojmë krijimin e një projekti që përfshin rezultatet e krahasimit të kohëmatësit. Me qasjen tradicionale, ne do të studiojmë me kujdes përshkrimin e regjistrave të kësaj pajisjeje periferike. Por tani mund të studiojmë tekstin e programit të ekzekutimit. Shkojmë te dosja e shembujve të pajisjeve periferike standarde, e cila ndodhet përgjatë shtegut ProjectSTM32F10x_StdPeriph_Examples. Këtu janë dosjet e shembujve me emrin e pajisjeve periferike të përdorura. Shkoni te dosja "TIM". Kohëmatësit në STM32 kanë shumë funksione dhe cilësime, kështu që është e pamundur të demonstrohen aftësitë e kontrolluesit vetëm me një shembull. Prandaj, brenda drejtorisë së specifikuar ka shumë shembuj të përdorimit të kohëmatësve. Ne jemi të interesuar në gjenerimin e një sinjali PWM nga një kohëmatës. Shkoni te dosja "7PWM_Output". Brenda ka një përshkrim të programit në anglisht dhe një grup skedarësh:

main.c stm32f10x_conf.h stm32f10x_it.h stm32f10x_it.c system_stm32f10x.c

Nëse projekti nuk ka ndërprerje, atëherë përmbajtja ndodhet tërësisht në skedarin main.c. Kopjoni këto skedarë në drejtorinë e projektit. Pasi të kemi përpiluar projektin, do të marrim një program për STM32 që do të konfigurojë kohëmatësin dhe portat I/O për të gjeneruar 7 sinjale PWM nga kohëmatësi 1. Më pas, mund të përshtatim kodin tashmë të shkruar me detyrën tonë. Për shembull, zvogëloni numrin e sinjaleve PWM, ndryshoni ciklin e punës, drejtimin e numërimit, etj. Funksionet dhe parametrat e tyre janë përshkruar mirë në skedarin stm32f10x_stdperiph_lib_um.chm. Emrat e funksioneve dhe parametrat e tyre lidhen lehtësisht me qëllimin e tyre për ata që dinë pak anglisht. Për qartësi, këtu është një pjesë e kodit shembull:

/* Konfigurimi i bazës kohore */ TIM_TimeBaseStructure.TIM_Prescaler = 0; // nuk ka parazgjedhje të pulseve të numërimit (regjistri 16-bit) TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // drejtimi i numërimit është lart TIM_TimeBaseStructure.TIM_Period = TimerPeriod; // numëro deri në vlerën e TimerPeriod (konstante në program) TIM_TimeBaseStructure.TIM_ClockDivision = 0; // nuk ka ndarje para-counter TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // numëruesi i tejmbushjes për gjenerimin e ngjarjeve (nuk përdoret në program) TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // futja e vlerave TimeBaseStructure në regjistrat e kohëmatësit 1 (futja e të dhënave në këtë variabël // është më lart) /* Konfigurimi i kanalit 1, 2,3 dhe 4 në modalitetin PWM */ // konfigurimi i daljeve PWM TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // Mënyra e funksionimit PWM2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // aktivizoni daljen e sinjaleve të kohëmatësit PWM TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // aktivizoni daljen plotësuese të kohëmatësit PWM TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; // gjerësia e pulsit Channel1Pulse – konstante në programin TIM_OCInitStructure.TIM_OCPolariteti = TIM_OCPolariteti_I ulët; // vendosja e polaritetit të daljes TIM_OCInitStructure.TIM_OCNPolariteti = TIM_OCNPolariteti_I lartë; // vendosja e polaritetit të daljes plotësuese TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; // vendosja e gjendjes së sigurt të daljes PWM TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; // vendosja e gjendjes së sigurt të daljes PWM plotësuese TIM_OC1Init(TIM1, &TIM_OCInitStructure); // duke futur vlerat e ndryshores TIM_OCInitStructure në regjistrat PWM të kanalit 1 // kohëmatës 1 TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; // ndryshoni gjerësinë e pulsit në variablin OCInitStructure dhe futeni atë në TIM_OC2Init(TIM1, &TIM_OCInitStructure); // regjistron kanalin PWM 2 timer1 TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; // ndryshoni gjerësinë e pulsit në variablin OCInitStructure dhe futeni atë në TIM_OC3Init(TIM1, &TIM_OCInitStructure); // regjistron kanalin PWM 3 timer1 TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; // ndryshoni gjerësinë e pulsit në variablin OCInitStructure dhe futeni atë në TIM_OC4Init(TIM1, &TIM_OCInitStructure); // regjistron kanalin PWM 4 timer1 /* numëruesi TIM1 aktivizon */ TIM_Cmd(TIM1, ENABLE); // start timer1 /* TIM1 Dalja kryesore Aktivizo */ TIM_CtrlPWMOoutputs(TIM1, ENABLE); // aktivizoni funksionimin e rezultateve të krahasimit të kohëmatësit 1

Në anën e djathtë, autori la një koment në Rusisht për secilën rresht të programit. Nëse hapim të njëjtin shembull në përshkrimin e funksioneve të bibliotekës stm32f10x_stdperiph_lib_um.chm, do të shohim që të gjithë parametrat e funksionit të përdorur kanë një lidhje me përshkrimin e tyre, ku do të tregohen vlerat e mundshme. Vetë funksionet kanë gjithashtu një lidhje me përshkrimin dhe kodin e tyre burimor. Kjo është shumë e dobishme sepse... Duke ditur se çfarë bën një funksion, ne mund të gjurmojmë se si e bën atë, cilat pjesë të regjistrave periferikë dhe si ndikon. Ky është, së pari, një burim tjetër informacioni për zotërimin e kontrolluesit, bazuar në përdorimin praktik të kontrolluesit. Ato. fillimisht do të zgjidhni problemin teknik dhe më pas do të studioni vetë zgjidhjen. Së dyti, kjo është një fushë për optimizimin e programit për ata që nuk janë të kënaqur me bibliotekën për sa i përket shpejtësisë dhe vëllimit të kodit.



Epo, deri tani gjithçka po shkon mirë, por vetëm llambat dhe butonat janë gati. Tani është koha për të marrë pajisje periferike më të rënda - USB, UART, I2C dhe SPI. Vendosa të filloj me USB - korrigjuesi ST-Link (madje ai i vërtetë nga Discovery) refuzoi me kokëfortësi të korrigjojë gabimet në tabelën time, kështu që korrigjimi i printimeve përmes USB është e vetmja metodë e korrigjimit të disponueshme për mua. Ju, sigurisht, mundeni përmes UART, por kjo është një bandë telash shtesë.

Unë shkova përsëri rrugën e gjatë- gjeneroi boshllëqet përkatëse në STM32CubeMX, shtoi USB Middleware nga paketa STM32F1Cube në projektin tim. Thjesht duhet të aktivizoni orën e USB-së, të përcaktoni mbajtësit përkatës të ndërprerjeve USB dhe të pastroni gjërat e vogla. Kryesisht të gjitha cilësimet e rëndësishme Moduli USB E kam kopjuar nga STM32GENERIC, me përjashtim të faktit që e ndryshova pak alokimin e memories (ata përdorën malloc dhe unë përdora ndarje statike).

Këtu janë disa pjesë interesante që kam rrëmbyer. Për shembull, në mënyrë që hosti (kompjuteri) të kuptojë se diçka është e lidhur me të, pajisja "deformon" linjën USB D+ (e cila është e lidhur me pinin A12). Pasi e pa këtë, hosti fillon të marrë në pyetje pajisjen se kush është, çfarë ndërfaqesh mund të trajtojë, me çfarë shpejtësie dëshiron të komunikojë, etj. Unë nuk e kuptoj vërtet pse kjo duhet të bëhet përpara fillimit të USB-së, por në stm32duino bëhet në të njëjtën mënyrë.

Dridhje USB

USBD_HandleTypeDef hUsbDeviceFS; void Reenumerate() ( // Inicializoni pinin PA12 GPIO_InitTypeDef pinInit; pinInit.Pin = GPIO_PIN_12; pinInit.Mode = GPIO_MODE_OUTPUT_PP; pinInit.Speed ​​= GPIO_SPEED_FREQ_LOWA; e di: GPIO_SPEED_FREQ_LOWA; numëroni pajisjet USB të aktivizuara autobusi HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET); for(int i panënshkruar i=0; i<512; i++) {}; // Restore pin mode pinInit.Mode = GPIO_MODE_INPUT; pinInit.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &pinInit); for(unsigned int i=0; i<512; i++) {}; } void initUSB() { Reenumerate(); USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS); USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC); USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS); USBD_Start(&hUsbDeviceFS); }


Një pikë tjetër interesante është mbështetja për ngarkuesin stm32duino. Për të ngarkuar firmuerin, së pari duhet të rindizni kontrolluesin në ngarkuesin e ngarkimit. Mënyra më e lehtë është të shtypni butonin e rivendosjes. Por për ta bërë këtë në mënyrë më të përshtatshme, mund të adoptoni përvojën e Arduino. Kur pemët ishin të reja, kontrollorët AVR nuk kishin ende mbështetje USB në bord; kishte një përshtatës USB-UART në bord. Sinjali DTR UART është i lidhur me rivendosjen e mikrokontrolluesit. Kur hosti dërgon sinjalin DTR, mikrokontrolluesi rindizet në ngarkuesin. Punon si betoni i armuar!

Në rastin e përdorimit të USB-së, ne emulojmë vetëm një portë COM. Prandaj, duhet të rindizni vetë në ngarkuesin. Ngarkuesi stm32duino, përveç sinjalit DTR, për çdo rast, pret gjithashtu një konstante magjike të veçantë (1EAF - një referencë për Leaf Labs)

static int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t gjatësi) ( ... rasti CDC_SET_CONTROL_LINE_STATE: dtr_pin++; // pin DTR është aktivizuar prishje; ... statike int8_t CDC_Receive_FS* byte është paketa magjike "1EAF" që e vendos MCU në ngarkuesin. */ if(*Len >= 4) ( /** * Kontrollo nëse hyrja përmban vargun "1EAF". * Nëse po, kontrollo nëse DTR ka është vendosur, për të vendosur MCU në modalitetin e ngarkuesit. */ if(dtr_pin > 3) ( if((Buf == "1")&&(Buf == "E")&&(Buf == "A")&& (Buf == "F")) (HAL_NVIC_SystemReset(); ) dtr_pin = 0; ) ) ... )

Kthimi: MiniArduino

Në përgjithësi, USB funksionoi. Por kjo shtresë funksionon vetëm me bajt, jo me vargje. Kjo është arsyeja pse printimet e korrigjimit duken kaq të shëmtuara.

CDC_Transmit_FS((uint8_t*)"Ping\n", 5); // 5 është një strlen ("Ping") + zero bajt
Ato. Nuk ka fare mbështetje për daljen e formatuar - nuk mund të printoni një numër ose të mblidhni një varg nga copa. Shfaqen opsionet e mëposhtme:

  • Vidhosni printf klasik. Opsioni duket të jetë i mirë, por kërkon +12 kb firmware (në një farë mënyre e quajta aksidentalisht sprintf)
  • Gërmoni zbatimin tuaj të printf-it nga akumulimi juaj. Një herë kam shkruar për AVR, duket se ky zbatim ka qenë më i vogël.
  • Bashkangjitni klasën Print nga Arduino në implementimin STM32GENERIC
Zgjodha opsionin e fundit sepse kodi i bibliotekës Adafruit GFX gjithashtu mbështetet në Print, kështu që më duhet ende ta vidhos atë. Përveç kësaj, unë tashmë kisha në dorë kodin STM32GENERIC.

Krijova një direktori MiniArduino në projektin tim me qëllimin për të vendosur sasinë minimale të kërkuar të kodit atje për të zbatuar pjesët e ndërfaqes arduino që më nevojiteshin. Fillova të kopjoj një skedar në një kohë dhe të shikoj se cilat varësi të tjera duheshin. Kështu përfundova me një kopje të klasës Print dhe disa skedarë të detyrueshëm.

Por kjo nuk mjafton. Ishte ende e nevojshme që disi të lidhej klasa Print me funksionet USB (për shembull, CDC_Transmit_FS ()). Për ta bërë këtë, na u desh të tërhiqnim në klasën SerialUSB. Ai tërhoqi përgjatë klasës Stream dhe një pjesë të inicializimit të GPIO. Hapi tjetër ishte lidhja e UART (kam një GPS të lidhur me të). Kështu që unë solla edhe klasën SerialUART, e cila tërhoqi me vete një shtresë tjetër të inicializimit periferik nga STM32GENERIC.

Në përgjithësi, e gjeta veten në situatën e mëposhtme. Kam kopjuar pothuajse të gjithë skedarët nga STM32GENERIC në MiniArduino-n tim. Kisha gjithashtu kopjen time të bibliotekave USB dhe FreeRTOS (duhej të kisha edhe kopje të HAL dhe CMSIS, por isha shumë dembel). Në të njëjtën kohë, kam një muaj e gjysmë që shënoj kohë - lidh dhe shkëput pjesë të ndryshme, por në të njëjtën kohë nuk kam shkruar asnjë rresht të kodit të ri.

U bë e qartë se ideja ime fillestare ishte të merrja kontrollin e të gjithëve pjesë e sistemit Nuk funksionon shumë mirë. Gjithsesi, një pjesë e kodit të inicializimit jeton në STM32GENERIC dhe duket se është më komode atje. Sigurisht, unë mund të shkurtoj të gjitha varësitë dhe të shkruaj klasat e mia të mbështjellësit për detyrat e mia, por kjo do të më ngadalësonte për një muaj tjetër - ky kod duhet ende të korrigjohet. Sigurisht, kjo do të ishte interesante për situatën tuaj emergjente, por ju duhet të ecni përpara!

Kështu, hodha të gjitha bibliotekat e kopjuara dhe pothuajse të gjithë shtresën e sistemit tim dhe u ktheva te STM32GENERIC. Ky projekt po zhvillohet në mënyrë mjaft dinamike - disa angazhohen në ditë vazhdimisht. Përveç kësaj, gjatë këtij muaji e gjysmë kam studiuar shumë, kam lexuar pjesën më të madhe të Manualit të Referencës STM32, kam parë se si janë bërë bibliotekat HAL dhe mbështjellësit STM32GENERIC dhe kam avancuar në të kuptuarit e përshkruesve USB dhe periferikëve të mikrokontrolluesve. Në përgjithësi, tani isha shumë më i sigurt në STM32GENERIC se më parë.

E kundërta: I2C

Megjithatë, aventurat e mia nuk mbaruan me kaq. Kishte ende UART dhe I2C (ekrani im jeton atje). Me UART gjithçka ishte shumë e thjeshtë. Sapo hoqa ndarjen dinamike të memories dhe në mënyrë që UART-të e papërdorura të mos e hanin këtë memorie, thjesht i komentova ato.

Por zbatimi i I2C në STM32GENERIC ishte pak problem. Një gjë shumë interesante, por që më mori të paktën 2 mbrëmje. Epo, ose dha 2 mbrëmje korrigjimi të vështirë në printime - kështu e shikoni.

Në përgjithësi, zbatimi i ekranit nuk filloi. Në stilin tashmë tradicional, thjesht nuk funksionon dhe kaq. Ajo që nuk funksionon nuk është e qartë. Vetë biblioteka e ekranit (Adafruit SSD1306) duket se është testuar në zbatimin e mëparshëm, por gabimet e ndërhyrjeve ende nuk duhet të përjashtohen. Dyshimi bie mbi HAL dhe zbatimin e I2C nga STM32GENERIC.

Për të filluar, unë komentova të gjithë ekranin dhe kodin I2C dhe shkrova një inicializim I2C pa asnjë bibliotekë, në HAL të pastër

Inicializimi I2C

GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed ​​= GPIO_SPEED_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); __I2C1_CLK_ENABLE(); hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed ​​= 400000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLED; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLED; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLED; HAL_I2C_Init(&hi2c1);


E hodha gjendjen e regjistrave menjëherë pas inicializimit. Unë bëra të njëjtën hale në një version pune në stm32duino. Kjo është ajo që kam marrë (me komente për veten time)

Mirë (Stm32duino):

40005404: 0 0 1 24 - I2C_CR2: Ndërprerja e gabimit është aktivizuar, 36 Mhz
40005408: 0 0 0 0 - I2C_OAR1: zero adresën e vet

40005410: 0 0 0 AF - I2C_DR: regjistri i të dhënave

40005418: 0 0 0 0 - I2C_SR2: regjistri i statusit

E keqe (STM32GENERIC):
40005400: 0 0 0 1 - I2C_CR1: Aktivizo periferik
40005404: 0 0 0 24 - I2C_CR2: 36 Mhz
40005408: 0 0 40 0 ​​- I2C_OAR1: !!! Bit i pa përshkruar në grupin e regjistrit të adresave
4000540C: 0 0 0 0 - I2C_OAR2: Regjistri i adresave të veta
40005410: 0 0 0 0 - I2C_DR: regjistri i të dhënave
40005414: 0 0 0 0 - I2C_SR1: regjistri i statusit
40005418: 0 0 0 2 - I2C_SR2: grup bit i zënë
4000541C: 0 0 80 1E - I2C_CCR: modaliteti 400 kHz
40005420: 0 0 0 B - I2C_TRISE

Dallimi i parë i madh është biti i 14-të i vendosur në regjistrin I2C_OAR1. Ky bit nuk përshkruhet fare në fletën e të dhënave dhe bie në seksionin e rezervuar. Vërtetë, me paralajmërimin që ju duhet ende të shkruani një atje. Ato. Ky është një gabim në libmaple. Por meqenëse gjithçka funksionon atje, atëherë problemi nuk është ky. Le të gërmojmë më tej.

Një tjetër ndryshim është se biti i zënë është vendosur. Në fillim nuk i kushtova ndonjë rëndësi, por duke parë përpara do të them se ishte ai që sinjalizoi problemin!.. Por gjërat e para.

Kam rrahur kodin e inicializimit pa asnjë bibliotekë.

Inicializimi i ekranit

void sendCommand(I2C_HandleTypeDef * dorezë, uint8_t cmd) ( SerialUSB.print("Dërgimi i komandës"); SerialUSB.println(cmd, 16); uint8_t xBuffer; xBuffer = 0x00; xBuffer = cmd;<<1, xBuffer, 2, 10); } ... sendCommand(handle, SSD1306_DISPLAYOFF); sendCommand(handle, SSD1306_SETDISPLAYCLOCKDIV); // 0xD5 sendCommand(handle, 0x80); // the suggested ratio 0x80 sendCommand(handle, SSD1306_SETMULTIPLEX); // 0xA8 sendCommand(handle, 0x3F); sendCommand(handle, SSD1306_SETDISPLAYOFFSET); // 0xD3 sendCommand(handle, 0x0); // no offset sendCommand(handle, SSD1306_SETSTARTLINE | 0x0); // line #0 sendCommand(handle, SSD1306_CHARGEPUMP); // 0x8D sendCommand(handle, 0x14); sendCommand(handle, SSD1306_MEMORYMODE); // 0x20 sendCommand(handle, 0x00); // 0x0 act like ks0108 sendCommand(handle, SSD1306_SEGREMAP | 0x1); sendCommand(handle, SSD1306_COMSCANDEC); sendCommand(handle, SSD1306_SETCOMPINS); // 0xDA sendCommand(handle, 0x12); sendCommand(handle, SSD1306_SETCONTRAST); // 0x81 sendCommand(handle, 0xCF); sendCommand(handle, SSD1306_SETPRECHARGE); // 0xd9 sendCommand(handle, 0xF1); sendCommand(handle, SSD1306_SETVCOMDETECT); // 0xDB sendCommand(handle, 0x40); sendCommand(handle, SSD1306_DISPLAYALLON_RESUME); // 0xA4 sendCommand(handle, SSD1306_DISPLAYON); // 0xA6 sendCommand(handle, SSD1306_NORMALDISPLAY); // 0xA6 sendCommand(handle, SSD1306_INVERTDISPLAY); sendCommand(handle, SSD1306_COLUMNADDR); sendCommand(handle, 0); // Column start address (0 = reset) sendCommand(handle, SSD1306_LCDWIDTH-1); // Column end address (127 = reset) sendCommand(handle, SSD1306_PAGEADDR); sendCommand(handle, 0); // Page start address (0 = reset) sendCommand(handle, 7); // Page end address uint8_t buf; buf = 0x40; for(uint8_t x=1; x<17; x++) buf[x] = 0xf0; // 4 black, 4 white lines for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) { HAL_I2C_Master_Transmit(handle, I2C1_DEVICE_ADDRESS<<1, buf, 17, 10); }


Pas disa përpjekjesh, ky kod funksionoi për mua (në këtë rast, ai vizatoi vija). Kjo do të thotë se problemi është në shtresën I2C të STM32GENERIC. Fillova ta heq gradualisht kodin tim, duke e zëvendësuar me pjesët e duhura nga biblioteka. Por sapo kalova kodin e inicializimit të pinit nga zbatimi im në atë të bibliotekës, i gjithë transmetimi I2C filloi të skadonte.

Pastaj m'u kujtua pjesa e zënë dhe u përpoqa të kuptoja kur ndodh. Doli që flamuri i zënë shfaqet sapo kodi i inicializimit aktivizon rrahjen I2c. Ato. Moduli ndizet dhe menjëherë nuk funksionon. Interesante.

Ne biem në inicializim

uint8_t * pv = (uint8_t*) 0x40005418; //Regjistri I2C_SR2. Duke kërkuar për flamurin BUSY SerialUSB.print("40005418 = "); SerialUSB.println(*pv, 16); // Printon 0 __HAL_RCC_I2C1_CLK_ENABLE(); SerialUSB.print("40005418 = "); SerialUSB.println(*pv, 16); //Shtyp 2


Mbi këtë kod është vetëm inicializimi i kunjave. Epo, çfarë të bëni - mbuloni korrigjimin me printime përgjatë vijës dhe atje

Inicializimi i kunjave STM32GENERIC

void stm32AfInit(lista konst stm32_af_pin_list_type, madhësia int, konst void *instancë, GPIO_TypeDef *port, uint32_t pin, uint32_t modaliteti, uint32_t tërheq) ( ... GPIO_InitTypeDef GPIOIntStrucit_Initt; t.Mode = modalitet;GP IO_InitStruct. Tërhiq = tërheq; GPIO_InitStruct.Speed ​​= GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(port, &GPIO_InitStruct); ...)


Por fat i keq - GPIO_InitStruct është mbushur saktë. Vetëm e imja funksionon, por kjo jo. Vërtet, mistik!!! Gjithçka është sipas tekstit shkollor, por asgjë nuk funksionon. Kam studiuar kodin e bibliotekës rresht pas rreshti, duke kërkuar ndonjë gjë të dyshimtë. Përfundimisht hasa në këtë kod (ai thërret funksionin e mësipërm)

Një pjesë tjetër e inicializimit

void stm32AfI2CInit(const I2C_TypeDef *shembull, ...) ( stm32AfInit(chip_af_i2c_sda, ...); stm32AfInit(chip_af_i2c_scl, ...); )


A shihni një defekt në të? Dhe ajo është! Madje hoqa edhe parametrat e panevojshëm për ta bërë më të qartë problemin. Në përgjithësi, ndryshimi është se kodi im inicializon të dy kunjat menjëherë në një strukturë, dhe kodin STM32GENERIC një nga një. Me sa duket, kodi i inicializimit të pinit ndikon disi në nivelin në këtë pin. Para fillimit, asgjë nuk del në këtë kunj dhe rezistenca e ngre nivelin në një. Në momentin e inicializimit, për ndonjë arsye kontrolluesi vendos zero në këmbën përkatëse.

Ky fakt në vetvete është i padëmshëm. Por problemi është se ulja e linjës SDA gjatë rritjes së linjës SCL është një kusht fillestar për autobusin i2c. Për shkak të kësaj, marrësi i kontrolluesit çmendet, vendos flamurin BUSY dhe fillon të presë të dhënat. Vendosa të mos e heq bibliotekën në mënyrë që të shtoj aftësinë për të inicializuar disa kunja në të njëjtën kohë. Në vend të kësaj, unë thjesht i ndërrova këto 2 rreshta - inicializimi i ekranit ishte i suksesshëm. Rregullimi u miratua në STM32GENERIC.

Nga rruga, në libmaple inicializimi i autobusit bëhet në një mënyrë interesante. Përpara se të filloni të inicializoni pajisjet periferike i2c në autobus, së pari bëni një rivendosje. Për ta bërë këtë, biblioteka i kalon kunjat në modalitetin normal GPIO dhe i tund këto këmbë disa herë, duke simuluar sekuencat e fillimit dhe të ndalimit. Kjo ndihmon për të ringjallur pajisjet e ngecura në autobus. Fatkeqësisht, nuk ka diçka të ngjashme në HAL. Ndonjëherë ekrani im mbërthehet dhe pastaj zgjidhja e vetme është të fikni energjinë.

Inicializimi i i2c nga stm32duino

/** * @brief Rivendos një autobus I2C. * * Rivendosja realizohet duke frekuentuar pulset derisa ndonjë skllav i varur * të lëshojë SDA dhe SCL, më pas duke gjeneruar një kusht START, më pas një kusht STOP *. * * @param dev I2C pajisja */ void i2c_bus_reset(const i2c_dev *dev) ( /* Lëshoni të dyja linjat */ i2c_master_release_bus(dev); /* * Sigurohuni që autobusi të jetë i lirë duke e fiksuar atë derisa ndonjë skllav të lëshojë autobusin *. */ ndërsa (!gpio_read_bit(sda_port(dev), dev->sda_pin)) ( /* Prisni që çdo orë që shtrihet të përfundojë */ ndërsa (!gpio_read_bit(scl_port(dev), dev->scl_pin)) ; vonesë_us(10 ); /* Tërhiqe ulët */ gpio_write_bit(scl_port(dev), dev->scl_pin, 0); delay_us(10); /* Lësho sërish lart */ gpio_write_bit(scl_port(dev), dev->scl_pin, 1); vonesë_us(10); ) /* Gjeneroni kushtin e fillimit dhe më pas ndaloni */ gpio_write_bit(sda_port(dev), dev->sda_pin, 0);delay_us(10);gpio_write_bit(scl_port(dev), dev->scl_pin, 0); vonesë_us (10); gpio_write_bit (scl_port (dev), dev->scl_pin, 1); vonesë_us (10); gpio_write_bit (sda_port (dev), dev->sda_pin, 1); )

Aty sërish: UART

Isha i lumtur që më në fund iu ktheva programimit dhe vazhdova të shkruaj veçori. Pjesa tjetër e madhe ishte lidhja e kartës SD nëpërmjet SPI. Ky në vetvete është një aktivitet emocionues, interesant dhe i dhimbshëm. Unë patjetër do të flas për të veçmas në artikullin tjetër. Një nga problemet ishte ngarkesa e lartë e CPU-së (>50%). Kjo vuri në pikëpyetje efikasitetin energjetik të pajisjes. Dhe ishte e pakëndshme për të përdorur pajisjen, sepse ... UI ishte tmerrësisht budalla.

Duke kuptuar çështjen, gjeta arsyen e këtij konsumi të burimeve. E gjithë puna me kartën SD ndodhi bajt pas bajt, duke përdorur procesorin. Nëse ishte e nevojshme të shkruani një bllok të dhënash në kartë, atëherë për çdo bajt thirret funksioni i dërgimit të bajtit

Për (uint16_t i = 0; i< 512; i++) { spiSend(src[i]);
Jo, nuk është serioze! Ka DMA! Po, biblioteka SD (ajo që vjen me Arduino) është e ngathët dhe duhet ndryshuar, por problemi është më global. E njëjta pamje vërehet edhe në bibliotekën e ekranit, madje dëgjimi i UART-it është bërë përmes një sondazhi. Në përgjithësi, fillova të mendoj se rishkrimi i të gjithë komponentëve në HAL nuk është një ide aq budalla.

Fillova, natyrisht, me diçka më të thjeshtë - një drejtues UART që dëgjon transmetimin e të dhënave nga GPS. Ndërfaqja Arduino nuk ju lejon të bashkëngjitni ndërprerjen UART dhe të rrëmbeni karakteret hyrëse në fluturim. Si rezultat, mënyra e vetme për të marrë të dhëna është përmes sondazheve të vazhdueshme. Sigurisht, unë shtova vTaskDelay(10) në mbajtësin GPS për të zvogëluar ngarkesën të paktën pak, por në realitet kjo është një patericë.

Mendimi i parë, natyrisht, ishte bashkimi i DMA. Madje do të funksiononte nëse nuk do të ishte për protokollin NMEA. Problemi është se në këtë protokoll informacioni thjesht rrjedh, dhe paketa individuale(linjat) ndahen nga një karakter i ndërprerjes së linjës. Për më tepër, çdo rresht mund të jetë me gjatësi të ndryshme. Për shkak të kësaj, nuk dihet paraprakisht se sa të dhëna duhet të merren. DMA nuk funksionon kështu - numri i bajteve duhet të caktohet paraprakisht kur inicializon transferimin. Me pak fjalë, DMA nuk nevojitet më, ndaj ne po kërkojmë një zgjidhje tjetër.

Nëse shikoni nga afër dizajnin e bibliotekës NeoGPS, mund të shihni se biblioteka pranon të dhëna hyrëse bajt pas bajt, por vlerat përditësohen vetëm kur të ketë mbërritur e gjithë linja (për të qenë më të saktë, një grup prej disa rreshtash ). Se. nuk ka asnjë ndryshim nëse do të ushqehen bajtët e bibliotekës një nga një kur merren, ose pastaj të gjitha menjëherë. Pra, ju mund të kurseni kohën e procesorit duke ruajtur linjën e marrë në një tampon dhe këtë mund ta bëni direkt në ndërprerje. Kur të merret e gjithë linja, mund të fillojë përpunimi.

Shfaqet dizajni i mëposhtëm

Klasa e shoferit UART

// Madhësia e bufferit të hyrjes UART konst uint8_t gpsBufferSize = 128; // Kjo klasë trajton ndërfaqen UART që merr karaktere nga GPS dhe i ruan ato në një klasë buferi GPS_UART ( // UART doreza e harduerit UART_HandleTypeDef uartHandle; // Marrja e bufferit të unazës uint8_t rxBuffer; e paqëndrueshme uint8_t lastReadIndex = 0;/VollatI fundit. / Doreza e fijeve GPS TaskHandle_t xGPSThread = NULL;


Megjithëse inicializimi është kopjuar nga STM32GENERIC, ai korrespondon plotësisht me atë që ofron CubeMX

Inicializimi UART

void init() ( // Rivendosni treguesit (vetëm në rast se dikush thërret init() disa herë) lastReadIndex = 0; lastReceivedIndex = 0; // Inicializoni dorezën e fillesës GPS xGPSThread = xTaskGetCurrentTaskHandle(); // Aktivizo clocking-in e peri_RC_KHP ); __HAL_RCC_USART1_CLK_ENABLE(); // Pikat e inicimit në modalitetin e funksionit alternativ GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_9; //Pin TX GPIO_InitStruct.Mode =GPIOPIOt. SPEED_FREQ_ LARTË; HAL_GPIO_Init (GPIOA, &GPIO_InitStruct); GPIO_InitStruct .Pin = GPIO_PIN_10; //Pinën RX GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct.; = 9600; uartHand le.Init. Gjatësia e fjalës = UART_WORDLENGTH_8B; uartHandle.Init.StopBits = UART_STOPBITS_1; uartHandle.Init.Barazia = UART_PARITY_NONE; uartHandle.Init.Mode = UART_MODE_TX_RX; UART_MODE_TX. uartHandle.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&uartHandle); // Do të përdorim ndërprerjen UART për të marrë të dhëna HAL_NVIC_SetPriority (USART1_IRQn, 6, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); // Ne do të presim për një të drejtë të vetme char të marrë direkt në buffer HAL_UART_Receive_IT(&uartHandle, rxBuffer, 1); )


Në fakt, pini TX nuk mund të inicializohej, por uartHandle.Init.Mode mund të vendoset në UART_MODE_RX - ne vetëm do ta marrim atë. Sidoqoftë, le të jetë - çka nëse më duhet të konfiguroj disi modulin GPS dhe të shkruaj komanda në të.

Dizajni i kësaj klase mund të dukej më mirë nëse jo për kufizimet e arkitekturës HAL. Pra, ne nuk mund të vendosim thjesht modalitetin, thonë ata, të pranojmë gjithçka, t'i bashkëngjitni drejtpërdrejt ndërprerjes dhe të rrëmbejmë bajtet e marra drejtpërdrejt nga regjistri marrës. Ne duhet t'i tregojmë HAL paraprakisht se sa dhe ku do të marrim bajt - vetë mbajtësit përkatës do t'i shkruajnë bajtet e marra në buferin e dhënë. Për këtë qëllim, në rreshtin e fundit të funksionit të inicializimit ka një thirrje për HAL_UART_Receive_IT(). Meqenëse gjatësia e vargut është e panjohur paraprakisht, duhet të marrim një bajt në të njëjtën kohë.

Ju gjithashtu duhet të deklaroni deri në 2 thirrje mbrapa. Njëri është një mbajtës i ndërprerjeve, por detyra e tij është vetëm të thërrasë mbajtësin nga HAL. Funksioni i dytë është "kthimi i thirrjes" i HAL që bajt është marrë tashmë dhe është tashmë në buffer.

Kthesat e UART

// Përcjelle përpunimin e ndërprerjes së UART te HAL e jashtme "C" void USART1_IRQHandler(void) ( HAL_UART_IRQHandler(gpsUart.getUartHandle()); ) // HAL e thërret këtë thirrje mbrapsht kur merr një char nga UART. Përcjelle atë në klasën e jashtme "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) (gpsUart.charReceivedCB(); )


Metoda charReceivedCB() përgatit HAL për të marrë bajtin tjetër. Është gjithashtu ai që përcakton se linja tashmë ka përfunduar dhe se kjo mund të sinjalizohet në programin kryesor. Një semafor në modalitetin e sinjalit mund të përdoret si një mjet sinkronizimi, por për qëllime kaq të thjeshta rekomandohet përdorimi i njoftimeve të drejtpërdrejta.

Përpunimi i një bajt të marrë

( është i disponueshëm për t'u lexuar nëse (lastReceivedChar == "\n") vTaskNotifyGiveFromISR (xGPSThread, NULL); )


Funksioni i përgjigjes (në pritje) është waitForString(). Detyra e tij është thjesht të varet në objektin e sinkronizimit dhe të presë (ose të dalë me një afat kohor)

Në pritje të fundit të rreshtit

// Prisni derisa të merret e gjithë rreshti bool waitForString() (kthimi ulTaskNotifyTake(pdTRUE, 10); )


Punon kështu. Thread që është përgjegjës për GPS normalisht fle në funksionin waitForString(). Bajtet që vijnë nga GPS shtohen në një tampon nga mbajtësi i ndërprerjeve. Nëse karakteri \n (fundi i rreshtit) arrin, atëherë ndërprerja zgjon fillin kryesor, i cili fillon të derdhë bajt nga buferi në analizues. Epo, kur analizuesi përfundon përpunimin e paketës së mesazheve, ai do të përditësojë të dhënat në modelin GPS.

Transmetimi GPS

void vGPSTask(void *pvParameters) ( // Inicializimi i GPS duhet të bëhet brenda thread-it GPS pasi doreza e fillesës ruhet // dhe përdoret më vonë për qëllime sinkronizimi gpsUart.init(); për (;;) ( // Prisni derisa të jetë e gjithë vargu marrë nëse(!gpsUart.waitForString()) vazhdon; // Lexoni vargun e marrë dhe analizoni karakterin e transmetimit GPS sipas shkronjave ndërsa(gpsUart.available()) ( int c = gpsUart.readChar(); //SerialUSB.write(c) ; gpsParser.handle(c); ) if(gpsParser.available()) ( GPSDataModel::instance().processNewGPSFix(gpsParser.read()); GPSDataModel::instance().processNewSatellitesData(gpsParser.Parser_satellites. ); ) vTaskDelay(10); ) )


Kam hasur në një moment shumë jo të parëndësishëm në të cilin kam ngecur për disa ditë. Duket sikur kodi i sinkronizimit është marrë nga shembujt, por në fillim nuk funksionoi - rrëzoi të gjithë sistemin. Mendova se problemi ishte në njoftimet direkte (funksionet xTaskNotifyXXX), e ndryshova në semaforë të rregullt, por aplikacioni përsëri u rrëzua.

Doli që duhet të jeni shumë të kujdesshëm me përparësinë e ndërprerjes. Si parazgjedhje, i vendos të gjitha ndërprerjet në zero (prioriteti më i lartë). Por FreeRTOS ka një kërkesë që prioritetet të jenë brenda një diapazoni të caktuar. Ndërprerjet me përparësi shumë të lartë nuk mund të thërrasin funksionet FreeRTOS. Mund të telefonojnë vetëm ndërprerjet me konfigurim prioritarLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY e më poshtë funksionet e sistemit(shpjegim i mirë dhe ). Ky cilësim i paracaktuar është vendosur në 5. Ndryshova përparësinë e ndërprerjes së UART në 6 dhe gjithçka funksionoi.

Atje përsëri: I2C nëpërmjet DMA

Tani mund të bëni diçka më komplekse, siç është drejtuesi i ekranit. Por këtu duhet të bëjmë një ekskursion në teorinë e autobusit I2C. Vetë ky autobus nuk rregullon protokollin e transferimit të të dhënave në autobus - mund të shkruani bajt ose t'i lexoni ato. Madje mund të shkruani dhe më pas të lexoni në një transaksion (për shembull, shkruani një adresë dhe më pas lexoni të dhënat në këtë adresë).

Sidoqoftë, shumica e pajisjeve përcaktojnë protokollin e nivelit më të lartë në të njëjtën mënyrë. pajisja i siguron përdoruesit një grup regjistrash, secili me adresën e vet. Për më tepër, në protokollin e komunikimit, bajt i parë (ose disa) në çdo transaksion përcakton adresën e qelizës (regjistrit) në të cilën do të lexojmë ose shkruajmë më tej. Në këtë rast, është gjithashtu i mundur shkëmbimi i shumë bajteve në stilin "tani do të shkruajmë/lexojmë shumë bajt duke filluar nga kjo adresë". Opsioni i fundit është i mirë për DMA.

Fatkeqësisht, ekrani i bazuar në kontrolluesin SSD1306 ofron një protokoll krejtësisht të ndryshëm - komandë. Bajti i parë i çdo transaksioni është atributi "komandë ose të dhëna". Në rastin e një komande, bajt i dytë është kodi i komandës. Nëse një komandë ka nevojë për argumente, ato kalohen si komanda të veçanta pas të parës. Për të inicializuar ekranin, duhet të dërgoni rreth 30 komanda, por ato nuk mund të vendosen në një grup dhe të dërgohen në një bllok. Ju duhet t'i dërgoni ato një nga një.

Por kur dërgoni një grup pikselësh (buferi i kornizës), është mjaft e mundur të përdorni shërbimet DMA. Kjo është ajo që ne do të përpiqemi.

Por biblioteka Adafruit_SSD1306 është shkruar shumë në mënyrë të ngathët dhe është e pamundur ta ngjeshësh pak gjak nuk punon. Me sa duket biblioteka u shkrua fillimisht për të komunikuar me ekranin nëpërmjet SPI. Më pas dikush shtoi mbështetjen e I2C, por mbështetja e SPI mbeti e aktivizuar. Pastaj dikush filloi të shtonte të gjitha llojet e optimizimeve të nivelit të ulët dhe t'i fshehte ato pas ifdefeve. Si rezultat, doli të ishte një rrëmujë kodi për mbështetjen e ndërfaqeve të ndryshme. Pra, përpara se të shkonim më tej, ishte e nevojshme ta rregullonim atë.

Në fillim u përpoqa ta rregulloja këtë duke inkuadruar kodin për ndërfaqe të ndryshme me ifdefs. Por nëse dua të shkruaj kodin e komunikimit me ekranin, të përdor DMA dhe sinkronizimin përmes FreeRTOS, atëherë nuk do të mund të bëj shumë. Do të jetë më i saktë, por ky kod do të duhet të shkruhet drejtpërdrejt në kodin e bibliotekës. Prandaj, vendosa të ripunoj bibliotekën edhe një herë, të krijoj një ndërfaqe dhe ta vendos secilin drejtues në një klasë të veçantë. Kodi u bë më i pastër dhe do të ishte e mundur të shtohej pa dhimbje mbështetje për drejtuesit e rinj pa ndryshuar vetë bibliotekën.

Ekrani i ndërfaqes së shoferit

// Ndërfaqja për drejtuesin e harduerit // Adafruit_SSD1306 nuk funksionon drejtpërdrejt me harduerin // Të gjitha kërkesat e komunikimit i përcillen klasës së shoferit ISSD1306Driver ( publike: virtual void start() = 0; virtual void sendCommand(uint8_t cmd) = 0 ; virtual void sendData (uint8_t * të dhëna, madhësia_t) = 0; );


Pra, le të shkojmë. Unë kam treguar tashmë inicializimin I2C. Asgjë nuk ka ndryshuar atje. Por dërgimi i komandës u bë pak më i lehtë. E mbani mend kur fola për ndryshimin midis protokolleve të regjistrit dhe komandës për pajisjet I2C? Dhe megjithëse ekrani zbaton një protokoll komandimi, ai mund të simulohet mjaft mirë duke përdorur një protokoll regjistri. Thjesht duhet të imagjinoni që ekrani ka vetëm 2 regjistra - 0x00 për komandat dhe 0x40 për të dhënat. Dhe HAL madje ofron një funksion për këtë lloj transferimi

Dërgimi i një komande në ekran

void DisplayDriver::sendCommand(uint8_t cmd) ( HAL_I2C_Mem_Write(&handle, i2c_addr, 0x00, 1, &cmd, 1, 10); )


Në fillim nuk ishte shumë e qartë për dërgimin e të dhënave. Kodi origjinal dërgoi të dhëna në pako të vogla prej 16 bajt

Kodi i çuditshëm i dërgimit të të dhënave

për (uint16_t i=0; i


Provova të luaja me madhësinë e paketës dhe të dërgoja pako më të mëdha, por në rastin më të mirë pata një ekran të thërrmuar. Epo, ose gjithçka ishte e varur.

Ekrani i prerë



Arsyeja doli të ishte e parëndësishme - tejmbushja e tamponit. Klasa Wire nga Arduino (të paktën STM32GENERIC) siguron buferin e vet prej vetëm 32 bajte. Por pse na duhet fare një buffer shtesë nëse klasa Adafruit_SSD1306 tashmë e ka një të tillë? Për më tepër, me HAL, dërgimi bëhet në një rresht

Transferimi i saktë i të dhënave

void DisplayDriver::sendData(uint8_t * të dhëna, madhësia_t) (HAL_I2C_Mem_Write(&handle, i2c_addr, 0x40, 1, data, size, 10); )


Pra, gjysma e betejës është bërë - ne kemi shkruar një shofer për ekranin në HAL të pastër. Por në këtë version ai ende kërkon burime - 12% e procesorit për një ekran 128x32 dhe 23% për një ekran 128x64. Përdorimi i DMA është vërtet i mirëpritur këtu.

Së pari, le të inicializojmë DMA. Ne duam të implementojmë përcjelljen e të dhënave në I2C nr. 1, dhe ky funksion jeton në kanalin e gjashtë DMA. Inicializoni kopjimin byte-pas-byte nga memorja në pajisjet periferike

Konfigurimi i DMA për I2C

// Ora e kontrolluesit DMA aktivizon __HAL_RCC_DMA1_CLK_ENABLE(); // Inicializoni DMA hdma_tx.Instance = DMA1_Channel6; hdma_tx.Init.Drejtimi = DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_tx.Init.Mode = DMA_NORMAL; hdma_tx.Init.Priority = DMA_PRIORITY_LOW; HAL_DMA_Init(&hdma_tx); // Lidhni dorezën e inicializuar DMA me dorezën I2C __HAL_LINKDMA(&handle, hdmatx, hdma_tx); /* DMA interrupt init */ /* DMA1_Channel6_IRQn konfigurimi i ndërprerjes */ HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 7, 0); HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);


Ndërprerjet janë një pjesë e detyrueshme e dizajnit. Përndryshe, funksioni HAL_I2C_Mem_Write_DMA() do të nisë një transaksion I2C, por askush nuk do ta përfundojë atë. Përsëri kemi të bëjmë me dizajnin e rëndë HAL dhe nevojën për deri në dy kthime. Gjithçka është saktësisht e njëjtë si me UART. Një funksion është një mbajtës i ndërprerjeve - ne thjesht e ridrejtojmë thirrjen në HAL. Funksioni i dytë është një sinjal që të dhënat tashmë janë dërguar.

Trajtuesit e ndërprerjeve DMA

i jashtëm "C" i pavlefshëm DMA1_Channel6_IRQHandler(void) ( HAL_DMA_IRQHandler(displayDriver.getDMAHandle()); ) i jashtëm "C" i pavlefshëm HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef (i2C_HandleTypeDef *hi2cverted);


Natyrisht, nuk do të anketojmë vazhdimisht I2C për të parë nëse transferimi ka përfunduar tashmë? Në vend të kësaj, duhet të flini në objektin e sinkronizimit dhe të prisni derisa të përfundojë transferimi

Transferimi i të dhënave përmes DMA me sinkronizim

void DisplayDriver::sendData(uint8_t * të dhëna, madhësia_t) ( // Fillo transferimin e të dhënave HAL_I2C_Mem_Write_DMA(&handle, i2c_addr, 0x40, 1, të dhënat, madhësia); // Prisni derisa të përfundojë transferimi ulTaskNotidifyTake, 1 (void)0); DisplayDriver::transferCompletedCB() ( // Rifillo lidhjen e shfaqjes vTaskNotifyGiveFromISR(xDisplayThread, NULL); )


Transferimi i të dhënave kërkon ende 24 ms - kjo është pothuajse koha e transferimit të pastër prej 1 kB (madhësia e tamponit të ekranit) në 400 kHz. Vetëm në këtë rast, shumicën e kohës procesori thjesht fle (ose bën gjëra të tjera). Ngarkesa e përgjithshme e CPU-së ra nga 23% në vetëm 1.5-2%. Unë mendoj se kjo shifër ia vlente të luftohej!

Atje përsëri: SPI nëpërmjet DMA

Lidhja e një karte SD përmes SPI ishte në një farë kuptimi më e lehtë - në këtë kohë fillova të instaloja bibliotekën sdfat dhe atje njerëzit e mirë kishin ndarë tashmë komunikimin me kartën në një ndërfaqe të veçantë drejtuesi. Vërtetë, me ndihmën e definicioneve mund të zgjidhni vetëm një nga 4 versionet e gatshme të shoferit, por kjo mund të shpërdorohet lehtësisht dhe të zëvendësohet me zbatimin tuaj.

Ndërfaqja e drejtuesit SPI për të punuar me një kartë SD

// Ky është zbatim i personalizuar i klasës SPI Driver. Biblioteka SdFat është // duke përdorur këtë klasë për të hyrë në kartën SD përmes SPI // // Synimi kryesor i këtij zbatimi është të drejtojë transferimin e të dhënave // ​​mbi DMA dhe të sinkronizojë me aftësitë FreeRTOS. klasa SdFatSPIDriver: publik SdSpiBaseDriver ( // moduli SPI SPI_HandleTypeDef spiHandle; // Trajtimi i fillit GPS TaskHandle_t xSDThread = NULL; publik: SdFatSPIDriver(); aktivizimi i zbrazëtisë virtuale (); virtuali void activate(); virtuali void activate(); uint8_t marrë (); uint8_t virtuale marrë (uint8_t* buf, size_t n); dërgim i pavlefshëm virtual (të dhëna uint8_t); dërgim i pavlefshëm virtual (const uint8_t* buf, size_t n); zbrazëti virtuale zgjidhni (); virtuale void setSpiSettings (SPISettings spiSettings Virtual void unselect(); );


Si më parë, ne fillojmë me diçka të thjeshtë - me një zbatim lisi pa asnjë DMA. Inicializimi është gjeneruar pjesërisht nga CubeMX dhe pjesërisht është bashkuar me implementimin SPI të STM32GENERIC

Inicializimi i SPI

SdFatSPIDriver::SdFatSPIDriver() ( ) //void SdFatSPIDriver::activate(); void SdFatSPIDriver::begin(uint8_t chipSelectPin) ( // Injoro pinin e kaluar CS - Ky drejtues funksionon me një (void)chipSelectPin të paracaktuar; // Inicializimi i dorezës së fillesë GPS xSDThread = xTaskGetCurrentTaskHandle_CurrentTaskHandle (/AL_ClO) korrespondon me korrespondencënGHP_CLO; _ENABLE() ; __HAL_RCC_SPI1_CLK_ENABLE(); // Pikat e inicimit GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7; //MOSI & SCK GPIO_InitStrucpeed_InitStruct. = GPIO_SPE ED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_6; //MISO GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct; GPIO_InitStruct.GPIO_InitStruct.GPIO_InitStruct. PIO_MODE_OUTPUT_PP; GP IO_InitStruct.Speed ​​= GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init (GPIOA, &GPIO_InitStruct); // Cakto pinin CS High si parazgjedhje HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // Init SPI spiHandle.Instance = SPI1; spiHandle.Init.Mode = SPI_MODE_MASTER; spiHandle.Init.Direction = SPI_DIRECTION_2LINES; spiHandle.Init.DataSize = SPI_DATASIZE_8BIT; spiHandle.Init.CLKPolariteti = SPI_POLARITY_LOW; spiHandle.Init.CLKPhase = SPI_PHASE_1EDGE; spiHandle.Init.NSS = SPI_NSS_SOFT; spiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; spiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB; spiHandle.Init.TIMode = SPI_TIMODE_DISABLE; spiHandle.Init.CRCCllogaritje = SPI_CRCCALCULATION_DISABLE; spiHandle.Init.CRCPpolinom = 10; HAL_SPI_Init(&spiHandle); __HAL_SPI_ENABLE (&spiHandle); )


Dizajni i ndërfaqes është përshtatur për Arduino me kunja të numëruara me një numër. Në rastin tim, nuk kishte asnjë pikë për të vendosur pinin CS përmes parametrave - unë e kam këtë sinjal të lidhur rreptësisht me pinin A4, por ishte e nevojshme të pajtohej me ndërfaqen.

Sipas projektimit të bibliotekës SdFat, shpejtësia e portit SPI rregullohet përpara çdo transaksioni. Ato. teorikisht, mund të filloni të komunikoni me kartën me shpejtësi të ulët, dhe më pas ta rrisni atë. Por hoqa dorë nga kjo dhe rregullova shpejtësinë një herë në metodën start(). Pra, metodat e aktivizimit/çaktivizimit rezultuan bosh. Njësoj si setSpiSettings ()

Trajtues të parëndësishëm transaksionesh

void SdFatSPIDriver::activate() ( // Nuk nevojitet aktivizim i veçantë ) void SdFatSPIDriver::deactivate() ( // Nuk nevojitet çaktivizim i veçantë ) void SdFatSPIDriver::setSpiSettings(const SPISettings & spiSettings) ( - ne po i shpërfillim të njëjtat cilësime për të gjitha transferimet)


Metodat e kontrollit të sinjalit CS janë mjaft të parëndësishme

Kontrolli i sinjalit CS

void SdFatSPIDriver::select() ( HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);


Le të kalojmë te pjesa argëtuese - leximi dhe shkrimi. Zbatimi i parë më i lisit pa DMA

Transferimi i të dhënave pa DMA

uint8_t SdFatSPIDriver::receive() ( uint8_t buf; uint8_t dummy = 0xff; HAL_SPI_TransmitReceive(&spiHandle, &dummy, &buf, 1, 10); return buf; ) uint8_t (SdFatSPID) BËJ : Merre nëpërmjet DMA këtu memset(buf, 0xff, n); HAL_SPI_Receive(&spiHandle, buf, n, 10); ktheje 0; ) void SdFatSPIDriver::send(uint8_t të dhëna) ( HAL_SPI_Transmit(&data0,1, ); ) void SdFatSPIDriver::send(const uint8_t* buf, size_t n) ( // TODO: Transmeto përmes DMA këtu HAL_SPI_Transmit(&spiHandle, (uint8_t*)buf, n, 10); )


Në ndërfaqen SPI, marrja dhe transmetimi i të dhënave ndodhin njëkohësisht. Për të marrë diçka, duhet të dërgoni diçka. Zakonisht HAL e bën këtë për ne - ne thjesht thërrasim funksionin HAL_SPI_Receive() dhe ai organizon dërgimin dhe marrjen. Por në fakt, ky funksion dërgon mbeturina që ishin në buferin e pranimit.
Për të shitur diçka të panevojshme, së pari duhet të blini diçka të panevojshme (C) Prostokvashino

Por ka një nuancë. Kartat SD janë shumë kapriçioze. Atyre nuk u pëlqen t'u jepet asgjë ndërsa karta po dërgon të dhëna. Prandaj, më duhej të përdorja funksionin HAL_SPI_TransmitReceive() dhe të dërgoja me forcë 0xffs gjatë marrjes së të dhënave.

Le të bëjmë matje. Lëreni një thread të shkruajë 1 kb të dhëna në kartë në një lak.

Provoni kodin për dërgimin e një transmetimi të dhënash në një kartë SD

uint8_t sd_buf; uint16_t i=0; uint32_t prev = HAL_GetTick(); while(true) (bulkFile.write(sd_buf, 512); bulkFile.write(sd_buf, 512); i++; uint32_t cur = HAL_GetTick(); if(cur-prev >= 1000) (prev = cur; usbDebugWrite( "U ruaj %d kb\n", i); i = 0; ) )


Me këtë qasje, rreth 15-16 kb mund të regjistrohen në sekondë. Jo shume. Por doli që e vendosa preskalerin në 256. Kjo është. Clocking SPI është vendosur në shumë më pak se xhiroja e mundshme. Eksperimentalisht, zbulova se nuk ka kuptim të vendosësh frekuencën më të lartë se 9 MHz (parashkallëzuesi është vendosur në 8) - një shpejtësi regjistrimi më e lartë se 100-110 kb/s nuk mund të arrihet (në një tjetër flash drive, meqë ra fjala , për disa arsye ishte e mundur të regjistrohej vetëm 50-60 kb/s, dhe në të tretën është përgjithësisht vetëm 40 kb/s). Me sa duket gjithçka varet nga koha e ndërprerjes së vetë flash drive.

Në parim, kjo tashmë është më se e mjaftueshme, por ne do të pompojmë të dhëna përmes DMA. Ne vazhdojmë sipas skemës tashmë të njohur. Para së gjithash, inicializimi. Ne marrim dhe transmetojmë nëpërmjet SPI në kanalin e dytë dhe të tretë DMA, përkatësisht.

Inicializimi i DMA

// Ora e kontrolluesit DMA aktivizon __HAL_RCC_DMA1_CLK_ENABLE(); // Kanali Rx DMA dmaHandleRx.Instance = DMA1_Channel2; dmaHandleRx.Init.Direction = DMA_PERIPH_TO_MEMORY; dmaHandleRx.Init.PeriphInc = DMA_PINC_DISABLE; dmaHandleRx.Init.MemInc = DMA_MINC_ENABLE; dmaHandleRx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; dmaHandleRx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; dmaHandleRx.Init.Mode = DMA_NORMAL; dmaHandleRx.Init.Priority = DMA_PRIORITY_LOW; HAL_DMA_Init(&dmaHandleRx); __HAL_LINKDMA(&spiHandle, hdmarx, dmaHandleRx); // Kanali Tx DMA dmaHandleTx.Instance = DMA1_Channel3; dmaHandleTx.Init.Direction = DMA_MEMORY_TO_PERIPH; dmaHandleTx.Init.PeriphInc = DMA_PINC_DISABLE; dmaHandleTx.Init.MemInc = DMA_MINC_ENABLE; dmaHandleTx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; dmaHandleTx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; dmaHandleTx.Init.Mode = DMA_NORMAL; dmaHandleTx.Init.Priority = DMA_PRIORITY_LOW; HAL_DMA_Init(&dmaHandleTx); __HAL_LINKDMA(&spiHandle, hdmatx, dmaHandleTx);


Mos harroni të aktivizoni ndërprerjet. Për mua ata do të shkojnë me prioritetin 8 - pak më të ulët se UART dhe I2C

Konfigurimi i ndërprerjeve DMA

// Setup DMA ndërpret HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 8, 0); HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn); HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 8, 0); HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);


Vendosa që shpenzimet e përgjithshme të ekzekutimit të DMA dhe sinkronizimit për transferime të shkurtra mund të tejkalojnë përfitimin, kështu që për paketat e vogla (deri në 16 bajt) lashë opsionin e vjetër. Paketat më të gjata se 16 bajt dërgohen nëpërmjet DMA. Metoda e sinkronizimit është saktësisht e njëjtë si në seksionin e mëparshëm.

Përcjellja e të dhënave përmes DMA

madhësia konst_t DMA_TRESHOLD = 16; uint8_t SdFatSPIDriver::receive(uint8_t* buf, size_t n) ( memset(buf, 0xff, n); // Nuk përdor DMA për transferime të shkurtra if(n<= DMA_TRESHOLD) { return HAL_SPI_TransmitReceive(&spiHandle, buf, buf, n, 10); } // Start data transfer HAL_SPI_TrsnsmitReceive_DMA(&spiHandle, buf, buf, n); // Wait until transfer is completed ulTaskNotifyTake(pdTRUE, 100); return 0; // Ok status } void SdFatSPIDriver::send(const uint8_t* buf, size_t n) { // Not using DMA for short transfers if(n <= DMA_TRESHOLD) { HAL_SPI_Transmit(&spiHandle, buf, n, 10); return; } // Start data transfer HAL_SPI_Transmit_DMA(&spiHandle, (uint8_t*)buf, n); // Wait until transfer is completed ulTaskNotifyTake(pdTRUE, 100); } void SdFatSPIDriver::dmaTransferCompletedCB() { // Resume SD thread vTaskNotifyGiveFromISR(xSDThread, NULL); }


Sigurisht, nuk ka rrugë pa ndërprerje. Gjithçka këtu është e njëjtë si në rastin e I2C

DMA ndërpret

spiDriver i jashtëm SdFatSPIDriver; i jashtëm "C" i pavlefshëm DMA1_Channel2_IRQHandler(void) (HAL_DMA_IRQHandler(spiDriver.getHandle().hdmarx); ) i jashtëm "C" i pavlefshëm DMA1_Channel3_IRQHandler(void) (HAL_DMA_IRQHandlerhdler(Haldxver) extern. _Tx CpltCallback (SPI_HandleTypeDef *hspi) ( spiDriver.dmaTransferCompletedCB(); ) i jashtëm "C" i pavlefshëm HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) ( spiDriver.dmaTransferCompleted)


Le të nisim dhe kontrollojmë. Për të mos munduar flash drive, vendosa të korrigjoj gabimet duke lexuar një skedar të madh dhe jo duke shkruar. Këtu zbulova një pikë shumë interesante: shpejtësia e leximit në versionin jo-DMA ishte rreth 250-260 kb/s, ndërsa me DMA ishte vetëm 5!!! Për më tepër, konsumi i CPU-së pa përdorur DMA ishte 3%, dhe me DMA - 75-80%!!! Ato. rezultati është saktësisht i kundërt i asaj që pritej.

Jashtë temës rreth 3%

Këtu pata një defekt qesharak me matjen e ngarkesës së procesorit - ndonjëherë funksioni thoshte që procesori ishte vetëm 3% i ngarkuar, megjithëse përqindja duhet të ishte rrahur pa u ndalur. Në fakt, ngarkesa ishte 100% dhe funksioni im i matjes nuk u thirr fare - ai ka përparësinë më të ulët dhe thjesht nuk kishte kohë të mjaftueshme për të. Prandaj, mora vlerën e fundit të kujtuar përpara se të fillonte ekzekutimi. Në kushte normale funksioni funksionon më saktë.


Pasi regjistrova kodin e drejtuesit pothuajse në çdo linjë, zbulova një problem: përdora funksionin e gabuar të kthimit të thirrjes. Fillimisht, kodi im përdori HAL_SPI_Receive_DMA() dhe së bashku me të u përdor kthimi i thirrjes HAL_SPI_RxCplt. Ky dizajn nuk funksionoi për shkak të nuancës me dërgimin e njëkohshëm të 0xff. Kur ndryshova HAL_SPI_Receive_DMA() në HAL_SPI_TransmitReceive_DMA(), më duhej gjithashtu të ndryshoja kthimin e thirrjes në HAL_SPI_TxRxCpltCallback(). Ato. në fakt, leximi u zhvillua, por për shkak të mungesës së thirrjeve, shpejtësia u rregullua me një kohëzgjatje prej 100 ms.

Pasi rregulloi kthimin e telefonatës, gjithçka ra në vend. Ngarkesa e procesorit ra në 2.5% (tani e sinqertë), dhe shpejtësia madje u hodh në 500 kb/s. Vërtetë, parashkallëzuesi duhej të vendosej në 4 - me parashkallëzuesin në 2, pohimet po derdheshin në bibliotekën SdFat. Duket se ky është kufiri i shpejtësisë së kartës sime.

Fatkeqësisht, kjo nuk ka të bëjë me shpejtësinë e regjistrimit. Shpejtësia e shkrimit ishte ende rreth 50-60 kb/s, dhe ngarkesa e procesorit luhatej në intervalin 60-70%. Por, pasi u futa gjatë gjithë mbrëmjes dhe bëra matje në vende të ndryshme, kuptova se funksioni send() i vetë drejtuesit tim (i cili shkruan një sektor 512-byte) kërkon vetëm 1-2 ms, duke përfshirë pritjen dhe sinkronizimin. Ndonjëherë, megjithatë, ndodh një lloj skadimi dhe regjistrimi zgjat 5-7 ms. Por problemi në të vërtetë nuk është në drejtuesin, por në logjikën e punës me sistemin e skedarëve FAT.

Duke u ngjitur në nivelin e skedarëve, ndarjeve dhe grupimeve, detyra për të shkruar 512 në një skedar nuk është aq e parëndësishme. Ju duhet të lexoni tabelën FAT, të gjeni një vend në të për sektorin që do të shkruhet, të shkruani vetë sektorin, të përditësoni hyrjet në tabelën FAT, t'i shkruani këta sektorë në disk, të përditësoni hyrjet në tabelën e skedarëve dhe drejtorive, dhe një mori gjërash të tjera. Në përgjithësi, një thirrje në FatFile::write() mund të zgjasë deri në 15-20 ms, dhe një pjesë e madhe e kësaj kohe merret nga puna aktuale e procesorit për të përpunuar të dhënat në sistemin e skedarëve.

Siç e kam vërejtur tashmë, ngarkesa e procesorit gjatë regjistrimit është 60-70%. Por ky numër varet gjithashtu nga lloji i sistemit të skedarëve (Fat16 ose Fat32), madhësia dhe, në përputhje me rrethanat, numri i këtyre grupimeve në ndarje, shpejtësia e vetë flash drive, sa i ngarkuar dhe i fragmentuar është media, përdorimi emrat e skedarëve të gjatë dhe shumë më tepër. Prandaj ju kërkoj t'i trajtoni këto matje si një lloj shifra relative.

Përsëri: USB me buferim të dyfishtë

Doli interesante me këtë komponent. Implementimi origjinal i serialit USB nga STM32GENERIC kishte një sërë mangësish dhe vendosa ta rishkruaj vetë. Por ndërsa po studioja se si funksionon USB CDC, lexoja kodin burimor dhe studioja dokumentacionin, djemtë nga STM32GENERIC përmirësuan ndjeshëm zbatimin e tyre. Por gjërat e para së pari.

Pra, zbatimi origjinal nuk më përshtatet për arsyet e mëposhtme:

  • Mesazhet dërgohen në mënyrë sinkronike. Ato. një transferim banal byte-pas-byte i të dhënave nga GPS UART në USB pret që çdo bajt individual të dërgohet. Për shkak të kësaj, ngarkesa e procesorit mund të arrijë deri në 30-50%, që është sigurisht shumë (shpejtësia UART është vetëm 9600)
  • Nuk ka sinkronizim. Kur printoni mesazhe nga fije të shumta, dalja është një petë mesazhesh që pjesërisht mbishkruajnë njëra-tjetrën
  • Teprica e buferave të pranimit dhe dërgimit. Disa buffer janë deklaruar në USB Middleware, por në të vërtetë nuk përdoren. Disa buferë të tjerë janë deklaruar në klasën SerialUSB, por duke qenë se unë po përdor vetëm daljen, buferi i pranimit thjesht po harxhon memorie.
  • Më në fund, thjesht jam i mërzitur nga ndërfaqja e klasës Print. Nëse, për shembull, dua të shfaq vargun "shpejtësia aktuale XXX km/h", atëherë duhet të bëj deri në 3 thirrje - për pjesën e parë të vargut, për numrin dhe për pjesën tjetër të vargut. Personalisht, unë jam më afër në frymë me printf klasik. Transmetimet plus janë gjithashtu në rregull, por duhet të shikoni se çfarë lloj kodi gjenerohet nga përpiluesi.
Tani për tani, le të fillojmë me diçka të thjeshtë - dërgimin sinkron të mesazheve, pa sinkronizim dhe formatim. Në fakt, sinqerisht kopjova kodin nga STM32GENERIC.

Zbatimi "përballë"

i jashtëm USBD_HandleTypeDef hUsbDeviceFS; i pavlefshëm usbDebugWrite(uint8_t c) ( usbDebugWrite(&c, 1); ) i pavlefshëm usbDebugWrite(const char * str) ( usbDebugWrite((const uint8_t *)str, strlen(str)); ) i pavlefshëm usbDebugWrite ) ( // Injoroni dërgimin e mesazhit nëse USB nuk është i lidhur nëse (hUsbDeviceFS.dev_state != USBD_STATE_CONFIGURED) kthehet; // Transmetoni mesazhin por jo më shumë se koha e skadimit uint32_t timeout = HAL_GetTick() + 5; while(HAL_GetTick()< timeout) { if(CDC_Transmit_FS((uint8_t*)buffer, size) == USBD_OK) { return; } } }


Formalisht, ky nuk është kod sinkron, sepse nuk pret që të dhënat të dërgohen. Por ky funksion pret derisa të dërgohen të dhënat e mëparshme. Ato. thirrja e parë do të dërgojë të dhëna në port dhe do të dalë, por thirrja e dytë do të presë derisa të dhënat e dërguara në thirrjen e parë të dërgohen realisht. Në rast të një afati kohor, të dhënat humbasin. Gjithashtu, asgjë nuk ndodh nëse nuk ka fare lidhje USB.

Sigurisht, kjo është vetëm një përgatitje, sepse... ky zbatim nuk zgjidh problemet e identifikuara. Çfarë nevojitet për ta bërë këtë kod asinkron dhe jo bllokues? Epo, të paktën një tampon. Por kur të transferohet ky buffer?

Unë mendoj se ia vlen të bëni një ekskursion të shkurtër në parimet e funksionimit të USB. Fakti është se vetëm hosti mund të fillojë transferimin në protokollin USB. Nëse një pajisje ka nevojë të transferojë të dhëna te hosti, të dhënat përgatiten në një bufer të veçantë PMA (Zonë e kujtesës së paketës) dhe pajisja pret që hosti të marrë këto të dhëna. Funksioni CDC_Transmit_FS() përgatit buferin PMA. Ky bufer jeton brenda pajisjes periferike USB, dhe jo në kodin e përdoruesit.

Sinqerisht doja të vizatoja një fotografi të bukur këtu, por nuk arrija të kuptoja se si ta shfaqja më mirë.

Por do të ishte mirë të zbatohej skema e mëposhtme. Kodi i klientit shkruan të dhënat në një bufer ruajtjeje (përdoruesi) sipas nevojës. Herë pas here vjen hosti dhe merr gjithçka që është grumbulluar në tampon në atë moment. Kjo është shumë e ngjashme me atë që përshkrova në paragrafin e mëparshëm, por ka një paralajmërim kryesor: të dhënat janë në buferin e përdoruesit, jo në PMA. Ato. Do të doja të bëja pa thirrur CDC_Transmit_FS(), i cili transferon të dhëna nga buferi i përdoruesit në PMA, dhe në vend të kësaj kap kthimin e thirrjes "këtu ka mbërritur hosti, duke kërkuar të dhëna".

Fatkeqësisht, kjo qasje nuk është e mundur në modelin aktual të USB CDC Middleware. Më saktësisht, mund të jetë e mundur, por ju duhet të futeni në zbatimin e drejtuesit të CDC. Unë nuk kam ende përvojë të mjaftueshme në protokollet USB për ta bërë këtë. Përveç kësaj, nuk jam i sigurt se kufijtë kohorë të USB-së janë të mjaftueshëm për një operacion të tillë.

Për fat të mirë, në atë moment vura re se STM32GENERIC tashmë kishte udhëtuar rreth një gjëje të tillë. Këtu është kodi që unë ripunova në mënyrë krijuese prej tyre.

USB Serial i dyfishtë me bufer

#define USB_SERIAL_BUFFER_SIZE 256 uint8_t usbTxBuffer; i paqëndrueshëm uint16_t usbTxHead = 0; i paqëndrueshëm uint16_t usbTxTail = 0; volatile uint16_t usbTransmetimi = 0; uint16_t transmetuesContigutiguousBuffer () (Uint16_t Count = 0; // Transmetoni të dhënat kontigjente deri në fund të tamponit nëse (USBTXHEAD> USBTXTAil) (Count = USBTXHEAD - USBTXTAIL;) Else (Count = Sizeof (USBTXbuffer) - USBTXTAil;) (&usbTxBuffer, count); kthen numërimin; ) void usbDebugWriteInternal(const char *buffer, size_t size, bool reverse = false) ( // Injoro dërgimin e mesazhit nëse USB nuk është i lidhur nëse (hUsbDeviceFS.dev_state != USBD_STATE_CONFIGURED) kthehu / / Transmetoni mesazhin por jo më shumë se koha e skadimit uint32_t timeout = HAL_GetTick() + 5; // Mbroni këtë funksion nga dollapi i shumëfishtë MutexLocker (usbMutex); // Kopjo të dhënat në buffer për(size_t i=0; i< size; i++) { if(reverse) --buffer; usbTxBuffer = *buffer; usbTxHead = (usbTxHead + 1) % sizeof(usbTxBuffer); if(!reverse) buffer++; // Wait until there is a room in the buffer, or drop on timeout while(usbTxHead == usbTxTail && HAL_GetTick() < timeout); if (usbTxHead == usbTxTail) break; } // If there is no transmittion happening if (usbTransmitting == 0) { usbTransmitting = transmitContiguousBuffer(); } } extern "C" void USBSerialTransferCompletedCB() { usbTxTail = (usbTxTail + usbTransmitting) % sizeof(usbTxBuffer); if (usbTxHead != usbTxTail) { usbTransmitting = transmitContiguousBuffer(); } else { usbTransmitting = 0; } }


Ideja pas këtij kodi është si më poshtë. Megjithëse nuk ishte e mundur të kapej njoftimi "hotësi ka mbërritur dhe kërkon të dhëna", doli që ishte e mundur të organizohej një thirrje mbrapa "I dërgova të dhënat te hosti, ju mund të derdhni atë tjetër". Rezulton të jetë një lloj buferi i dyfishtë - ndërsa pajisja pret që të dhënat të dërgohen nga buferi i brendshëm PMA, kodi i përdoruesit mund të shtojë bajt në buferin e ruajtjes. Kur të përfundojë dërgimi i të dhënave, buferi i ruajtjes transferohet në PMA. E tëra që mbetet është të organizohet pikërisht kjo kthim. Për ta bërë këtë, duhet të ndryshoni pak funksionin USBD_CDC_DataIn().

Regjistruar USB Middleware

static uint8_t USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum) ( USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData; if(pdev0dct=pClassLcT=p ; USBSerialTransferCompleted CB();ktheje USBD_OK ;) tjetër (kthejeni USBD_FAIL;))


Nga rruga, funksioni usbDebugWrite mbrohet nga një mutex dhe duhet të funksionojë si duhet nga fijet e shumta. Nuk e mbrojta funksionin USBSerialTransferCompletedCB() - ai thirret nga një ndërprerje dhe funksionon në variabla të paqëndrueshme. Sinqerisht, ka një gabim diku këtu, simbolet gëlltiten shumë herë pas here. Por për mua kjo nuk është kritike për korrigjimin e gabimeve. Kjo nuk do të quhet në kodin e "prodhimit".

Atje përsëri: printf

Deri tani kjo gjë mund të funksionojë vetëm me vargje konstante. Është koha për të shtrënguar analogun printf(). Nuk dua të përdor funksionin real printf() - ai përfshin 12 kilobajt kod shtesë dhe një "grumbull" që nuk e kam. Më në fund gjeta regjistruesin tim të korrigjimit, të cilin dikur e shkrova për AVR. Implementimi im mund të printojë vargje, si dhe numra në format dhjetor dhe heksadecimal. Pas disa mbarimit dhe testimit doli diçka e tillë:

Implementimi i thjeshtuar i printf

// Zbatimi i sprintf kërkon më shumë se 10 kb dhe shton një grumbull në projekt. Unë mendoj se kjo është // shumë për funksionalitetin që më nevojitet // // Më poshtë është një funksion i hedhjes si printf i homebrew i cili pranon: // - %d për shifra // - %x për numrat si HEX // - %s për vargjet // - %% për simbolin e përqindjes // // Implementimi mbështet gjithashtu gjerësinë e vlerës si dhe mbushjen zero // Printoni numrin në bufer (në rend të kundërt) // Kthen numrin e simboleve të printuara size_t PrintNum(vlera int e panënshkruar , uint8_t radix, char * buf, uint8_t gjerësia, char padSymbol) ( //TODO kontrolloni negativ këtu size_t len ​​= 0; // Printoni numrin do ( char digit = vlera % radix; *(buf++) = shifra< 10 ? "0" + digit: "A" - 10 + digit; value /= radix; len++; } while (value >0); //Shto mbushje zero while(len< width) { *(buf++) = padSymbol; len++; } return len; } void usbDebugWrite(const char * fmt, ...) { va_list v; va_start(v, fmt); const char * chunkStart = fmt; size_t chunkSize = 0; char ch; do { // Get the next byte ch = *(fmt++); // Just copy the regular characters if(ch != "%") { chunkSize++; continue; } // We hit a special symbol. Dump string that we processed so far if(chunkSize) usbDebugWriteInternal(chunkStart, chunkSize); // Process special symbols // Check if zero padding requested char padSymbol = " "; ch = *(fmt++); if(ch == "0") { padSymbol = "0"; ch = *(fmt++); } // Check if width specified uint8_t width = 0; if(ch >"0" && kap<= "9") { width = ch - "0"; ch = *(fmt++); } // check the format switch(ch) { case "d": case "u": { char buf; size_t len = PrintNum(va_arg(v, int), 10, buf, width, padSymbol); usbDebugWriteInternal(buf + len, len, true); break; } case "x": case "X": { char buf; size_t len = PrintNum(va_arg(v, int), 16, buf, width, padSymbol); usbDebugWriteInternal(buf + len, len, true); break; } case "s": { char * str = va_arg(v, char*); usbDebugWriteInternal(str, strlen(str)); break; } case "%": { usbDebugWriteInternal(fmt-1, 1); break; } default: // Otherwise store it like a regular symbol as a part of next chunk fmt--; break; } chunkStart = fmt; chunkSize=0; } while(ch != 0); if(chunkSize) usbDebugWriteInternal(chunkStart, chunkSize - 1); // Not including terminating NULL va_end(v); }


Implementimi im është shumë më i thjeshtë se ai i bibliotekës, por mund të bëjë gjithçka që më nevojitet - të printojë vargje, numra dhjetorë dhe heksadecimalë me formatim (gjerësia e fushës, duke përfunduar numrin me zero në të majtë). Nuk di ende si të printojë numra negativë ose numra me pikë lundruese, por nuk është e vështirë të shtosh. Më vonë unë mund të bëj të mundur që të shkruaj rezultatin në një buffer string (si sprintf) dhe jo vetëm në USB.

Performanca e këtij kodi është rreth 150-200 kb/s duke përfshirë transmetimin nëpërmjet USB-së dhe varet nga numri (gjatësia) e mesazheve, kompleksiteti i vargut të formatit dhe madhësia e buferit. Kjo shpejtësi është mjaft e mjaftueshme për të dërguar disa mijëra mesazhe të vogla në sekondë. Gjëja më e rëndësishme është që telefonatat të mos bllokohen.

Edhe më keq: HAL i nivelit të ulët

Në parim, mund të kishim përfunduar atje, por vura re që djemtë nga STM32GENERIC sapo shtuan një HAL të ri. Gjëja interesante për të është se shumë skedarë u shfaqën me emrin stm32f1xx_ll_XXXX.h. Ata zbuluan një zbatim alternativ dhe të nivelit më të ulët të HAL. Ato. një HAL i rregullt ofron një ndërfaqe mjaft të nivelit të lartë në stilin e "merr këtë grup dhe ma jep mua duke përdorur këtë ndërfaqe. Raportoni përfundimin me një ndërprerje.” Përkundrazi, skedarët me shkronjat LL në emër ofrojnë një ndërfaqe të nivelit më të ulët si "vendosni këta flamuj për një regjistër të tillë".

Misticizmi i qytetit tonë

Pasi pashë skedarët e rinj në depon e STM32GENERIC, doja të shkarkoja kompletin e plotë nga faqja e internetit e ST. Por kërkimi në google më çoi vetëm te HAL (STM32 Cube F1) versioni 1.4, i cili nuk përmban këto skedarë të rinj. Konfiguruesi grafik STM32CubeMX gjithashtu ofroi këtë version. Pyeta zhvilluesit e STM32GENERIC se ku e morën versionin e ri. Për habinë time, mora një lidhje në të njëjtën faqe, vetëm tani ajo ofroi shkarkimin e versionit 1.6. Google gjithashtu papritmas filloi të "gjente" versionin e ri, si dhe CubeMX të përditësuar. Misticizëm dhe asgjë më shumë!


Pse është e nevojshme kjo? Në shumicën e rasteve, një ndërfaqe e nivelit të lartë në fakt e zgjidh problemin mjaft mirë. HAL (Hardware Abstraction Layer) i përshtatet plotësisht emrit të tij - ai abstrakton kodin nga regjistrat e procesorit dhe harduerit. Por në disa raste, HAL kufizon imagjinatën e programuesit, ndërsa duke përdorur abstraksione të nivelit më të ulët do të ishte e mundur të zbatohej detyra në mënyrë më efikase. Në rastin tim këto janë GPIO dhe UART.

Le të provojmë ndërfaqet e reja. Le të fillojmë me llamba. Fatkeqësisht, ende nuk ka shembuj të mjaftueshëm në internet. Ne do të përpiqemi të kuptojmë komentet e kodit për funksionet, për fat të mirë gjithçka është në rregull.

Me sa duket edhe këto gjëra të nivelit të ulët mund të ndahen në 2 pjesë:

  • funksione pak më të larta në stilin e një HAL të rregullt - këtu është struktura e inicializimit, ju lutemi inicializoni periferinë për mua.
  • Vendosësit e niveleve pak më të ulëta dhe marrësit e flamujve ose regjistrave individualë. Në pjesën më të madhe, funksionet e këtij grupi janë inline dhe vetëm me kokë
Si parazgjedhje, të parat çaktivizohen nga USE_FULL_LL_DRIVER. Epo, ata janë me aftësi të kufizuara dhe dreqin me ta. Ne do të përdorim të dytën. Pas një shamanizmi të vogël mora këtë shofer LED

Morgulka në LL HAL

// Klasa për të inkapsuluar punën me LED(et) në bord // // Shënim: kjo klasë inicializon kunjat përkatëse në konstruktor. // Mund të mos funksionojë siç duhet nëse objektet e kësaj klase krijohen si variabla globale LEDDriver ( const uint32_t pin = LL_GPIO_PIN_13; public: LEDDriver() ( //aktivizo orën në periferinë GPIOC __HAL_RCC_GPIOC_IS_CLK_ENABLED1 si output / Input / Input / Input / ENABLED1); LL_GPIO_SetPinMode(GPIOC, pin, LL_GPIO_MODE_OUTPUT); LL_GPIO_SetPinOutputType(GPIOC, pin, LL_GPIO_OUTPUT_PUSHPULL); LL_GPIO_SetPinSpeed(GPIOC_GPIO_MODE_OUTPUT); LL_GPIO_ResetOutput Pin(GPIOC, pin); ) void turnOff() (LL_GPIO_SetOutputPin(GPIOC , pin); ) void toggle() (LL_GPIO_TogglePin(GPIOC, pin); ) ); void vLEDThread(void *pvParameters) ( LEDDriver LED; // Thjesht pulsoni një herë në 2 sekonda për (;;) ( vTaskDelay(2000); led.turnOn(); vTaskDelay(100); led.turnOff(); ) )


Gjithçka është shumë e thjeshtë! Gjëja e bukur është se këtu vërtet punoni drejtpërdrejt me regjistra dhe flamuj. Nuk ka shpenzime të përgjithshme për modulin HAL GPIO, i cili vetë përpilon deri në 450 bajt, dhe kontrollin e pinit nga STM32GENERIC, i cili merr 670 bajtë të tjerë. Këtu, në përgjithësi, e gjithë klasa me të gjitha thirrjet futet në funksionin vLEDThread, i cili është vetëm 48 bajt në madhësi!

Nuk e kam përmirësuar kontrollin e orës nëpërmjet LL HAL. Por kjo nuk është kritike, sepse... thirrja e __HAL_RCC_GPIOC_IS_CLK_ENABLED() nga HAL normale është në fakt një makro që thjesht vendos disa flamuj në regjistra të caktuar.

Është po aq e lehtë me butona

Butonat nëpërmjet LL HAL

// Caktimi i kunjave konst uint32_t SEL_BUTTON_PIN = LL_GPIO_PIN_14; const uint32_t OK_BUTTON_PIN = LL_GPIO_PIN_15; ( PIOC, SEL_BUTTON_PIN, LL_ GPIO_PULL_DOWN); LL_GPIO_SetPinMode( GPIOC , OK_BUTTON_PIN, LL_GPIO_MODE_INPUT); LL_GPIO_SetPinPull(GPIOC, OK_BUTTON_PIN, LL_GPIO_PULL_DOWN); ) // Gjendja e butonit të leximit (kryej fillimisht debounce) inline bool getBututint( ifButinT3 et(GPIOC, pin) ( // dob ouncing vTaskDelay( DEBOUNCE_DURATION ); if(LL_GPIO_IsInputPinSet(GPIOC, pin)) kthen true; ) kthe false; )


Me UART gjithçka do të jetë më interesante. Më lejoni t'ju kujtoj problemin. Kur përdorni HAL, marrësi duhej të "ringarkohej" pas çdo bajt të marrë. Modaliteti "merr gjithçka" nuk ofrohet në HAL. Dhe me LL HAL ne duhet të kemi sukses.

Vendosja e kunjave jo vetëm që më bëri të mendoj dy herë, por gjithashtu më bëri të shikoja manualin e referencës

Vendosja e kunjave UART

// Pikat e inicimit në modalitetin e funksionit alternativ LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_9, LL_GPIO_MODE_ALTERNATE); //Pin TX LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_9, LL_GPIO_SPEED_FREQ_HIGH); LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_9, LL_GPIO_OUTPUT_PUSHPULL); LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_10, LL_GPIO_MODE_INPUT); //Pin RX


Ripërpunimi i inicializimit të UART për ndërfaqe të reja

Inicializimi UART

// Përgatitja për inicializimin LL_USART_Disable(USART1); // Init LL_USART_SetBaudRate(USART1, HAL_RCC_GetPCLK2Freq(), 9600); LL_USART_SetDataWidth(USART1, LL_USART_DATAWIDTH_8B); LL_USART_SetStopBitsLength(USART1, LL_USART_STOPBITS_1); LL_USART_SetParity(USART1, LL_USART_PARITY_NONE); LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_TX_RX); LL_USART_SetHWFlowCtrl(USART1, LL_USART_HWCONTROL_NONE); // Do të përdorim ndërprerjen UART për të marrë të dhëna HAL_NVIC_SetPriority (USART1_IRQn, 6, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); // Aktivizo ndërprerjen UART në marrjen e bajtit LL_USART_EnableIT_RXNE(USART1); // Më në fund aktivizoni pajisjen periferike LL_USART_Enable(USART1);


Tani ndërprerje. Në versionin e mëparshëm, ne kishim deri në 2 funksione - njëri përpunoi ndërprerjen, dhe i dyti ishte një thirrje mbrapa (nga i njëjti ndërprerje) në lidhje me bajtin e marrë. Në versionin e ri, ne e konfiguruam ndërprerjen që të marrë vetëm një bajt, kështu që bajtin e marrë do ta marrim menjëherë.

Ndërprerja e UART

// Ruani bajtin e marrë në linjë void charReceivedCB(uint8_t c) ( rxBuffer = c; lastReceivedIndex++; // Nëse është marrë një simbol EOL, njoftoni thread-in GPS se linja është e disponueshme për t'u lexuar nëse(c == "\n") vTaskNotifyGiveFromISR(xGPSTh NULL); ) i jashtëm "C" i pavlefshëm USART1_IRQHandler(void) (uint8_t byte = LL_USART_ReceiveData8(USART1); gpsUart.charReceivedCB(byte); )


Madhësia e kodit të shoferit u ul nga 1242 në 436 bajt, dhe konsumi i RAM-it nga 200 në 136 (nga të cilat 128 janë buferë). Jo keq për mendimin tim. E vetmja keqardhje është se kjo nuk është pjesa më grykëse. Do të ishte e mundur të shkurtoja pak diçka tjetër, por për momentin nuk jam duke ndjekur veçanërisht konsumin e burimeve - i kam ende ato. Dhe ndërfaqja e nivelit të lartë HAL funksionon mjaft mirë në rastin e pajisjeve të tjera periferike.

Shikoj prapa

Edhe pse në fillim të kësaj faze të projektit isha skeptik për HAL-in, megjithatë arrita të rishkruaj të gjithë punën me pajisjet periferike: GPIO, UART, I2C, SPI dhe USB. Unë kam bërë përparim të madh në të kuptuarit se si funksionojnë këto module dhe jam përpjekur të përcjell njohuritë në këtë artikull. Por ky nuk është aspak një përkthim i Manualit të Referencës. Përkundrazi, unë punova në kontekstin e këtij projekti dhe tregova se si mund të shkruani drejtues periferikësh në HAL të pastër.

Artikulli doli të ishte një histori pak a shumë lineare. Por në fakt, unë kisha një sërë degëzash në të cilat në të njëjtën kohë sharraja në drejtime saktësisht të kundërta. Në mëngjes mund të hasja probleme me performancën e disa bibliotekave Arduino dhe vendosja me vendosmëri të rishkruaj gjithçka në HAL, dhe në mbrëmje do të zbuloja se dikush kishte shtuar tashmë mbështetjen DMA në STM32GENERIC dhe do të kisha dëshirë të ikja prapa . Ose, për shembull, kaloni disa ditë duke luftuar me ndërfaqet Arduino, duke u përpjekur të kuptoni se si është më i përshtatshëm transferimi i të dhënave përmes I2C, ndërsa në HAL kjo bëhet në 2 rreshta.

Në përgjithësi, arrita atë që doja. Puna kryesore me pajisjet periferike është nën kontrollin tim dhe e shkruar në HAL. Arduino vepron vetëm si një përshtatës për disa biblioteka. Vërtetë, kishin mbetur ende disa bishta. Ju ende duhet të mblidhni guximin tuaj dhe të hiqni STM32GENERIC nga depoja juaj, duke lënë vetëm disa klasa vërtet të nevojshme. Por një pastrim i tillë nuk do të zbatohet më për këtë artikull.

Sa për Arudino dhe klonet e tij. Më pëlqen ende kjo kornizë. Me të, ju mund të prototiponi shpejt diçka pa u shqetësuar vërtet me leximin e manualeve dhe fletëve të të dhënave. Në parim, madje mund të bëni pajisje fundore me Arduino, nëse nuk ka kërkesa të veçanta për shpejtësinë, konsumin ose kujtesën. Në rastin tim, këto parametra janë mjaft të rëndësishëm, kështu që më duhej të kaloja në HAL.

Fillova të punoj në stm32duino. Ky klon me të vërtetë meriton vëmendje nëse dëshironi të keni një Arduino në STM32 dhe gjithçka të funksionojë jashtë kutisë. Përveç kësaj, ata monitorojnë nga afër konsumin e RAM dhe flash. Përkundrazi, vetë STM32GENERIC është më i trashë dhe bazohet në HAL monstruoze. Por kjo kornizë po zhvillohet në mënyrë aktive dhe është gati për t'u përfunduar. Në përgjithësi, unë mund t'i rekomandoj të dy kornizat me një preferencë të lehtë për STM32GENERIC sepse HAL dhe zhvillim më dinamik për momentin. Përveç kësaj, interneti është plot me shembuj për HAL dhe gjithmonë mund të personalizoni diçka që t'i përshtatet vetes.

Unë ende e konsideroj veten HAL me njëfarë neverie. Biblioteka është shumë e rëndë dhe e shëmtuar. Lëshoj faktin që biblioteka është e bazuar në C, gjë që kërkon përdorimin e emrave të gjatë të funksioneve dhe konstantave. Por megjithatë, kjo nuk është një bibliotekë me të cilën është argëtuese të punosh. Përkundrazi, është një masë e nevojshme.

Mirë, ndërfaqja - të brendshmet gjithashtu të bëjnë të mendosh. Funksionet e mëdha me funksionalitet për të gjitha rastet sjellin humbje burimesh. Për më tepër, nëse mund të merreni me kodin e tepërt në flash duke përdorur optimizimin e kohës së lidhjes, atëherë konsumi i madh i RAM-it mund të kurohet vetëm duke e rishkruar atë në LL HAL.

Por kjo nuk është edhe ajo që është shqetësuese, por në disa vende është thjesht mospërfillja e burimeve. Kështu që vura re mbipërdorimin e madh të memories në kodin USB Middleware (formalisht nuk është HAL, por ofrohet si pjesë e STM32Cube). Strukturat USB marrin 2.5 kb memorie. Për më tepër, struktura USBD_HandleTypeDef (544 bytes) përsërit në masë të madhe PCD_HandleTypeDef nga shtresa e poshtme (1056 bytes) - pikat fundore përcaktohen gjithashtu në të. Buferët e transmetuesit deklarohen gjithashtu në të paktën dy vende - USBD_CDC_HandleTypeDef dhe UserRxBufferFS/UserTxBufferFS.

Përshkruesit në përgjithësi deklarohen në RAM. Per cfare? Janë konstante! Pothuajse 400 bajt në RAM. Për fat të mirë, disa nga përshkruesit janë konstante (pak më pak se 300 bajt). Përshkruesit janë informacion i pandryshueshëm. Dhe këtu ekziston një kod i veçantë që i rregullon ato, dhe, përsëri, me një konstante. Dhe madje një që tashmë është përfshirë atje. Për disa arsye, funksionet si SetBuffer nuk pranojnë një buffer konstant, i cili gjithashtu parandalon vendosjen e përshkruesve dhe disa gjërave të tjera në blic. Cila eshte arsyeja? Do të rregullohet për 10 minuta!!!

Ose, struktura e inicializimit është pjesë e dorezës së objektit (për shembull i2c). Pse ta ruani këtë pasi të jetë inicializuar pajisja periferike? Pse më duhen tregues për strukturat e papërdorura - për shembull, pse më duhen të dhëna të lidhura me DMA nëse nuk e përdor atë?

Dhe gjithashtu kodi dublikatë.

rasti USB_DESC_TYPE_CONFIGURATION: if(pdev->dev_speed == USBD_SPEED_HIGH) ( pbuf = (uint8_t *)pdev->pClass->GetHSConfigDescriptor(&len); pbuf = USB_DESC_TYPE_CONFIGURATION; *-puint>pbuf GetFSConfigDescriptor(&len); pbuf = USB_DESC_TYPE_CONFIGURATION; ) ndërprerje;


Një konvertim i veçantë në "lloj Unicode", i cili mund të bëhet në kohën e përpilimit. Për më tepër, një tampon i veçantë është ndarë për këtë

Tallje me të dhënat statistikore

ALIGN_BEGIN uint8_t USBD_StrDesc __ALIGN_END; void USBD_GetString(konst char *desc, uint8_t *unicode, uint16_t *len) ( uint8_t idx = 0; if (desc != NULL) ( *len = USBD_GetLen(desc) * 2 + 2; unicode = *len; unicode = *len; unicode = USBSTRING_DE ; ndërsa (*desc != "\0") ( unicode = *desc++; unicode = 0x00; ) )


Jo fatale, por të bën të pyesësh nëse HAL është aq i mirë sa shkruajnë apologjetët për të? Epo, kjo nuk është ajo që prisni nga një bibliotekë nga prodhuesi dhe e krijuar për profesionistët. Këta janë mikrokontrollues! Këtu njerëzit kursejnë çdo bajt dhe çdo mikrosekondë është e çmuar. Dhe këtu, ju e dini, ka një tampon gjysmë kilogrami dhe konvertim në fluturim të vargjeve konstante. Vlen të përmendet se shumica e komenteve vlejnë për USB Middleware.

UPD: në HAL 1.6 u prish gjithashtu kthimi i thirrjes I2C DMA Transfer Completed. Ato. Atje, kodi që gjeneron një konfirmim kur të dhënat dërgohen përmes DMA është zhdukur plotësisht, megjithëse përshkruhet në dokumentacion. Ka një për pritje, por jo për transmetim. Më duhej të kthehesha në HAL 1.4 për modulin I2C, për fat të mirë ka një modul - një skedar.

Më në fund, do të jap konsumin e blicit dhe RAM-it të komponentëve të ndryshëm. Në seksionin Drivers, unë kam dhënë vlera si për drejtuesit e bazuar në HAL ashtu edhe për drejtuesit e bazuar në LL HAL. Në rastin e dytë, seksionet përkatëse nga seksioni HAL nuk përdoren.

Konsumi i memories

Kategoria Nënkategoria .tekst .rodata .të dhënat .bss
Sistemi vektor i ndërprerjes 272
manipulues ISR 178
libc 760
float matematikë 4872
mëkat/cos 6672 536
kryesore & etj 86
Kodi im Kodi im 7404 833 4 578
printf 442
Fontet 3317
NeoGPS 4376 93 300
FreeRTOS 4670 4 209
Adafruit GFX 1768
Adafruit SSD1306 1722 1024
SdFat 5386 1144
USB Middleware Bërthamë 1740 333 2179
CDC 772
Drejtues UART 268 200
USB 264 846
I2C 316 164
SPI 760 208
Butonat LL 208
LED LL 48
UART LL 436 136
Arduino gpio 370 296 16
të ndryshme 28 24
Printo 822
HAL USB LL 4650
SysTick 180
NVIC 200
DMA 666
GPIO 452
I2C 1560
SPI 2318
RCC 1564 4
UART 974
grumbull (nuk përdoret vërtet) 1068
FreeRTOS Heap 10240

Kjo eshte e gjitha. Do të jem i lumtur të marr komente konstruktive, si dhe rekomandime nëse diçka këtu mund të përmirësohet.

Etiketa:

  • HAL
  • STM32
  • STM32 kub
  • arduino
Shto etiketa

Një listë artikujsh që do të ndihmojnë edhe një fillestar të mësojë mikrokontrolluesin STM32. Detaje për gjithçka me shembuj që variojnë nga ndezja LED deri te kontrolli i motorit pa furça. Shembujt përdorin SPL standarde (Biblioteka Periferike Standarde).

Tabela e testimit STM32F103, programuesi ST-Link dhe softueri për firmware për Windows dhe Ubuntu.

VIC (Nested vectored interrupt controller) – moduli i kontrollit të ndërprerjeve. Vendosja dhe përdorimi i ndërprerjeve. Ndërprisni prioritetet. Ndërprerje të mbivendosura.

ADC (konvertuesi analog në dixhital). Diagrami i furnizimit me energji elektrike dhe shembuj të përdorimit të ADC në mënyra të ndryshme. Kanale të rregullta dhe të injektuara. Përdorimi i ADC me DMA. Termometri i brendshëm. Mbrojtës analog.

Kohëmatësi për qëllime të përgjithshme. Gjenerimi i një ndërprerjeje në intervale të rregullta. Matja e kohës midis dy ngjarjeve.

Kapja e sinjalit nga një kohëmatës duke përdorur shembullin e punës me një sensor tejzanor HC-SR04

Përdorimi i një kohëmatës për të punuar me një kodues.

Gjenerimi i PWM. Kontrolli i ndriçimit LED. Kontrolli i servo drive (servos). Gjenerimi i zërit.

Unë tregova se biblioteka standarde është e lidhur me sistemin. Në fakt, CMSIS është i lidhur - sistemi i përfaqësimit strukturor të përgjithësuar të MK, si dhe SPL - biblioteka standarde periferike. Le të shohim secilën prej tyre:

CMSIS
Është një grup skedarësh me kokë dhe një grup i vogël kodesh për unifikimin dhe strukturimin e punës me thelbin dhe periferinë e MK. Në fakt, pa këto skedarë është e pamundur të punosh normalisht me MK. Bibliotekën mund ta merrni në faqen MK.
Kjo bibliotekë, sipas përshkrimit, u krijua për të unifikuar ndërfaqet kur punoni me çdo MK të familjes Cortex. Megjithatë, në realitet rezulton se kjo është e vërtetë vetëm për një prodhues, d.m.th. Duke kaluar në një mikrokontrollues nga një kompani tjetër, ju jeni të detyruar të studioni pajisjet periferike të tij pothuajse nga e para.
Edhe pse ato skedarë që kanë të bëjnë me bërthamën e procesorit të MK janë identike nga të gjithë prodhuesit (nëse vetëm sepse kanë të njëjtin model të bërthamës së procesorit - të ofruara në formën e blloqeve IP nga ARM).
Prandaj, puna me pjesë të tilla të kernelit si regjistrat, instruksionet, ndërprerjet dhe njësitë e bashkëprocesorit është standarde për të gjithë.
Sa i përket periferisë, STM32 dhe STM8 (papritmas) janë pothuajse të ngjashme, dhe kjo është pjesërisht e vërtetë edhe për MK-të e tjera të lëshuara nga ST. Në pjesën praktike, unë do të tregoj se sa e lehtë është përdorimi i CMSIS. Sidoqoftë, vështirësitë në përdorimin e tij shoqërohen me hezitimin e njerëzve për të lexuar dokumentacionin dhe për të kuptuar modelin MK.

SPL
Biblioteka standarde periferike - bibliotekë standarde periferike. Siç sugjeron emri, qëllimi i kësaj biblioteke është të krijojë një abstraksion për periferinë e MK. Biblioteka përbëhet nga skedarë kokësh ku deklarohen konstante të lexueshme nga njeriu për konfigurimin dhe punën me pajisjet periferike MK, si dhe skedarët e kodit burimor të mbledhura në vetë bibliotekën për operacionet me pajisjet periferike.
SPL është një abstraksion mbi CMSIS, duke i paraqitur përdoruesit një ndërfaqe të përbashkët për të gjitha MK-të jo vetëm nga një prodhues, por në përgjithësi për të gjitha MK-të me bërthama e procesorit Cortex-Mxx.
Besohet se është më i përshtatshëm për fillestarët, sepse... ju lejon të mos mendoni se si funksionojnë pajisjet periferike, por cilësia e kodit, universaliteti i qasjes dhe kufizimi i ndërfaqeve vendosin kufizime të caktuara për zhvilluesin.
Gjithashtu, funksionaliteti i bibliotekës nuk ju lejon gjithmonë të zbatoni me saktësi konfigurimin e disa komponentëve si USART (porta serike universale sinkron-asinkrone) në kushte të caktuara. Në pjesën praktike do të përshkruaj edhe punën me këtë pjesë të bibliotekës.

Artikujt më të mirë mbi këtë temë