Fordítónak a GCC van kinevezve, ebből kell egy ARM-ra fordító verzió. Jó esetben ez kb. az összes ARM magot támogatni fogja, beleértve az M0-t is, ehhez csak egy nem túl régi verzió kell.
A keresési találatok nagy része ezt a launchpad-os linket adja, ezzel már anno találkoztam! Ez egy „előfordított” toolchain, használatra kész formában letölthető Windows® illetve Linux®™ verzióban is. A találatoktól függetlenül nem érdemes ezt használni, mivel – ahogy az oldalon is szerepel – valamikortól már ide nem kerül feltöltésre új verzió, azokat az ARM-tól lehet letölteni. A jelenlegi utolsó verzió a 2017. Q4, ez (gondolom) a 2017. 4. negyedévben elérhető változat. Annyit erről a toolchainról érdemes tudni, hogy ez a Cortex-M illetve Cortex-R magokat támogatja csak, ezek mind mikrovezérlő stílusú cuccok. A Cortex-A verzióra (alkalmazásprocesszorok) ezzel nem lehet forgatni, ahhoz (valószínűleg) „teljes” ARM-GCC kell. Nekem jelenleg pont a mikrovezérlős verzió kell, kipróbáltam, működik, de aztán végül mégse ez lesz a későbbiekben használva.
A fordító „nevezéktanáról” is érdemes pár szót ejteni, ez nagyjából valahogy így szokott kinézni:
architektúra-vendor-ostípus-abitípus-programnév
Ezek jelentése:
- architektúra – Milyen CPU architektúrához tartozik az eszköz? Esetünkben ez az
arm
lesz; - vendor – Ki a toolchain „szállítója”? Ez sokszor ki van hagyva, enélkül is elég hosszúak a nevek;
- ostípus – Milyen „cél” operációs rendszerre fordít a toolchain? Mivel jelenleg én írnám az OS-t, :) így ez nem meghatározható, most ez a
none
lesz; - abitípus – Milyen ABI-t használ a fordító? Az ABI az Application Binary Interface rövidítése, ez itt arról szól, hogy milyen adattípus hogyan van a memóriába eltárolva, függvényhívásnál milyen módon vannak a paraméterek átadva, stb. Beágyazott rendszerfejlesztéshez az
eabi
, az Embedded ABI használatos (?), szerintem itt a kompatibilitás annyira nem lényeges, (ritkán fordul az elő, hogy egy µC egy programját több, különböző fajta fordítóval fordítanák,) ez viszonylag szabadon változhat (de mást azeabi
-n kívül itt még nem láttam :) ); - programnév – a toolchain külön tooljainak a neve, itt például
gcc
;
A fenti „nevezéktan” alapján a GCC neve arm-none-eabi-gcc
lesz, az ARM-tól letölthető toolchain is ilyen neveket tartalmaz. A disztribúcióhoz tartozó csomagkezelővel is ilyet érdemes keresni, Fedora-n ez elérhető csomagból:
Nem mondom, nem lesz kevés... :) (Gyaníthatóan ez a teljes ARM palettára tud fordítani, nem csak a mikrovezérlő változatokra.) Viszont jó lenne nekem ez CentOS7 alá is, ott persze nincs belőle csomag. Akkor fordítsunk! :) Itt is „elővettem a szokásos trükköt”: Fedora alatt a forrás-RPM-et letöltve, majd azt kicsomagolva (rpm2cpio csomagnév | cpio -idmv
) meg is van a forrás .tar.gz2
tömörítéssel, az esetleges peccsek, és ami a fontos, maga a .spec
fájl, ez alapján generálódik a csomag. Ezt minimálisan módosítva készíthető belőle CentOS alá is .rpm
. Azért vannak érdekességek... :)
- Az
arm-none-eabi-binutils-cs
nagyjából simán generálható, appl-devel
illetvecloog
függőségek kiszedése után. (Csúnya hack; de lefordul nélkülük is, CentOS alatt nem találtam ilyen csomagot. A CLooG valami optimalizációs segítség lenne, hogy kell-e nekem, azt egyelőre nem tudom. :) ) - Az
arm-none-eabi-gcc-cs
(a-c++
csomag ugyanebből a forrásból készül) már egy kicsit viccesebb. A fordításhoz szükség lesz azarm-none-eabi-newlib
csomagra. Viszont ennek a csomagnak a fordításához meg szükség van azarm-none-eabi-gcc
-re, amit épp most akarnék fordítani. :) A klasszikus 22-es csapdája. A Fedora-s csomagot a készítő úgy oldotta meg, (illetve úgy tűnik, ez a „normális” metódus, ) hogy a.spec
fájlban van egybootstrap
változó. Ha ez 1, akkor azarm-none-eabi-newlib
függőség nélkül lefordul egy „egyszerűsített verzió”, ezt felrakva forgatható vele a-newlib
, majd az így létrejövő-newlib
csomagot föltéve, abootstrap
változót 0-ra visszaállítva már lefordul a „teljes”arm-none-eabi-gcc
. Ez az „teljes” változat nálam pörgött vagy 4 órát, mire elkészült... :) - Az
arm-none-eabi-newlib
fordítása az előző trükkel sima ügy; ez egy csak ARM-os kódokat tartalmazó csomag, a klasszikus „C-s” függvényeket ez valósítja meg. - Az
arm-none-eabi-gdb
a debugger, ezzel semmi bonyodalom nincs.
Tehát némi túrás, meg jó pár óra várakozás után van CentOS7-en is arm-none-eabi-gcc
! Szuper! És igen, továbbra is ilyen vontatottan halad a téma. Már készen van minden is, de kódot, azt még mindig nem írtam egy sort se... Most már éppen ideje lesz! Vagyis: ideje van! (Ami most következik, az erősen jegyzet magamnak; némi próbálkozással körítve!)
A leg-egyszerűbb-tesztkód-evör legyen a következő, main.c
:
int main (void) {
while (1);
}
Még egy nyamvadt #include <stdio.h>
sincs benne, egyelőre még az is luxus lenne! Talán nagyon magyarázni nem kell: egy sima végtelen-ciklus, ami nem csinál semmit. Ezt érdemes lenne lefordítani:
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c -o main.elf
A -nostartfiles
paraméterrel nem kerül bele a végeredménybe a C saját inicializálása, egyelőre nem is sikerülne neki berakni, erről később. A végeredmény a main.elf
fájl, ami köszönhető az -o main.elf
paraméternek, (enélkül egy a.out
nevű, szintén .elf
típusú fájl keletkezne,) de van egy kövér warning:
A figyelmeztetésben levő elérési út egy kicsit „furcsa”; de úgy tűnik, hogy a futtatott programhoz képest relatív útvonalon keresztül éri el a másik toolt, így nem szükségszerű, hogy a toolchain benne legyen az elérési útban. Ez később még jól jöhet, lehet ugyanolyan néven több verzió is telepítve, nem kell hogy összevesszenek. Maga a figyelmeztetés:
ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
Nincs definiálva a _start
cím, ezért az alapérték, 0x00008000 van használva. Ez nekünk nem jó, a µC programmemóriája nem itt kezdődik, nem ide kell pakolni a kódot. A fordítás az valahogy úgy megy, hogy a C-fordító lefordítja a kódo(ka)t, ami(k)ben a címek maximum részlegesen vannak feloldva. Ebből a linker csinálja meg a binárist, beállítva a tényleges, eddig még nem kiszámított címeket. (A fenti figyelmeztetés pontosan a linkertől jön.) Ahhoz, hogy a kívánt helyre kerüljön minden, a linkernek meg kell mondani, hogy mit hova pakoljon, erre szolgál a linker-script. Tehát kell egy ilyen is, ez is – egyelőre – a leg-egyszerűbb-linkerszkript-evör verzióban, NUC140.ld
:
ENTRY(main)
SECTIONS
{
.text : {
*(.vectors)
*(.text)
}
}
Fordítás újra:
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles -T NUC140.ld main.c -o main.elf
Itt a -T NUC140.ld
paraméter adja meg a linker-script nevét. A fordítás bármilyen üzenet nélkül megtörténik, előáll a main.elf
fájl, ~66900 BYTE méretben. Nem kicsi, de ennek a töredéke kell majd... No de mi is került bele? Erre is van kiváló parancs:
$ arm-none-eabi-objdump -d main.elf
Ez „diszasszemblálja” az imént elkészült main.elf
-et, a végeredmény valami ilyesmi:
main.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <main>:
0: b580 push {r7, lr}
2: af00 add r7, sp, #0
4: e7fe b.n 4 <main+0x4>
Ez egy függvény-előkészítés, illetve egy „magára ugró” ugró-utasítás, azaz egy végtelen-ciklus. Ez így még jó is! :) Az .elf
fájl még továbbra se programozható be a mikrovezérlőbe, abból a „bináris kódot” még ki kell másolni, de a fentiekben még van egy – nem éppen elhanyagolható – hiányosság. A Cortex-M0 CPU mag „úgy indul”, hogy a memória legelején, 0x00000000 címtől kezdődően kell neki egy „vektor-tábla”, aminek az első eleme a beállítandó SP (veremmutató), a második meg az indulási cím, jelenleg az lenne a main()
függvény kezdőcíme. Tehát minimum ez a kettő 32 bites érték szükséges a kód elejére, 0x00000000 címtől kezdődően. Ezt viszonylag egyszerű belerakni, a main.c
fájl végére a következő kerül:
int *myvectors[2] __attribute__ ((section(".vectors"))) = {
(unsigned int *) 0x20004000, // SP
(unsigned int *) main // Main
};
Az, hogy ez a két unsigned int
hova is kerüljön, azt az __attribute__ ((section(".vectors")))
paraméter mondja meg. A .vectors
az ismerős a fenti linker-script-ből, azzal „kezdődik” a kiosztás, nem véletlenül... Az így kiegészített main.c
fordítása utáni „diszasszemblálás” végeredménye:
main.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <myvectors>:
0: 00 40 00 20 09 00 00 00 .@. ....
00000008 <main>:
8: b580 push {r7, lr}
a: af00 add r7, sp, #0
c: e7fe b.n c <main+0x4>
Ez már kezd úgy kinézni, amit bele is lehetne tölteni a µC-be. A „mi Cortex-M0 magunk” Little-Endian, emiatt az alsó helyiértékkel kezdődnek a számok. (A „régebbi” ARM magok, mint amilyen az ARM7TDMI, Bi-Endian-ok, szoftverből átkapcsolhatók Little-Endian / Big-Endian módra. A Cortex-M0 is lehet mindkét fajta, de az, hogy milyen lesz, a csip implementálásakor dől el, szoftverből ezek nem állíthatóak.) Az első 4 BYTE 00 40 00 20
, ami a 0x20004000 memóriacímet jelöli, ez lesz a veremmutató, eddig sima ügy. A következő 4 BYTE 09 00 00 00
viszont érdekes: ez a 0x00000009 memóriacímet jelöli. A kód viszont 0x00000008-on kezdődik... A válasz (nagyjából) megtalálható a Cortex-M0 Generic User Guide-ban. A Cortex-M a thumb utasításkészletet támogatja csak, ezek 16 (vagy esetenként 32) bites op-kódokból állnak, amik mindig páros címen kezdődnek. A cím B0 bitje az EPSR „T” bitjébe másolódik be, amikor a (bármelyik) vektort beolvassa a CPU, ez pedig a „Thumb” állapotot jelöli. Mivel nincs „natív ARM” utasításkészlet támogatás, így ennek a bitnek mindig 1-nek kell lennie, különben egy Hard-Fault lesz a jutalom. Azaz a vektorokban itt a B0=1, tehát jó az úgy.
Az eddigi elkészült tesztkód már „futóképes” lenne, de – mivel az volt a cél – nem csinál semmit. Nálam a (bármilyen) µC-es Hello World példaprogram az szokott lenni, hogy beállítok kimenetre egy portbitet, meg bemenetre egy másikat. Majd a bemenet állapotától függően 1-be illetve 0-ba rakom a kimenetet. A kimeneti portlábra jön egy LED, a bemenetre meg egy kapcsoló, ha a tesztkód fut, akkor a kapcsoló állapotától függ, hogy a LED világít-e vagy sem. (A „többség” általában LED-et villogtat, de ahhoz várakozni kell, ahhoz meg illik tudni a CPU sebességét, szóval szerintem bonyolultabb mint az én verzióm.) Ehhez már minimális regiszterismeret kell, de erre jó az adatlap. A gyártók ezeket a konfigurációs regisztereket – természetesen (?) – elnevezik, sőt, szoktak adni C-s header
fájlokat, amik tartalmazzák ezeknek a definícióit. Itt is van ilyen, tele is van struktúradefinícióval, de azt most későbbre halasztom, egyelőre maradjunk az #include
-nélküliségnél... :) A tesztkód most valami ilyesmi lesz, újabb main.c
:
#define GPIOA_PMD ((volatile unsigned int *) 0x50004000)
#define GPIOA_DOUT ((volatile unsigned int *) 0x50004008)
#define GPIOA_PIN ((volatile unsigned int *) 0x50004010)
int main (void) {
*GPIOA_PMD = 0x00000001; // PORTA B15..1 Input, B0 Output
while (1) {
if ((*GPIOA_PIN & 0x00000002) != 0) {
*GPIOA_DOUT &= 0xfffffffe;
} else {
*GPIOA_DOUT |= 0x00000001;
}
}
}
int *myvectors[2] __attribute__ ((section(".vectors"))) = {
(unsigned int *) 0x20004000, // SP
(unsigned int *) main // Main
};
Szerencsére a GPIO felélesztése nem egy komoly feladat, ehhez még nem kell órajeleket kapcsolgatni. A tokban 16 bites „szélességű” GPIO blokkok vannak, de a regisztereik 32 bitesek. A példakód három regisztert használ, a PMD a Port Mode, ezzel lehet az adatirányt beállítani. Minden portbithez két konfigurációs bit tartozik, ezek kombinációi:
%00
– Az adott vonal bemenet%01
– Az adott vonal sima push-pull kimenet%10
– Az adott vonal Open-collector-os kimenet%11
– Az adott vonal „Quasi-Bidirectional” vonal lesz
Ezen utolsó üzemmód egy olyan kimenet, aminél ha alacsonyra kapcsolják a vezérlő bitet, akkor alacsonyat hajt a vonalra; ez eddig egy open-collector-os viselkedés. Viszont ha magasra kapcsolják a vonalat, akkor két órajelciklusig „alacsony impedanciával” magasat hajt a láb, majd utána kikapcsolódik ez a hajtás, a vonalon a magas szintet egy felhúzó ellenállás biztosítja a továbbiakban. Ilyenkor a külső eszköz lehúzhatja nyugodtan a vonalat alacsonyra, az ekkor simán bemenetként viselkedik. Tehát ez úgy kétirányú üzemmód, hogy közben nem kell az adatirányt kapcsolgatni.
A csip fejlesztőinek itt azért beszólnék egy kicsit: a lábakon van felhúzó ellenállás, de közvetlenül nem kapcsolható, a „Quasi-Bidirectional” módban aktív csak, ha simán bemenetként használnám, akkor lehet „kerülgetni”. Ráadásul az összes port RESET esetén ebben az üzemmódban indul el, amit azért nem érzek túl szerencsésnek...
A másik két regiszter már egyértelmű, a PIN a Port in, ezen lehet a lábak állapotát visszaolvasni, a DOUT pedig a Data out, ezen keresztül lehet a kimeneteket írni. A fenti tesztkód beállítja a PORTA 0. bitjét kimenetre, ez fogja hajtani a LED-et, az 1-est (a többi vonallal együtt) meg bemenetre kapcsolja. A végtelen-ciklus a PORTA 1. bitjének az állapotától függően törli vagy beállítja a 0. bitet, ez lesz most a tesztkód. A fenti linker-script
-tel lefordítva, majd „diszasszemblálva”:
00000000 <myvectors>:
0: 00 40 00 20 09 00 00 00 .@. ....
00000008 <main>:
8: b580 push {r7, lr}
a: af00 add r7, sp, #0
c: 4b0a ldr r3, [pc, #40] ; (38 <main+0x30>)
e: 220d movs r2, #13
10: 601a str r2, [r3, #0]
12: 4b0a ldr r3, [pc, #40] ; (3c <main+0x34>)
14: 681b ldr r3, [r3, #0]
16: 2202 movs r2, #2
18: 4013 ands r3, r2
1a: d006 beq.n 2a <main+0x22>
1c: 4b08 ldr r3, [pc, #32] ; (40 <main+0x38>)
1e: 681a ldr r2, [r3, #0]
20: 4b07 ldr r3, [pc, #28] ; (40 <main+0x38>)
22: 2101 movs r1, #1
24: 438a bics r2, r1
26: 601a str r2, [r3, #0]
28: e7f3 b.n 12 <main+0xa>
2a: 4b05 ldr r3, [pc, #20] ; (40 <main+0x38>)
2c: 681a ldr r2, [r3, #0]
2e: 4b04 ldr r3, [pc, #16] ; (40 <main+0x38>)
30: 2101 movs r1, #1
32: 430a orrs r2, r1
34: 601a str r2, [r3, #0]
36: e7ec b.n 12 <main+0xa>
38: 50004000 .word 0x50004000
3c: 50004010 .word 0x50004010
40: 50004008 .word 0x50004008
Az látszik, hogy nem éppen optimális a kód (semmiféle optimalizáció nincs bekapcsolva egyelőre), de ez akár már működhet is! Jöjjön a bináris előállítása:
$ arm-none-eabi-objcopy -O binary main.elf main.bin
A parancs kimásolja a bináris tartalmat egy külön fájlba (main.bin
), ami – meglepetés! – 68 BYTE hosszú lesz. Ez azért már baráti, így, ahogy van, be is lehet sütni. Ehhez a múltkor tárgyalt OpenOCD kell, a hozzá tartozó openocd.cfg
fájl valahogy így néz ki:
source [find interface/ftdi/luminary-icdi.cfg]
transport select swd
set CHIPNAME NUC140VE3AN
source [find target/numicro.cfg]
adapter_khz 500
init
reset run
halt
targets
numicro chip_erase
flash write_image erase main.bin 0x0
reset run
shutdown
A végeredmény:
Ez – látszólag – tökéletes! :) Ehhez már csak egy apróság kell: egy LED meg némi ellenállás a megfelelő pontokra csatlakoztatva:
A LED anódja egy ellenálláson keresztül a tápra van kötve, a katód meg a PORTA0 vonalra. A kapcsolót most egy jumper helyettesíti, a két érintkező közül az egyik a GND-re kötött, a másik a PORTA1 vonalra jön, de ezt egy másik ellenállás felhúzza a tápra. (Ha a jumper fel van rakva, az alacsonyra kapcsolja a PORTA1 vonalat, egyébként meg az ellenállás magasra húzza.) A tesztkódnak annyit kellene csinálni, hogy ha a PORTA1 vonal magas (nincs jumper), akkor a PORTA0 alacsony (LED bekapcsolva). Fordított esetben minden fordul. És... Csinálja is! Működik a mini Hello World! :) De ha már van valami, ami csinál is valamit, esetleg lehetne egy kicsit debuggolni, még ha itt konkrétan az is történik, aminek kell. Ehhez szükség lesz egy másik OpenOCD konfigurációs fájlra, legyen mondjuk debug.cnf
:
source [find interface/ftdi/luminary-icdi.cfg]
transport select swd
set CHIPNAME NUC140VE3AN
source [find target/numicro.cfg]
adapter_khz 500
init
reset run
halt
Ez egy openocd -f debug.cnf
paranccsal indítható, szépen le is áll tőle a target:
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000014 msp: 0x20003ff8
A debughoz a GDB, a GNU Project Debugger kell, annak is az ARM-ra fordított verziója. (Ebből készült csomag még az elején, ez is a toolchain része.)
$ arm-none-eabi-gdb
Az arm-none-eabi-gdb
parancsot elindítva a GDB parancsértelmezője kerül elő, ebbe lehet manuálisan a parancsokat beleirkálni, klasszikus MONITOR fíling. :) A GDB „csak” az UI-ért felel, a debug funkciókat a GDB-Server valósítja meg. Ebben az esetben a GDB-Server az OpenOCD maga, ehhez kapcsolódik a GDB a
(gdb) target remote localhost:3333
parancs segítségével. Mivel az OOCD konfigurációs fájlban már meg lett állítva a target, ezért a tesztkód LED-kapcsolgatása se megy jelenleg. (Nyilván...) Egy sima
(gdb) continue
paranccsal tovább lehet indítani, egy Ctrl+C billentyűkombináció viszont leállítja, mint egy rendes programot... :) A regiszterek aktuális állapotát az
(gdb) info registers
paranccsal lehet lekérdezni, de van egy jó nagy rakás parancs, amivel lehet bűvészkedni. Ez valószínűleg megérne egy komplett blogbejegyzést... Amennyiben a GDB úgy van indítva, hogy
$ arm-none-eabi-gdb -tui main.elf
akkor a debugger a címekhez ismerni fogja a szimbólumokat, jóformán a C-s forráskódot lehet figyelni közvetlenül. A -tui
paraméterrel egy „több-ablakos” karakteres felület lesz a jutalom, ami alapból nem túl információ-gazdag. (Valamerre láttam ehhez egy konfigurációs fájlt, amivel egy kicsit fel lehet turbózni a látottakat, majd ez is megér egy vizsgálatot.) A nexti
vagy röviden ni
paranccsal lehet utasításokat végrehajtani egyesével, betöltött .elf
mellett kiírja, hogy éppen melyik utasítás hajtódik végre, de ez így tényleg nagyon „nyögvenyelős”. Lehet, hogy érdemes lesz ehhez valami GUI-s frontendet keríteni. (Nem, az Eclipse-t nem akarom! :) ) Kilépni a quit
paranccsal lehet egy megerősítés után, az OpenOCD-t meg egy Ctrl+C megállítja. (Viszont ha haltolva volt a µC, akkor az úgy is marad!)
De vissza a „kódolásra”. A fenti példában a CPU vektorai a C-s forrásba vannak beillesztve, de meg lehet ezt csinálni úgy is, hogy egy saját, assembly startup kód illeszti be ezt a kód elejére. Ennek jelentősége talán csak akkor van, ha az ember inkább asm-ben kódolna, a C-t meg mondjuk meghagyja az inicializációs feladatokra. A C-s példakód egy asm rutin-indítást csinál, ez se sokkal bonyolultabb, mint a leg-egyszerűbb-tesztkód-evör, main.c
:
void AsmFunction(void);
int main (void) {
AsmFunction();
}
Ehhez tartozik a „startup”, amiben ott vannak a vektorok a hívott függvénnyel, de ez is minimalista verzió, ez se csinál semmit, startup.s
:
.section .vectors
Vectors:
.word 0x20004000
.word main
.section .text
.global AsmFunction
AsmFunction:
nop
nop
nop
b AsmFunction
.end
A linker-script marad az eddigi. A fordítás:
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles -T NUC140.ld main.c startup.s -o main.elf
A végeredmény:
main.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <Vectors>:
0: 20004000 .word 0x20004000
4: 00000009 .word 0x00000009
00000008 <main>:
8: b580 push {r7, lr}
a: af00 add r7, sp, #0
c: f000 f804 bl 18 <AsmFunction>
10: 2300 movs r3, #0
12: 0018 movs r0, r3
14: 46bd mov sp, r7
16: bd80 pop {r7, pc}
00000018 <AsmFunction>:
18: 46c0 nop ; (mov r8, r8)
1a: 46c0 nop ; (mov r8, r8)
1c: 46c0 nop ; (mov r8, r8)
1e: e7fb b.n 18 <AsmFunction>
Ez a tesztkód ismételten nem érdemli meg, hogy letöltsem a µC-be, de úgy néz ki, ahogyan elvárható, a main()
függvény meghívja az asm rutint, ami jelenleg nem is tér vissza. (Ha meg mégis visszatérne, a main()
is befejeződik, amiből jó eséllyel egy fagyás lesz. :) )
Egy dologgal kapcsolatban még van mit megvizsgálni, erről egyelőre nem találtam információt. A Cortex-M0 magnál nem konfigurálható a vektortábla kezdőcíme, az mindig 0x00000000. Viszont a NuMicro-sorozat tartalmaz bootloader-képességet, amihez van egy külön 4 KBYTE-os FLASH terület LDROM néven, 0x00100000..0x00100FFF tartományban. Ahhoz, hogy innen induljon RESET esetén a végrehajtás, a Config0 regiszterben (ez szintén EEPROM, programozásnál beállítandó) be kell programozni a CBS
(Chip Boot Selection) bitet. De ebben az esetben vajon mi történik? Ilyenkor szimplán elindul a végrehajtás 0x00100000-tól? Vagy átkerül a vektortábla kezdőcíme ide, tehát ugyanúgy kellenek a vektorok az elejére? Én az utóbbira tippelek, de egy próbát mindenképpen megér. Így lesz LDROM FLASH, illetve konfigurációs terület programozási próba is, amik eddig elmaradtak... A sima LED-kapcsolgatós teszt tökéletes ide is, a linker-script-et kell csak módosítani a megfelelő eredmény érdekében. Ha nincs külön megadva, alapesetben 0x00000000 címen kezdődik minden, de itt most más kell, NUC140.ld
az LDROM-hoz:
ENTRY(main)
SECTIONS
{
. = 0x00100000;
.text : {
*(.vectors)
*(.text)
}
}
Ezzel lefordítva a LED-kapcsolgatós tesztet, a végeredmény az LDROM címére kerül:
main.elf: file format elf32-littlearm
Disassembly of section .text:
00100000 <myvectors>:
100000: 00 40 00 20 09 00 10 00 .@. ....
00100008 <main>:
100008: b580 push {r7, lr}
10000a: af00 add r7, sp, #0
10000c: 4b0a ldr r3, [pc, #40] ; (100038 <main+0x30>)
10000e: 220d movs r2, #13
...és a többi. A cím az jó, be kell sütni a FLASH LDROM területére, illetve a megfelelő Config0
bitet is be kell pirítani, ehhez szintén az OOCD segítsége kell:
source [find interface/ftdi/luminary-icdi.cfg]
transport select swd
set CHIPNAME NUC140VE3AN
source [find target/numicro.cfg]
adapter_khz 500
init
reset run
halt
targets
numicro chip_erase
flash write_image erase main.bin 0x100000
numicro write_isp 0x300000 0xffffff7f
reset run
shutdown
Az LDROM kezdőcíme 0x00100000, oda égeti be az elkészült kódot. A numicro write_isp 0x300000 0xffffff7f
sor a Config0
regisztert programozza be, ennek a 7-es bitje ha 0, akkor bútol az LDROM-ból a µC. A végeredmény:
A program természetesen nem fut. A fenti képről az derül ki, hogy ha a CBS
bit be van programozva, akkor az LDROM tartalma 0x00000000 címre lapozódik, nem mennek sehova a vektorok! :) A numicro read_isp
paranccsal ebben az esetben is „minden a helyéről olvasható”, de a sima memóriaolvasás az LDROM-ot a memória elején látja, a saját címéről nem is hajlandó olvasni. Ha a fenti OOCD-s konfigurációval az eredeti, 0x00000000 címre fordított kód van beprogramozva, akkor – természetesen – minden szépen működik. A kezdőcím eltolásos próbának annyi értelme azért volt, hogy ennek a működése is kiderült, máshova még jó lesz ez!
A numicro chip_erase
parancs törli az egész FLASH tartalmat, beleértve a konfigurációs biteket is! Jó esetben ezzel meg lehet szabadulni egy beprogramozott Lock-bittől is, de ezt még nem mertem kipróbálni. :-D Az OpenOCD-ben közben találtam egy furcsaságot, amit egyelőre nem is sikerült megoldani. A fenti példákban a
reset run
halt
sorok állítják le a mikrovezérlőt, ezután működik a többi parancs. Viszont ez csak akkor jó, ha éppen van (és fut is) valami a FLASH-ben. Ha üres a tok, akkor folyamatosan valami hibára fut, ekkor ez a leállítás hatástalan. Ilyenkor a
reset halt
sor lesz megfelelő a fenti két sor helyett, de ezt vagy érdemes lenne „rendbe szedni”, vagy valahogy automatizálni kellene. Az utóbbi feladat valószínűleg nem lehetetlen, mivel az OpenOCD konfigurációs fájljai Tcl szkriptek, de a „rendbe szedés” inkább célravezetőbb. (Vagy lesz rá valami workaround; külön erase
, utána meg mindig üres tokkal indul a programozás?) A csip RESET-elése még tesztelendő, mert most nem tudom, hogyan megy. Van ugye az nSRST vonal, de az SWD DAP-on keresztül is újraindítható a tok, hogy jelenleg melyik aktív, az OOCD konfiguráció függő.
Kezd ez a rész is elnyúlni, pedig rengeteg dolog van még vissza. Azok majd a következő rész(ek)re marad(nak). De! A jelenlegi kísérletezések végére egy erős figyelmeztetés ide kívánkozik... Az eddigi tesztkódok nem alkalmasak arra, hogy egy C-s projekt alapjai legyenek! Nagyon sok minden hiányzik, leginkább a linker-script oldalán, ugyanis nincsenek definiálva memória-részek. A veremmutató be van állítva, mivel a CPU RESET esetén felszedi a megfelelő helyről. Emiatt a függvényhívások működnek, illetve a függvényeken belüli (lokális) változók is használhatók, mert azokat a (beállított) verembe pakolja a fordított kód. Viszont a globális változóknak nincs memória kijelölve! A fordító – ennek hiányában – (gondolom) a programkód utáni részre helyezné el őket, de az ROM, nem írható. Aztán a C-s „startup” is hiányzik, emiatt nem történik meg a memóriatörlés sem, illetve az előre beállított változók rendberakása is hiányzik. Tehát a legjobb esetben is csak az asm példa használható, az eddigiek csak a toolchain próbálgatásai!
Link most nem lesz túl sok, az majd a következő részben. Ami hátra van: végleges(ebb) linker-script, amivel használható az eredeti startup-kód is, illetve legyen normális C-s használhatóság. Aztán az eredeti header
(ek) használata a regiszterdefiníciókkal, sokat egyszerűsíti a perifériák kezelését. Meg a gyári programozási példák is jók lennének, legalább mintának. Esetleg CMSIS? (Bár erről az „erőforrások elpocsékolása” előbb jut az eszembe, mint az hogy „újrafelhasználható kód”, de mindegy.) Plusz egy komolyabb demó-projekt, amiben lesz legalább egy soros-port kezelés órajelállításokkal, engedélyezésekkel, miegyébbel. Izgalmas lesz, pláne, hogy csak fejben van meg. :)
A jelenlegi tesztekből azért csináltam egy csomagot, de a fenti figyelmeztetés értelmében csak saját felelősségre! A „gyökér”-ben van egy tchn.sh
nevű fájl, ebben vannak megadva a használt toolchain-programok nevei. Ezekből egyszerű bármilyen verziót használni, belenézve a fájlba egyből látszik, hogy mit kell átírni benne ehhez. A tesztek könyvtárában van néhány „számnevű” szkript. Ebből az 1
egy tisztítást csinál, a 2
végzi a fordítást (használva az előbb említett tchn.sh
-t), a 3
„diszasszemblál” illetve elkészíti a beégetni valót. Az esetleges 4
az OpenOCD-t indítja a tok programozásával, az 5
meg ugyanezt teszi a debughoz szükséges módon. A többi meg értelemszerű. Jelenleg három teszt van, a testcode1
az első, LED-kapcsolgatós példaprogram, a testcode2
az asm próba, a testcode3
pedig szintén a LED-kapcsolgatás, de ez a boot-ROM-os (LDROM) verzió.
Folyt.köv.
Linkek:
- Első rész, leginkább hardver...
- Következő rész, linker
- A rengeteget emlegetett OpenOCD oldala, most éppen működik, de pár napja nem ment / nagyon lassú volt... :\ (A keresős linkek zöme ide mutat.) Cserébe találtam egy ilyet, itt a linkek egy része nem működik... Valami csere készül?
- OOCD, GDB „minta”
- Saját tesztkód-minták, saját felelősségre!
balagesz
---
2018.02.20.
2018.09.23. Elírás jav.
2019.02.17. Köv. rész link
2019.12.21. D8.
2024.08.25. Kép.jav. + elírások
- balagesz blogja
- A hozzászóláshoz be kell jelentkezni
Hozzászólások
Komoly írás, még nem tudtam végigolvasni. De megerősít abban, hogy csak akkor mozdulok el majd a 8 bites AVR-ekről, ha muszáj lesz...
"A csip fejlesztőinek itt azért beszólnék egy kicsit: a lábakon van felhúzó ellenállás, de közvetlenül nem kapcsolható, a „Quasi-Bidirectional” módban aktív csak, ha simán bemenetként használnám, akkor lehet „kerülgetni”. Ráadásul az összes port RESET esetén ebben az üzemmódban indul el, amit azért nem érzek túl szerencsésnek..."
Nem egyértelmű számomra - és nem néztem utána az adatlapon :-) - hogy melyik a default üzemmód? És miért szólsz be a fejlesztőknek?
- A hozzászóláshoz be kell jelentkezni
A default üzemmód a Quasi-Bidirectional, RESET-kor abban indul a tok. Az adatregiszter persze 0xFFFF, tehát minden "kvázi-kimenet" magas, vagyis a belső felhúzó ellenállások miatt magas, ha az tud lenni. A szemöldökráncolás emiatt van; minden normális µC esetén "lebeg" az összes I/O láb RESET-kor, itt meg felhúzza őket egy ellenállás magasra. Ha kimenetnek használsz egy lábat, erre oda kell figyelni! Az azért a "mentségükre" szól, hogy ez a felhúzó ellenállás 100K nagyságrendű, de ezt jóval elegánsabban is meg lehetett volna oldani, persze IMHO.
(És igen, a 8 bites AVR nekem is az egyik kedvenc. :) )
- A hozzászóláshoz be kell jelentkezni
Off: script logfile /bin/sh
, majd a logfile tartalmát code-tagbe olvasztod, és így nem kell képekkel vacakolnod :)
- A hozzászóláshoz be kell jelentkezni
:) A képekkel együtt is elég "nyers" a téma, de a lényeg
code-tag
-elve van, kereshető normálisan. Viszont köszi az ötletet, ezt a ficsőrt se ismertem eddig. :-D
- A hozzászóláshoz be kell jelentkezni
Viszont a képek eltűnhetnek 1-2 év múlva :)
Szívesen, örülök, hogy tudtam újat és hasznosat mondani :)
- A hozzászóláshoz be kell jelentkezni
bookmark szintén
- A hozzászóláshoz be kell jelentkezni