.

相关bug可以查看 《bug记录/imx6ull》

从 USB 下载程序

按照正点原子步骤编译链接,更改为二进制文件,

1
2
3
4
led.bin : 1_leds.s
arm-linux-gnueabihf-gcc -g -c 1_leds.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

要想在imx上运行,还需要在 bin 文件前加上一个头部,用于初始化 ddr,这个正点原子已经给出 imxdownload。(它程序中配置的是将imx文件拷贝到 SD 卡,需要将这些文件注释掉,保留创建 imx 的文件).

1
2
gcc -o imxdownload imxdownload.c
sudo ./imxdownload led.bin /dev/sdd

最后获取 nxp 提供的 uuu 工具(不是用aptget安装的),

1
sudo snap install universal-update-utility

然后将 imx 通过 USB 连接到电脑,注意要将 USB 接到虚拟机中,然后可能还需要执行下面这个命令才能连上 usb 。

1
sudo apt-get install libusb-1.0.0-dev libzip-dev libbz2-dev

在 makefile 中添加下面这一行:

1
2
run:
sudo uuu load.imx

至此,编译运行后,将板子拨码开关拨到 USB 启动,按下 reset , 控制台输入 make run 即可执行, 有个缺点是,只有 手动 下载一次,才能运行一次,按板子上的 reset 好像没用。完整 makefile 如下:

1
2
3
4
5
6
7
8
9
10
11
led.bin : 1_leds.s
arm-linux-gnueabihf-gcc -g -c 1_leds.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
gcc -o imxdownload imxdownload.c
sudo ./imxdownload led.bin /dev/sdd
run:
sudo uuu load.imx
clean:
rm -rf *.o led.bin led.elf led.dis

通用makefile

首先重命名一些变量

1
2
3
4
5
6
7
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= ledc

CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

主要就是找头文件路径,源文件路径。

头文件路径交给 gcc 使用,源文件路径赋值给 vpath,也供 gcc 使用。

先找到了各自的文件夹。

1
2
3
4
5
6
7
8
9
10
INCUDIRS := imx6ull \
bsp/clk \
bsp/delay \
bsp/led \
project

SRCDIRS := project \
bsp/clk \
bsp/delay \
bsp/led

然后需要从文件夹中获取头文件和源文件。

1
2
3
4
5
6
7
INCLUDE  := $(patsubst %, -I %, $(INCUDIRS)) #引用头文件时,需要加上 -I

SFILES := $(foreach dir , $(SRCDIRS), $(wildcard $(dir)/*.s)) #通配符在函数中会失效,需要加上 wildcard
CFILES := $(foreach dir , $(SRCDIRS), $(wildcard $(dir)/*.c)) #干嘛要加上路径呐,后面也不用路径

SFILESNDIR := $(notdir $(SFILES))# 去除路径,只取文件名,
CFILESNDIR := $(notdir $(CFILES))

输入文件(头文件,源文件)找完了,需要找输出文件:

1
2
3
4
SOBJS    := $(patsubst %.s, obj/%.o, $(SFILESNDIR))
COBJS := $(patsubst %.c, obj/%.o, $(CFILESNDIR))

OBJS := $(SOBJS)$(COBJS)

然后进行编译:

1
2
3
4
5
6
7
VPATH    := $(SRCDIRS)   #指定可以在那些文件夹中寻找依赖文件
$(SOBJS) : obj/%.o : %.s#不能这么用 $(SOBJS) : $(SFILES),和之前的匹配方式不一样(%o:%c),之前的会为每个.o 调用一次 gcc
# 如果向上面那行使用的话,会把所有同时作为输入编译,结果放到SOBJS的每个目标文件中($<值其实是一个一个取出来的)
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<

$(COBJS) : obj/%.o : %.c # 和正点的式子有区别 $(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<

为什么需要使用 $(SOBJS) 和 $(COBJS) ,不加时编译链接也不会出错。感觉像是做了一个限定,表示从 $(COBJS) 中找,

以 %c, %.c开头时(即以模式定义时),应该会自动为每个.c 编译一次,$< 会依次取出文件(和 $^不一样)

以$(SOBJS) : $(SFILES)开头时,会一次性编译 SFILES第一个文件(不会往后取文件),结果放到每个 SOBJS文件中。产生指令如下,且会报错:

生成最终的目标文件:

1
2
3
4
5
$(TARGET).bin : $(OBJS)
$(LD) -Timx6u.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(objdump) -D -m arm $(TARGET).elf > $(TARGET).dis
sudo ./imxdownload $@ /dev

整个文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= ledc

CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

INCUDIRS := imx6ull \
bsp/clk \
bsp/delay \
bsp/led \
project

SRCDIRS := project \
bsp/clk \
bsp/delay \
bsp/led

INCLUDE := $(patsubst %, -I %, $(INCUDIRS)) #引用头文件时,需要加上 -I

SFILES := $(foreach dir , $(SRCDIRS), $(wildcard $(dir)/*.s)) #通配符在函数中会失效,需要加上 wildcard
CFILES := $(foreach dir , $(SRCDIRS), $(wildcard $(dir)/*.c)) #干嘛要加上路径呐,后面也不用路径

SFILESNDIR := $(notdir $(SFILES))
CFILESNDIR := $(notdir $(CFILES))

SOBJS := $(patsubst %.s, obj/%.o, $(SFILESNDIR))
COBJS := $(patsubst %.c, obj/%.o, $(CFILESNDIR))

OBJS := $(SOBJS)$(COBJS)

VPATH := $(SRCDIRS) #指定可以在那些文件夹中寻找依赖文件
.PHONY : clean

$(TARGET).bin : $(OBJS)
$(LD) -Timx6u.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
sudo ./imxdownload $@ /dev

$(SOBJS) : obj/%.o : %.s#不能这么用 $(SOBJS) : $(SFILES),和之前的匹配方式不一样(%o:%c),之前的会为每个.o 调用一次 gcc,
# 如果向上面那行使用的话,会把所有同时作为输入编译,结果放到SOBJS的每个目标文件中($<值其实是一个一个取出来的)
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<

$(COBJS) : obj/%.o : %.c # 和正点的式子有区别 $(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<

run:
sudo uuu load.imx
clean :
rm $(TARGET).bin $(TARGET).dis $(TARGET).elf $(OBJS)


#SOBJS := $(patsubst %, obj/%, $(SFILESNDIR:.s=.o)) 正点是这么用的
#COBJS := $(patsubst %, obj/%, $(CFILESNDIR:.c=.o))
print:
# echo INCLUDE = $(INCLUDE)
# echo SFILES = $(SFILES)
# echo CFILES = $(CFILES)
# echo SFILESNDIR = $(SFILESNDIR)
# echo CFILESNDIR = $(CFILESNDIR)
# echo COBJS = $(COBJS)
# echo SOBJS = $(SOBJS)
# echo OBJS = $(OBJS)

三极管区分

NPN 三极管 输入高电平导通,反之关闭

PNP 三极管 输入低电平导通,反之关闭

正点原子的一个问题

07key实验中,程序没法运行,是因为 清除 .bss时,.bss 段没用四字节对齐,导致误清了后面的数据,将 .bss 四字节对齐即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
SECTIONS{
. = 0X87800000;
.text : {
obj/start.o
*(.text)
}
.rodata ALIGN(4) : { *(.rodata) }
.data ALIGN(4) : { *(.data) }
. = ALIGN(4); <<<<<<<<<加上这里后就可以实现四字节对齐
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(.COMMON) }
__bss_end = .;
}

tips:

左移右移 是两个 << >>

或运算与运算是一个 & |

时钟配置

总共需要配置两个地方,一个是 pll1,一个时 cacrr寄存器。(后面的那个/2 没用,正点原子说的)

接下来详细看 pll1 部分,有两个复用选择器。配置前,要先切换到step_clk上,然后配置 pll1,最后切换回 main_clk上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 初始化时钟
* @details 为什么需要先倍频再分频呢,(既然后面要分频,前面不能少倍频一点吗):arm pll输出有限制,650 MHz to 1.3 GHz
* @details 为什么要切换时钟,难道不支持直接配置吗:具体原因不知道,因为涉及到倍频分频,配置过程中,可能导致频率不稳定,而且不清楚之
* 前的配置,理应先关闭时钟再配置。
*/
void imx6u_clkinit(void){
/*设置主频*/
if(((CCM->CCSR >> 2) & 0X1) == 0){//当前时钟使用MAIN_CLK,则切换到step_clk
CCM->CCSR &= ~(1 << 8); //设置step_clk = osc_clk = 24M
CCM->CCSR |= (1 << 2); // pll_sw_clk =step_clk = 24M
}
/*设置PLL1 = 1056M*/
CCM_ANALOG->PLL_ARM &= ~(0X7F);
CCM_ANALOG->PLL_ARM |= 88; // 88 * 24 = 2112; PLL output frequency = Fref * DIV_SEL/2 计算得输出为1056M
CCM_ANALOG->PLL_ARM |= (1 << 13); //详见参考手册 644 页
/*至此 pll 配置完成,需要完成后面配置,按照629页时钟树,需要设一个分频*/
CCM->CACRR = 0x01; //详见参考书册 629 页
/*从step_clk 切换回 main_clk*/
CCM->CCSR &= ~(1 << 2); // pll_sw_clk = 528M
}

正点原子犯的一个错

如下图配置时钟时:

1
2
3
4
/*配置常用外设时钟源 AHB_CLK_ROOT,PERCLK_CLK_ROOT, IPG_CLK_ROOT, 第一个最大配132M,第二三个最大配66M,参考手册643页面*/
//先设置 AHB,
CCM->CBCMR &= ~(0X03 << 18); //第一次配置时直接在寄存器上配置,结果无输出,系统不闪灯,使用reg来配置才对了!!!!!!!!
CCM->CBCMR |= (0X01 << 18); //设置时钟来源 pll2_pfd2,无分频, 396M

这样配置不行,因为第一次配时看似是清零,实际上将寄存器相应区域配为了0,在处理器看来这是有意义的值,这个值可能导致系统(时钟)停止运行。为此,需要一个reg中介,来一次写入。

1
2
3
4
5
6
/*配置常用外设时钟源 AHB_CLK_ROOT,PERCLK_CLK_ROOT, IPG_CLK_ROOT, 第一个最大配132M,第二三个最大配66M,参考手册643页面*/
//先设置 AHB,
reg = CCM->CBCMR;
reg &= ~(0X03 << 18); //第一次配置时直接在寄存器上配置,结果无输出,系统不闪灯,使用reg来配置才对了!!!!!!!!
reg |= (0X01 << 18); //设置时钟来源 pll2_pfd2,无分频, 396M
CCM->CBCMR = reg;

ISB DSB 使用

详见https://zhuanlan.zhihu.com/p/601037646

在大部分场景下,我们不用特意关注内存屏障的,特别是在单处理器系统里,虽然CPU内部支持乱序执行以及预测式的执行,但是总体来说,CPU会保证最终执行结果符合程序员的要求。在多核并发编程的场景下,程序员需要考虑是不是应该用内存屏障指令。下面是一些需要考虑使用内存屏障指令的典型场景。

在多个不同CPU内核之间共享数据。在弱一致性内存模型下,某个CPU乱的内存访问次序可能会产生竞争访问。
执行和外设相关的操作,例如DMA操作。启动DMA操作的流程通常是这样的:第一步,把数据写入DMA缓冲区里;第二步,设置DMA相关寄存器来启动DMA。如果这中间没有内存屏障指令,第二步的相关操作有可能在第一步前面执行,这样DMA就传输了错误的数据。
修改内存管理的策略,例如上下文切换、请求缺页以及修改页表等。
修改存储指令的内存区域,例如自修改代码的场景。
总之,我们使用内存屏障指令的目的是想让CPU按照程序代码逻辑来执行,而不是被CPU乱序执行和预测执行打乱了代码的执行次序。

原子课程中,使用到了数据同步,指令同步

1
2
3
4
5
6
7
/*设置中断向量偏移 */
LDR R0, =0X87800000
dsb //数据同步
isb //指令同比
MCR p15, 0, R0, c12, c0, 0
dsb //数据同步
isb //指令同比

疑问

  1. 为什么中断保存是保存 r0-r3,这些寄存器,这些不是自动保存的吗
1
2
3
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */

U-BOOT 部分感想

  1. 使用u-boot时,u-boot被拷贝到dram 中 0x87800000 开始运行,堆栈指针指向内部 ram,后面为了给linux腾出空间,u-boot 代码要被拷贝到0x9fff0000(大致)开始运行,堆栈指针也指向了dram

  2. 一个疑问,如下,正点说应该引用stack 中的arch_reserve_stacks函数,但实际不可能呀。。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int arch_reserve_stacks(void) // 和stack的函数重名了,这解释的通吗
    {
    return 0;
    }

    static int reserve_stacks(void)
    {
    /* make stack pointer 16-byte aligned */
    gd->start_addr_sp -= 16;
    gd->start_addr_sp &= ~0xf;

    /*
    * let the architecture-specific code tailor gd->start_addr_sp and
    * gd->irq_sp
    */
    return arch_reserve_stacks();
    }