南昌網(wǎng)站建設(shè)制作網(wǎng)絡(luò)推廣接單平臺
目錄
讓我們從環(huán)境配置開始
目標(biāo)平臺
從Ubuntu開始
從交叉編譯器繼續(xù)
arm-linux-gnueabihf-gcc
vscode
沒學(xué)過ARM匯編
正文開始——速度體驗一把
寫一個鏈接腳本
寫一個簡單的Makefile腳本
使用正點原子的imxdownload下載到自己的SD卡上
更進(jìn)一步的筆記和說明
從IMX6ULL啟動方式說起
IMX6ULL的啟動方式
定制合法的可燒寫識別的合法執(zhí)行文件
關(guān)于點燈本身
讓我們從環(huán)境配置開始
當(dāng)你看到這篇文章的時候,說明你已經(jīng)準(zhǔn)備好開始嵌入式Linux開發(fā)之旅了。這篇博客也是筆者自己學(xué)習(xí)嵌入式Linux開發(fā)的日記。希望自己可以堅持到寫完這個博客筆記,通關(guān)一次簡單的嵌入式Linux開發(fā)。
目標(biāo)平臺
筆者使用的是正點原子Alpha開發(fā)板,上面是Cortex-A7的IMX6ULL的芯片,主頻啥的談不上高,但是學(xué)習(xí)足夠了
筆者使用的是Ubuntu24.04(其實別的也行)
從Ubuntu開始
起始從哪里開始都行,筆者這里為了少折騰,選擇從Ubuntu開始。筆者安裝的在了Arch主機(jī)上的雙系統(tǒng)上,具體如何操作這里不是重點。你可以跟正點原子一樣歡樂虛擬機(jī),但是我尋思著太麻煩了。
把Ubuntu配成自己喜歡的樣子(包括但不限于輸入法,基本的vscode,shell自定義,桌面自定義,代理等),基本上就可以歡歡樂樂的開始嵌入式Linux開發(fā)了。
關(guān)于如何配置Ubuntu,可以自己上網(wǎng)找博客看,在這里的一般都不會對后面的開發(fā)造成實質(zhì)的影響。
從交叉編譯器繼續(xù)
實際上筆者之前玩過交叉編譯器,簡單的講講啥是交叉編譯器,這個東西對于我們嵌入式開發(fā)者而言會天天見到。筆者建議好好熟悉了解相關(guān)的概念。
首先,你現(xiàn)在電腦上(哦!也有可能是虛擬機(jī)!)默認(rèn)的自帶的gcc是在x64上跑的,編寫的程序是給x64架構(gòu)的程序使用的編譯器。誠然,我們開發(fā)肯定還是在x64架構(gòu)的機(jī)器上搞開發(fā)。但是我們現(xiàn)在要面向的平臺毫無疑問的是ARM架構(gòu)的機(jī)器。那怎么辦呢?
這個時候就需要請出我們的交叉編譯器了。
arm-linux-gnueabihf-gcc
是的!這是我們要使用的gcc編譯器。它隸屬于arm-linux交叉編譯器的大家族
首先,交叉編譯器的命名規(guī)則是arch [-vendor] [-os] [-(gnu)eabi] [-language]
-
arch - 體系架構(gòu), 如arm(ARM-32bit)、aarch64(ARM-64bit)、x86等;
-
vendor -工具鏈提供商,經(jīng)常省略,或用 none 替代;
-
os - 目標(biāo)操作系統(tǒng), 如linux,沒針對具體 os 則 用 none 替代。同時沒有 vendor 和os 使用一個 none 替代。
-
eabi - 嵌入式應(yīng)用二進(jìn)制接口(Embedded Application binary Interface)
-
language - 編譯語言,如gcc,g++
那事情就很簡單的了:這個是面向arm架構(gòu)的,給嵌入式二進(jìn)制armhf接口生成跨平臺程序的gcc(當(dāng)然,還有其他的比如說arm-none-eabi-gcc,gcc-arm-linux-gnueabi等等,他們的區(qū)別不算大,但是值得你去根據(jù)你使用的機(jī)器查到底用哪個版本的gcc),如果你跟我一樣,使用的是面向CortexA7的IMX6ULL進(jìn)行開發(fā),你所干的事情很簡單。
sudo apt install gcc-arm-linux-gnueabihf
啥?你說正點原子告訴你Linaro那里去下?拜托,早就不維護(hù)了那個地址。當(dāng)然如果你擔(dān)心你沒法排查問題,用老壁燈gcc4沒問題,但是你確定你這是在學(xué)習(xí)嗎?之后的開發(fā)出問題了你又該咋辦?
現(xiàn)在,你啥也不用做,直接
arm-linux-gnueabihf-gcc -v
在筆者這個時間,你會高興的得到13.2的超級高版本的gcc!現(xiàn)在開始你也是現(xiàn)代的1嵌入式Linux開發(fā)人員了!
vscode
嗯,直接安裝vscode就行了,啥?你說你跟著的是Ubuntu16.04?非常壞老鐵,這年頭沒幾個人用咯,就跟著正點原子走吧。在24.04,安裝vscode就是sudo apt install code一句話搞定的事情。
沒學(xué)過ARM匯編
正文開始——速度體驗一把
速速開始正文。
我們將會把代碼燒到SD卡上,然后操控板子使用SD卡啟動啟動到內(nèi)存里去。裸機(jī)程序,沒有操作系統(tǒng),你可能就會想起STM32的開發(fā)了。是的,我們需要起手寫startup,放在DCD初始化結(jié)束后的位置上,IMX6ULL芯片執(zhí)行結(jié)束后會跳轉(zhuǎn)到地址0x87800000的地方,所以,我們需要在那個地方安排上我們的啟動程序。
一個startup其實可能比你想象的要簡單。
// start up files for led ? .global _start _start: /* GCC always find symbols in _start for the entry */mrs r0, cpsr/*該操作清除了 r0 中最低的 5 位,即將處理器模式和相關(guān)標(biāo)志清除*/bic r0, r0, #0x1f/*下面這條指令執(zhí)行按位或操作。它將 r0 中的內(nèi)容與 0x13(即二進(jìn)制的 10011)進(jìn)行按位或操作,并將結(jié)果存回 r0。0x13 代表了設(shè)置 CPSR 中的模式位為 0x13(對應(yīng)用戶模式)。*/orr r0, r0, #0x13msr cpsr, r0// load to the main and init the sp // in this way :)ldr sp, =0x80200000b main
啊哈!你說奇怪,系統(tǒng)時鐘,中斷向量表嘞?放心,早初始化完了,你需要做的事情很簡單。就是:
-
修改我們的處理器SVC模式,辦法是取出CPSR寄存器的值,對之進(jìn)行修改,然后放回去!簡單吧!
-
然后設(shè)置我們的棧指針指向更高的2MB地址出(說明我們給程序2MB的地址空間大小,足夠!)
-
跳轉(zhuǎn)道我們后面寫的main函數(shù)里去。
后面我們的main函數(shù)也不難:
#ifndef __MAIN_H #define __MAIN_H ? /* * CCM相關(guān)寄存器地址 */ #define CCM_CCGR0 *((volatile unsigned int *)0X020C4068) #define CCM_CCGR1 *((volatile unsigned int *)0X020C406C) ? #define CCM_CCGR2 *((volatile unsigned int *)0X020C4070) #define CCM_CCGR3 *((volatile unsigned int *)0X020C4074) #define CCM_CCGR4 *((volatile unsigned int *)0X020C4078) #define CCM_CCGR5 *((volatile unsigned int *)0X020C407C) #define CCM_CCGR6 *((volatile unsigned int *)0X020C4080) ? /* * IOMUX相關(guān)寄存器地址 */ #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4) ? /* * GPIO1相關(guān)寄存器地址 */ #define GPIO1_DR *((volatile unsigned int *)0X0209C000) #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004) #define GPIO1_PSR *((volatile unsigned int *)0X0209C008) #define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C) #define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010) #define GPIO1_IMR *((volatile unsigned int *)0X0209C014) #define GPIO1_ISR *((volatile unsigned int *)0X0209C018) #define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C) ? #endif
這里是函數(shù)的主體,如果您想嘗試,直接復(fù)制就好。
#include "main.h" ? void enable_clk() {CCM_CCGR0 = 0xffffffff;CCM_CCGR1 = 0xffffffff;CCM_CCGR2 = 0xffffffff;CCM_CCGR3 = 0xffffffff;CCM_CCGR4 = 0xffffffff;CCM_CCGR5 = 0xffffffff;CCM_CCGR6 = 0xffffffff; ? } ? void led_init() {SW_MUX_GPIO1_IO03 = 0x5;SW_PAD_GPIO1_IO03 = 0x10B0;GPIO1_GDIR = 0x00000008;GPIO1_DR = 0x0; } ? void led_on() {// set the forth bit as 0GPIO1_DR &= ~(1 << 3); } ? ? void led_off() {GPIO1_DR |= (1 << 3); } ? void delay_in_n_nops(volatile unsigned int n){while(n--){} } ? void delay_ms(volatile unsigned int n){while(n--){delay_in_n_nops(0x7ff);} } ? int main() {enable_clk();led_init();while(1){led_on();delay_ms(500); ?led_off();delay_ms(500);} ?return 0; // cheats anyway :) }
后面我會一個個結(jié)束,到這里就OK。
寫一個鏈接腳本
鏈接腳本是啥,也先不著急:
SECTIONS{. = 0x87800000;.text : {start.oled.o*(.text)}.rodata ALIGN(4) : {*(.rodata*)}.data ALIGN(4) : {*(.data*)}__bss_start = . ;.bss ALIGN(4) : {*(.bss) *(COMMON)}__bss_end = . ; }
寫一個簡單的Makefile腳本
objs := start.o led.o ARCH_PREFIX := arm-linux-gnueabihf CC := gcc LD := ld OBJDUMP := objdump OBJCOPY := objcopy ? RESULT_NAME := led ? ENTRY_SCRIPT := link.lds ? ${RESULT_NAME}.bin:$(objs)${ARCH_PREFIX}-${LD} -T$(ENTRY_SCRIPT) -o ${RESULT_NAME}.elf $^${ARCH_PREFIX}-${OBJCOPY} -O binary -S ${RESULT_NAME}.elf $@${ARCH_PREFIX}-${OBJDUMP} -D -m arm ${RESULT_NAME}.elf > ${RESULT_NAME}.dis ? %.o: %.s ${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< ? %.o: %.S${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< ? %.o: %.c${ARCH_PREFIX}-${CC} -Wall -nostdlib -c -o $@ $< ? clean:rm -rf *.o ${RESULT_NAME}.bin ${RESULT_NAME}.elf ${RESULT_NAME}.dis
這是一種Makefile腳本的寫法,當(dāng)然不止這一種!你可以按照這個層級把文件排放:
現(xiàn)在直接make得到我們的bin文件。
使用正點原子的imxdownload下載到自己的SD卡上
下一步是使用imxdownload將我們的bin文件下載到我們的SD卡上,這個imxdownload文件是正點原子提供的,實際上就是組合我們的頭部信息進(jìn)一步生成可以被板子執(zhí)行的文件。
現(xiàn)在我們需要做的就是插上SD卡。查看自己的SD卡被分配的分區(qū)。一般的/dev/sda是自己的電腦的磁盤,嗯,一種最好的方式是查看自己的dmesg輸出信息。
sudo dmesg | tail -n 20這個指令將會打印出內(nèi)核日志文件的倒數(shù)二十行它一般會記載著我們新USB設(shè)備掛載的信息
[ 3843.237182] usb 3-1: New USB device strings: Mfr=1, Product=3, SerialNumber=2 [ 3843.237188] usb 3-1: Product: Mass Storage Device [ 3843.237192] usb 3-1: Manufacturer: Generic [ 3843.237195] usb 3-1: SerialNumber: 121220160204 [ 3843.241247] usb-storage 3-1:1.0: USB Mass Storage device detected [ 3843.241710] scsi host6: usb-storage 3-1:1.0 [ 3844.243574] scsi 6:0:0:0: Direct-Access ? ? Mass ? ? Storage Device ? 1.00 PQ: 0 ANSI: 0 CCS [ 3844.244224] sd 6:0:0:0: Attached scsi generic sg2 type 0 [ 3844.477530] sd 6:0:0:0: [sdb] 122138624 512-byte logical blocks: (62.5 GB/58.2 GiB) [ 3844.477668] sd 6:0:0:0: [sdb] Write Protect is off [ 3844.477672] sd 6:0:0:0: [sdb] Mode Sense: 03 00 00 00 [ 3844.477826] sd 6:0:0:0: [sdb] No Caching mode page found [ 3844.477829] sd 6:0:0:0: [sdb] Assuming drive cache: write through [ 3844.479862] sdb: sdb1 [ 3844.480015] sd 6:0:0:0: [sdb] Attached SCSI removable disk
所以,筆者得到的目標(biāo)文件是向/dev/sdb寫東西!
注意,使用imxdownload將會完全覆蓋里面原本的東西,請做好備份再寫!
請注意!確保自己的SD卡掛載的位置!不要寫錯地方了,操作時不可逆的。正點原子之前提供的辦法是插拔反復(fù)對比,這個辦法不好但是總歸管用。
發(fā)現(xiàn)你的終端不自動補(bǔ)全imxdownload,請修改文件權(quán)限為744,即對自己完全可用,其他人只有讀權(quán)限的文件!
./imxdownload led.bin /dev/sdb
輸入密碼后,觀察寫入速率,一般而言是一百多到幾百KB每秒,大于這個速度上MB的一概認(rèn)為是讀寫失敗,重新拔下來看看自己的sd卡有沒有接好。
現(xiàn)在調(diào)整板子的啟動模式是SD卡啟動,如下所示:
現(xiàn)在,你的板子上電,應(yīng)該可以看到現(xiàn)象是:
是的,DS0標(biāo)記上的燈會閃爍。這就是現(xiàn)象!
更進(jìn)一步的筆記和說明
如果你并不打算進(jìn)一步仔細(xì)學(xué)習(xí),到這里就可以走了,后面的內(nèi)容比較的硬核。
從IMX6ULL啟動方式說起
首先,我們不在乎外面的構(gòu)建問題,從代碼編寫入手。
IMX6ULL的啟動方式
BOOT 的處理過程是發(fā)生在I.MX6U 芯片上電以后,芯片會根據(jù) BOOT_MODE[1:0]的設(shè)置來選擇 BOOT 方式。換而言之,你的板子的BOOT開關(guān)如何,深刻的決定了你的板子是如何啟動的。
筆者的板子默認(rèn)是從EMMC啟動的,那里存放著默認(rèn)的Linux操作系統(tǒng)。當(dāng)然這是后面筆者需要學(xué)習(xí)的
所以你可以看到,我們是把 BOOT_MODE1 為 1,BOOT_MODE0 為 0 的時候此模式使能,在此模式下,芯片會執(zhí)行內(nèi)部的 boot ROM 代碼,這段 boot ROM 代碼會進(jìn)行硬件初始化(一部分外設(shè)),然后從 boot 設(shè)備(就是存放代碼的設(shè)備、比如 SD/EMMC、NAND)中將代碼拷貝出來復(fù)制到指定的 RAM 中,一般是 DDR。
我們就是想要設(shè)置SD卡啟動,一次來看,那就是對我們的1號和7號撥碼開關(guān)拉高到ON就OK了,這就是我們上面給出的截圖。具體的內(nèi)容可以查手冊的Boot內(nèi)容
定制合法的可燒寫識別的合法執(zhí)行文件
-
Image vector table,簡稱 IVT,IVT 里面包含了一系列的地址信息,這些地址信息在ROM 中按照固定的地址存放著。
-
Boot data,啟動數(shù)據(jù),包含了鏡像要拷貝到哪個地址,拷貝的大小是多少等等。
-
Device configuration data,簡稱DCD,設(shè)備配置信息,重點是 DDR3 的初始化配置。
-
用戶代碼可執(zhí)行文件,比如 led.bin。
實際上就是四個部分組成!可以看出最終燒寫到 I.MX6U 中的程序其組成為:IVT+Boot data+DCD+.bin。所以所生成的 load.imx 就是在 led.bin 前面加上 IVT+Boot data+DCD。內(nèi)部 Boot ROM 會將 load.imx 拷貝到 DDR 中,用戶代碼是要一定要從 0X87800000 這個地方開始的,因為鏈接地址為 0X87800000,load.imx 在用戶代碼前面又有 3KByte 的 IVT+Boot Data+DCD 數(shù)據(jù),因此 load.imx 在 DDR 中的起始地址就是 0X87800000-3072=0X877FF400。
load.imx 最前面的就是 IVT 和 Boot Data,IVT 包含了鏡像程序的入口點、指向 DCD 的指針和一些用作其它用途的指針。內(nèi)部 Boot ROM 要求IVT 應(yīng)該放到指定的位置,不同的啟動設(shè)備位置不同,而 IVT 在整個 load.imx 的最前面,其實就相當(dāng)于要求 load.imx 在燒寫的時候該燒寫到存儲設(shè)備的指定位置去。整個位置都是相對于存儲設(shè)備的起始地址的偏移
IVT部分存者的就是這些
首先是頭部信息:格式如上!
復(fù)位以后,I.MX6U 片內(nèi)的所有寄存器都會復(fù)位為默認(rèn)值,但是這些默認(rèn)值往往不是我們想要的值,而且有些外設(shè)我們必須在使用之前初始化它。為此 I.MX6U 提出了一個 DCD(Device Config Data)的概念,和 IVT、Boot Data 一樣,DCD 也是添加到 load.imx 里面的,緊跟在 IVT和 Boot Data 后面,IVT 里面也指定了DCD 的位置。DCD 其實就是 I.MX6U 寄存器地址和對應(yīng)的配置信息集合,Boot ROM 會使用這些寄存器地址和配置集合來初始化相應(yīng)的寄存器,比如開啟某些外設(shè)的時鐘、初始化 DDR 等等。DCD 區(qū)域不能超過 1768Byte,
其中Tag 是單字節(jié),固定為0XD2,Length 為兩個字節(jié),表示DCD 區(qū)域的大小,包含header,同樣是大端模式,Version 是單字節(jié),固定為 0X40 或者 0X41。
header的結(jié)構(gòu)還是類似的。CMD的格式如下:
這里的Tag 為一個字節(jié),固定為 0XCC。Length 是兩個字節(jié),包含寫入的命令數(shù)據(jù)長度,包含 header,同樣是大端模式。Parameter 為一個字節(jié)
①、設(shè)置CCGR0~CCGR6 這 7 個外設(shè)時鐘使能寄存器,默認(rèn)打開所有的外設(shè)時鐘。 ②、配置DDR3 所用的所有IO。 ③、配置MMDC 控制器,初始化DDR3。 就是干這些事情。正點原子的imxdownload也是實際上完成這個工作。
這些地址需要在手冊中查出來。一一填寫。這個是搬運(yùn)工的活,具體可以看
比如說這里,我們可以查到這個CCGR0寄存器映射地址是0x020C4068等等。看官可以查看我們的imxdownload.h看看具體的數(shù)值對應(yīng)著查,體會一下查手冊的快樂(不是)
關(guān)于點燈本身
實際上就是在操作寄存器!我們需要關(guān)心的寄存器,其實需要去手冊上查。比如說根據(jù)原理圖,我們發(fā)現(xiàn)想要驅(qū)動我們的LED0,就需要我們找寄存器:SW_MUX_GPIO1_IO03和SW_PAD_GPIO1_IO03。在手冊中,我們需要尋找的就是SW_MUX_CTL_PAD_GPIO1_IO03 SW MUX ControlRegister (IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03)和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03寄存器(Man!找了很久的手冊!)
現(xiàn)在只需要查詢手冊依照配置就好了!完事了就是上面的程序!