2019年10月

软件模拟 spi 时序有以下几个点需要注意:

  1. cs 使能后到第一个 sck 边沿需要延时。
  2. 最后一个sck 边沿到下一个 cs 需要延时。
  3. sck 的高电平和低电平本身需要维持时间。
  4. mosi 需要先把数据放上去,然后启动上升沿,然后延时 sck 高电平的时间。
  5. 在延时了 sck 高电平的时间后,读取 miso 的电平,然后启动下降沿,然后延时 sck 低电平的时间。

今天使用 systick 的时候,只使用了头文件 core_cm4.h,结果就报错了,原因是 __FPU_PRESENT 没有定义,这个定义其实在 stm32f4xx.h 里面。所以如果要解决这个错误,有两种方法:

  1. 只使用 core_cm4.h,然后碰到所有未定义的,都手动定义,或者添加相应的头文件。
  2. 不使用 core_cm4.h,使用 stm32f4xx.h。

比较起来,1 需要包含的头文件比较少,但是操作比较繁琐,2 需要的操作比较少,相应包含的头文件就比较多了。

  1. 可以使用 go get -u github.com/gogs/gogs 来升级,但是因为网络原因,这种升级方式失败了。另外一种是在 github 或者 gitee 上面直接下载源码,然后解压到 $GOPATH/src/github.com/gogs/gogs 位置。
  2. $GOPATH/src/github.com/gogs/gogs 这个位置,使用 go build 来构建,或者 go build -tags "sqlite pam cert" 来构建。
  3. 从旧文件夹里面把 custom, data, log 3个个人文件夹复制到现在的位置。
  4. 使用 ./gogs web 来测试。
  5. 还有不要忘了对比 scripts/init/gentoo 目录下的启动文件和现有的文件差别。

gogs 使用当中,团队的作用暂时来看,主要是权限上的使用,一部分人只有读的权限。

  1. 打补丁,patch -p1 < ../linux-2.6.22.6.patch 。 -p1 这个命令参数的意思是忽略补丁文件中目录的第一个 / 之前的内容。后面参数的意思是把指定目录中的 patch 文件打到当前目录。

  2. 配置。可以使用 find -name "*.defconfig" 查找一下内核当前支持的一些 board 的配置。找到 s3c2410_defconfig 比较相近,就执行 make s3c2410_defconfig 进行配置,这时候 s3c2410_defconfig 里面的配置就写入到 .config 文件中了。然后使用 make menuconfig 在 s3c2410_defconfig 的基础上进行修改。一般内核先配置 make s3c2410_defconfig,然后编译 make menuconfig。有时候厂家会提供 config_厂家,这时候只需 cp config_厂家 .config,然后 make menuconfig 就可以了。

  3. 编译内核直接用 make,但是给 uboot 用的内核需要用 make uImage 来编译,这个和正常的内核相比,多了一个头部内容。适合给 uboot 引导使用。

  4. uboot 中烧录内核使用 k 命令,这个命令具体可以在 cmd_menu.c 中找到 k 的具体内容, usbslave 1 0x30000000, nand erase kernel, nand write.jffs2 0x30000000 kernel (filesize)。 先接受内核到 0x30000000 内存中,然后擦除 nand 中 kernel 分区,然后从 0x30000000 内存地址处读取数据,写入 nand 中的 kernel 分区,写入(filesize) 大小。

  5. 如果删除了 root 分区,那么 内核启动到一定程度的时候,就会卡在那边。

  6. 内核编译的时候,根据 .config 生成 include/linux/autoconf.h 这个里面是对各个配置生成的详细的头文件,里面的 CONFIG_ 给各个 C语言的源码使用。.config 中设置为 y 或者 m,在 autoconfig.h 中宏定义就都是 1. 而 y 和 m 的区别体现在 子目录的 makefile 里面,比如说 drivers/net/makefile。在内核子目录中的 makefile 里面,配置为 y 和 m 是不同的, obj_y+= 这个会编译到内核里面去, obj_m+= 这个会编译为模块 ko 给内核加载。 配置还会出现在 include/config/auto.conf 中。配置的 y 和 m 先在 auto.conf 中自动生成好。

make uImage 时候,首先根据 .config 生成 autoconfig.h 给源代码使用,生成 auto.conf 给 顶层 makefile 包含,给子目录的 makefile 使用。

  1. linux-版本号 / Doc / kbuild 这里面的 makefile.txt 里面有内核 makefile 的详细讲解。 如果要编译为一个模块,类似的写法是:

    obj_m += ab.o
    ab_objs := a.o b.o

    这样 a.c, b.c 两个文件就会被先编译为 a.o, b.o,然后被链接为 ab.ko 这个模块。

  2. 当使用 make uImage 的时候,会使用顶层的 makefile,顶层 makefile 里面有 include (srctree)/arch/(ARCH)/Makefile , 这样就包含了 arm 这样架构目录下面的 makefile。顶层 makefile 里面还有 include /config/auto.conf,这样就包含了配置。 uImage 的依赖是 vmlinux, vmlinux 是真正的内核文件,加上了头部之后,才是 uImage。如果直接就是 make 的话,那么就是 all,all 依赖的也是 vmlinux。 make uImage V=1 这个命令是让编译时候打印出来的信息更加详细。打印信息中的 ld -T 后面的 lds 文件是具体的链接脚本。第一个编译的文件是 arch/arm/kernel/head.o ,这个是 head.S 汇编文件,链接脚本是 arch/arm/kernel/vmlinux.lds

tq2440 里面需要编译的是 zImage,然后使用配套的 uboot,可以直接下载 zImage 内核并启动。 tq2440 make zImage 的时候,会提示错误 Can't use 'defined(@array)' (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373. /opt/ARM/mini6410/linux/linux-2.6.38/kernel/Makefile:140: recipe for target 'kernel/timeconst.h' failed make[1]: [kernel/timeconst.h] Error 255 Makefile:916: recipe for target 'kernel' failed make: [kernel] Error 2

报错信息提示我们文件kernelkernel/timeconst.pl的第373不能使用'defined(@array)',将kernel/timeconst.pl中第373行的defined()去掉只留下@val就可以了. 考虑去掉defined(),改为: 372         @val = @{canned_values{hz}}; 373         if (!@val) { 374                 @val = compute_values($hz); 375         }

关于 zImage 和 uImage 的差别,可以参考 https://www.cnblogs.com/linhaostudy/p/6735697.html

  1. compress 目录下面的 head.S 是为了压缩内核的。如果内核过大,可以压缩内核,然后在内核前面加上自解压代码,这样组合起来变成一个小一点的内核。运行的时候,先自解压,之后再执行内核。

  2. lookup_machine_type 中的
    
    3:      .long .
         .long arch_info_begin
         .long arch_info_end

adr r3, 3b @ 让 r3 等于 标号3所在地方的物理地址。 ldmia r3, {r4, r5, r6} @ 让 r4 等于 r3 的虚拟地址,也就是 标号3 的虚拟地址, 让 r5 等于 arch_info_begin, r6 等于 arch_info_end. sub r3, r3, r4 @ r3 等于虚拟地址和物理地址之间的偏移 add r5, r5, r3 @ r5 是对应的 物理地址 add r6, r6, r3 @ r6 是对应的 物理地址。


11. head.S 中内核启动时,首先判断是否支持 cpu,然后判断是否支持 单板(这个通过 u-boot 中 执行内核的 kernel 函数调用时传入的 machine_id),然后建立页表, 使能 mmu,然后跳转 start_kernel 来处理 u-boot 传入的启动参数。

start_kernel setup_arch //解析 u-boot 传入的启动参数 setup_command_line //解析 u-boot 传入的启动参数 parse_early_param do_early_param 从 setup_start 到 setup_end,调用 early 函数。 unknown_bootoption obsolute_checksetup 从 setup_start 到 setup_end,调用非 early 函数。 rest_init kernel_init prepare_namespace mount_root 这样就能挂载根文件系统了。 init_post 这个函数里面 打开 console,然后执行 init 等应用程序


挂载根文件系统按照 u-boot 中的 boot_args 这个参数来做。 uboot 中的 boot_args 里面的 root=/dev/mtdblock3 是和 linux 中的 arch/arm/plat-s3c24xx 中的 common-smdk.c 定义的 smdk_default_nand_part[] 这个分区结构体数组对应的。

使用 patch 命令打补丁, -p1 标识忽略第一级目录。 在串口上使用 q 命令,推出菜单界面,然后 print 可以打印出环境变量。想要返回菜单,只要输入 menu 即可。输入 ? 打印出可选命令。输入多条命令,可以使用分号隔开。输入 help 是各个命令的 usage 短帮助信息,输入 help print 这样指定命令,就会出现 help 长帮助信息。使用 set 命令修改环境变量,使用 save 保存环境变量,使用 reset 复位。boot 命令可以启动内核。 make 最后打印出来的信息是可以看到 ld 的具体命令,里面包含了链接地址,这个就是 sdram 中的位置。 start.s 汇编文件里面包含了启动汇编。 增加 uboot 命令,只需要在源文件中增加 类似于 do_bootm 这样的函数 和 U_BOOT_CMD 这样的宏,然后修改相应文件夹下的 makefile,增加对应的 obj 即可。然后 make,实在不行就 make disclean, make xxx_config, make。 linux 分区: boot, env, kernel, root ,具体分区在源码里面写死。MTDPARTS_DEFAULT 这个地方定义的,包含在单板头文件里面。使用命令 mtd 可以看到当前分区情况。do_bootm 是用来启动内核的, jffs2 这种格式不要求对齐,其他格式可能需要各种对齐。

flash 上存在的内核是 uImage,这个是由头部和真正的内核两部分组成。in_load 加载地址, in_ep 入口地址。加载地址是 u-boot 需要把真正的内核移动到那个地址。普通 bootm 的时候,可能把 uImage 放到 0x30007FC0,然后前面64字节是头部,真正的内核在 0x30008000 这个位置,如果 in_load 也是这个地址,那么 u-boot 就可以省略移动内核这个步骤,否则还需要把内核移动到 in_load 的地址。真正的启动命令 do_bootm_linux, 里面的 theKernel 就等于 in_ep 入口地址,然后从这个地址开始执行内核。

u-boot 需要把一些参数给 内核,这些参数放在一些约定好的地址,然后从这些地址按照指定的格式读取,可能是 0x30000100 地址。通过 setup_start_tag(bd) 这样的方式设定,最后一个是 setup_end_tag(bd)。bd->bi_arch_number 机器地址。

  1. ram 初始化: 在 start.S 中, bl cpu_init_crit 这句,在 tq2440 中是直接调用,在韦东山里面是通过和 TEXT_BASE 进行比较,如果从 RAM 中运行就不进行 初始化。

  2. uboot 第一阶段初始化主要在 start.S 里面,主要是芯片本身初始化。第二阶段初始化是从 lib_arm/board.c 里面的 start_armboot 开始的,里面有一个 for 循环,通过函数指针调用函数,存储这些指针的结构体 gd 在 SDRAM 中的 128 字节的 CPU_GBL_DATA_SIZE 里面,相应的函数实现在 board/EmbedSky/EmbedSky.c 里面。

  3. 在 start_armboot 函数中,韦东山代码增加了一个判断,从调试器加载时,相应的处理。if (PreLoadedONRAM)

  4. command.c command.h 中有 u_boot_cmd 这个段相关的变量,最后通过 链接文件,链接到一起。然后函数执行的时候,遍历段中所有的结构体的命令,相等的就执行相应的函数。

  5. 增加 uboot 命令,可以仿照 dobootm 命令来制作自己的命令,需要提供 函数 do 和 通过宏定义来实现的结构体变量 U_BOOT_CMD。还有别忘了相应的头文件。

  6. uboot 的 清除编译过程文件是 make distclean,配置命令 make EmbedSky_config,编译是 make。

  7. uboot 读出内核,通过 nand read.jffs2 0x30007FC0 kernel 来从 nand 中的 kernel 分区里面读出内核。nand 中的 kernel 分区是在 include/configs/EmbedSky.h 中定义的,通过宏定义 MTDPARTS_DEFAULT 来定义具体的各个分区。也可以通过 uboot 的命令行模式中输入 mtd 命令来查看当前 uboot 的 分区信息。 分区的名字只是个标识不重要,主要的是分区的开始偏移地址和分区大小。

  8. 命令 nand read.jffs2 不需要页对齐,可以长度是任意的,其他的 read 命令需要对齐。

  9. uboot 的内核镜像是 uImage,由头部和真正的内核组成,头部中的 ih_load 是加载地址,ih_ep 是入口地址,uboot 通过读取头部数据,把内核放到加载地址,然后跳转到入口地址执行。

  10. 编译时链接地址 0x30007FC0,这个地址加上 64 字节的 uImage 头部内容,结果就是 0x30008000,这个就是真正的内核加载地址。这样做的好处是,因为地址能够匹配上,那么 cmd_bootm.c 中 ntohl(hdr->ih_load) == addr 成立,就不会移动内核,否则通过 memmove 函数在调用把内核移动到加载地址处,这样能够加快启动速度。

  11. do_bootm_linux 来启动内核。

  12. uboot 通过设置启动参数的方法告诉内核一些必要的参数,然后跳转到入口地址,真正的开始启动内核。

  13. ih_ep 中包含函数指针,把这个函数指针交给 theKernel,然后调用 theKernel 来启动内核。

  14. uboot 在 0x30001000 按照一定的格式存放内核参数,然后由内核来读取这些参数。 在 cmd_bootm.c 中使用 setup_start_tag(bd),setup_memory_tags(bd), setup_end_tag(bd) 这种格式来存放 tag 参数。

  15. uboot 中 armlinux.c 里面 memory_tags 里面关于 sdram 开始地址和大小,是在 board 文件中的 dram_init()中 通过 gd->bd->bi_dram[0].start, .size 来赋值的。

  16. commandline_tas() 中的 参数 commandline 来源于 getenv 中的 “bootargs",这个可以在 uboot 运行起来会直接 print 查看,也可以查看源码。root= 是根文件系统位置,init= 这个是第一个应用程序,console= 这个是信息打印的窗口。

  17. theKernel(0, bd->bi_arch_number, bd->bi_boot_parms), 第三个参数是从 0x30001000 开始的 start_tag 开始的一连串的 parms。bi_arch_number 这个是机器ID,在 board 文件里面通过 gd->bd->bi_arch_number 来定义,arch_number 不同的电路板是不一样的数值。uboot 把 arch_number 交给内核,内核检查是否支持这个 arch_number 的PCBA。如果支持,系统控制权就交给内核,后面就没有 uboot 的事情了。

  18. 在 uboot 命令行下,输入 boot,就可以通过 bootcmd 和 bootarg 两个参数来启动内核。