南昌網(wǎng)站建設制作網(wǎng)絡推廣接單平臺
目錄
讓我們從環(huán)境配置開始
目標平臺
從Ubuntu開始
從交叉編譯器繼續(xù)
arm-linux-gnueabihf-gcc
vscode
沒學過ARM匯編
正文開始——速度體驗一把
寫一個鏈接腳本
寫一個簡單的Makefile腳本
使用正點原子的imxdownload下載到自己的SD卡上
更進一步的筆記和說明
從IMX6ULL啟動方式說起
IMX6ULL的啟動方式
定制合法的可燒寫識別的合法執(zhí)行文件
關于點燈本身
讓我們從環(huán)境配置開始
當你看到這篇文章的時候,說明你已經(jīng)準備好開始嵌入式Linux開發(fā)之旅了。這篇博客也是筆者自己學習嵌入式Linux開發(fā)的日記。希望自己可以堅持到寫完這個博客筆記,通關一次簡單的嵌入式Linux開發(fā)。
目標平臺
筆者使用的是正點原子Alpha開發(fā)板,上面是Cortex-A7的IMX6ULL的芯片,主頻啥的談不上高,但是學習足夠了
筆者使用的是Ubuntu24.04(其實別的也行)
從Ubuntu開始
起始從哪里開始都行,筆者這里為了少折騰,選擇從Ubuntu開始。筆者安裝的在了Arch主機上的雙系統(tǒng)上,具體如何操作這里不是重點。你可以跟正點原子一樣歡樂虛擬機,但是我尋思著太麻煩了。
把Ubuntu配成自己喜歡的樣子(包括但不限于輸入法,基本的vscode,shell自定義,桌面自定義,代理等),基本上就可以歡歡樂樂的開始嵌入式Linux開發(fā)了。
關于如何配置Ubuntu,可以自己上網(wǎng)找博客看,在這里的一般都不會對后面的開發(fā)造成實質的影響。
從交叉編譯器繼續(xù)
實際上筆者之前玩過交叉編譯器,簡單的講講啥是交叉編譯器,這個東西對于我們嵌入式開發(fā)者而言會天天見到。筆者建議好好熟悉了解相關的概念。
首先,你現(xiàn)在電腦上(哦!也有可能是虛擬機!)默認的自帶的gcc是在x64上跑的,編寫的程序是給x64架構的程序使用的編譯器。誠然,我們開發(fā)肯定還是在x64架構的機器上搞開發(fā)。但是我們現(xiàn)在要面向的平臺毫無疑問的是ARM架構的機器。那怎么辦呢?
這個時候就需要請出我們的交叉編譯器了。
arm-linux-gnueabihf-gcc
是的!這是我們要使用的gcc編譯器。它隸屬于arm-linux交叉編譯器的大家族
首先,交叉編譯器的命名規(guī)則是arch [-vendor] [-os] [-(gnu)eabi] [-language]
-
arch - 體系架構, 如arm(ARM-32bit)、aarch64(ARM-64bit)、x86等;
-
vendor -工具鏈提供商,經(jīng)常省略,或用 none 替代;
-
os - 目標操作系統(tǒng), 如linux,沒針對具體 os 則 用 none 替代。同時沒有 vendor 和os 使用一個 none 替代。
-
eabi - 嵌入式應用二進制接口(Embedded Application binary Interface)
-
language - 編譯語言,如gcc,g++
那事情就很簡單的了:這個是面向arm架構的,給嵌入式二進制armhf接口生成跨平臺程序的gcc(當然,還有其他的比如說arm-none-eabi-gcc,gcc-arm-linux-gnueabi等等,他們的區(qū)別不算大,但是值得你去根據(jù)你使用的機器查到底用哪個版本的gcc),如果你跟我一樣,使用的是面向CortexA7的IMX6ULL進行開發(fā),你所干的事情很簡單。
sudo apt install gcc-arm-linux-gnueabihf
啥?你說正點原子告訴你Linaro那里去下?拜托,早就不維護了那個地址。當然如果你擔心你沒法排查問題,用老壁燈gcc4沒問題,但是你確定你這是在學習嗎?之后的開發(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一句話搞定的事情。
沒學過ARM匯編
正文開始——速度體驗一把
速速開始正文。
我們將會把代碼燒到SD卡上,然后操控板子使用SD卡啟動啟動到內存里去。裸機程序,沒有操作系統(tǒng),你可能就會想起STM32的開發(fā)了。是的,我們需要起手寫startup,放在DCD初始化結束后的位置上,IMX6ULL芯片執(zhí)行結束后會跳轉到地址0x87800000的地方,所以,我們需要在那個地方安排上我們的啟動程序。
一個startup其實可能比你想象的要簡單。
// start up files for led ? .global _start _start: /* GCC always find symbols in _start for the entry */mrs r0, cpsr/*該操作清除了 r0 中最低的 5 位,即將處理器模式和相關標志清除*/bic r0, r0, #0x1f/*下面這條指令執(zhí)行按位或操作。它將 r0 中的內容與 0x13(即二進制的 10011)進行按位或操作,并將結果存回 r0。0x13 代表了設置 CPSR 中的模式位為 0x13(對應用戶模式)。*/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寄存器的值,對之進行修改,然后放回去!簡單吧!
-
然后設置我們的棧指針指向更高的2MB地址出(說明我們給程序2MB的地址空間大小,足夠!)
-
跳轉道我們后面寫的main函數(shù)里去。
后面我們的main函數(shù)也不難:
#ifndef __MAIN_H #define __MAIN_H ? /* * CCM相關寄存器地址 */ #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相關寄存器地址 */ #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068) #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4) ? /* * GPIO1相關寄存器地址 */ #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ù)的主體,如果您想嘗試,直接復制就好。
#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 :) }
后面我會一個個結束,到這里就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腳本的寫法,當然不止這一種!你可以按照這個層級把文件排放:
現(xiàn)在直接make得到我們的bin文件。
使用正點原子的imxdownload下載到自己的SD卡上
下一步是使用imxdownload將我們的bin文件下載到我們的SD卡上,這個imxdownload文件是正點原子提供的,實際上就是組合我們的頭部信息進一步生成可以被板子執(zhí)行的文件。
現(xiàn)在我們需要做的就是插上SD卡。查看自己的SD卡被分配的分區(qū)。一般的/dev/sda是自己的電腦的磁盤,嗯,一種最好的方式是查看自己的dmesg輸出信息。
sudo dmesg | tail -n 20這個指令將會打印出內核日志文件的倒數(shù)二十行它一般會記載著我們新USB設備掛載的信息
[ 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
所以,筆者得到的目標文件是向/dev/sdb寫東西!
注意,使用imxdownload將會完全覆蓋里面原本的東西,請做好備份再寫!
請注意!確保自己的SD卡掛載的位置!不要寫錯地方了,操作時不可逆的。正點原子之前提供的辦法是插拔反復對比,這個辦法不好但是總歸管用。
發(fā)現(xiàn)你的終端不自動補全imxdownload,請修改文件權限為744,即對自己完全可用,其他人只有讀權限的文件!
./imxdownload led.bin /dev/sdb
輸入密碼后,觀察寫入速率,一般而言是一百多到幾百KB每秒,大于這個速度上MB的一概認為是讀寫失敗,重新拔下來看看自己的sd卡有沒有接好。
現(xiàn)在調整板子的啟動模式是SD卡啟動,如下所示:
現(xiàn)在,你的板子上電,應該可以看到現(xiàn)象是:
是的,DS0標記上的燈會閃爍。這就是現(xiàn)象!
更進一步的筆記和說明
如果你并不打算進一步仔細學習,到這里就可以走了,后面的內容比較的硬核。
從IMX6ULL啟動方式說起
首先,我們不在乎外面的構建問題,從代碼編寫入手。
IMX6ULL的啟動方式
BOOT 的處理過程是發(fā)生在I.MX6U 芯片上電以后,芯片會根據(jù) BOOT_MODE[1:0]的設置來選擇 BOOT 方式。換而言之,你的板子的BOOT開關如何,深刻的決定了你的板子是如何啟動的。
筆者的板子默認是從EMMC啟動的,那里存放著默認的Linux操作系統(tǒng)。當然這是后面筆者需要學習的
所以你可以看到,我們是把 BOOT_MODE1 為 1,BOOT_MODE0 為 0 的時候此模式使能,在此模式下,芯片會執(zhí)行內部的 boot ROM 代碼,這段 boot ROM 代碼會進行硬件初始化(一部分外設),然后從 boot 設備(就是存放代碼的設備、比如 SD/EMMC、NAND)中將代碼拷貝出來復制到指定的 RAM 中,一般是 DDR。
我們就是想要設置SD卡啟動,一次來看,那就是對我們的1號和7號撥碼開關拉高到ON就OK了,這就是我們上面給出的截圖。具體的內容可以查手冊的Boot內容
定制合法的可燒寫識別的合法執(zhí)行文件
-
Image vector table,簡稱 IVT,IVT 里面包含了一系列的地址信息,這些地址信息在ROM 中按照固定的地址存放著。
-
Boot data,啟動數(shù)據(jù),包含了鏡像要拷貝到哪個地址,拷貝的大小是多少等等。
-
Device configuration data,簡稱DCD,設備配置信息,重點是 DDR3 的初始化配置。
-
用戶代碼可執(zhí)行文件,比如 led.bin。
實際上就是四個部分組成!可以看出最終燒寫到 I.MX6U 中的程序其組成為:IVT+Boot data+DCD+.bin。所以所生成的 load.imx 就是在 led.bin 前面加上 IVT+Boot data+DCD。內部 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 的指針和一些用作其它用途的指針。內部 Boot ROM 要求IVT 應該放到指定的位置,不同的啟動設備位置不同,而 IVT 在整個 load.imx 的最前面,其實就相當于要求 load.imx 在燒寫的時候該燒寫到存儲設備的指定位置去。整個位置都是相對于存儲設備的起始地址的偏移
IVT部分存者的就是這些
首先是頭部信息:格式如上!
復位以后,I.MX6U 片內的所有寄存器都會復位為默認值,但是這些默認值往往不是我們想要的值,而且有些外設我們必須在使用之前初始化它。為此 I.MX6U 提出了一個 DCD(Device Config Data)的概念,和 IVT、Boot Data 一樣,DCD 也是添加到 load.imx 里面的,緊跟在 IVT和 Boot Data 后面,IVT 里面也指定了DCD 的位置。DCD 其實就是 I.MX6U 寄存器地址和對應的配置信息集合,Boot ROM 會使用這些寄存器地址和配置集合來初始化相應的寄存器,比如開啟某些外設的時鐘、初始化 DDR 等等。DCD 區(qū)域不能超過 1768Byte,
其中Tag 是單字節(jié),固定為0XD2,Length 為兩個字節(jié),表示DCD 區(qū)域的大小,包含header,同樣是大端模式,Version 是單字節(jié),固定為 0X40 或者 0X41。
header的結構還是類似的。CMD的格式如下:
這里的Tag 為一個字節(jié),固定為 0XCC。Length 是兩個字節(jié),包含寫入的命令數(shù)據(jù)長度,包含 header,同樣是大端模式。Parameter 為一個字節(jié)
①、設置CCGR0~CCGR6 這 7 個外設時鐘使能寄存器,默認打開所有的外設時鐘。 ②、配置DDR3 所用的所有IO。 ③、配置MMDC 控制器,初始化DDR3。 就是干這些事情。正點原子的imxdownload也是實際上完成這個工作。
這些地址需要在手冊中查出來。一一填寫。這個是搬運工的活,具體可以看
比如說這里,我們可以查到這個CCGR0寄存器映射地址是0x020C4068等等??垂倏梢圆榭次覀兊膇mxdownload.h看看具體的數(shù)值對應著查,體會一下查手冊的快樂(不是)
關于點燈本身
實際上就是在操作寄存器!我們需要關心的寄存器,其實需要去手冊上查。比如說根據(jù)原理圖,我們發(fā)現(xiàn)想要驅動我們的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)在只需要查詢手冊依照配置就好了!完事了就是上面的程序!