WCH恒沁单片机-CH32V307学习记录1----启动代码分析

WCH恒沁单片机-CH32V307学习记录1----启动代码分析

启动流程

启动流程的代码看起来和 RAM 系列的没啥区别,内部逻辑都是一样的。

  1. 设置栈空间
  2. 设置向量表
  3. 搬运代码到运行空间
  4. 设置系统时钟
  5. 跳转到 main 函数

接下来咱们就一步一步把代码看起来~

汇编代码

该代码在 MounRiver Studio 后可以在 Startup 文件夹中找到 startup_ch32v30x_D8C.S 文件。

在这里插入图片描述
第11行的意思是说把 .section 的后面的代码叫做 .init , “ax”代表 allocatable 和 executable。
第12行: .global 的意思是声明一个全局变量叫做 _start,这个变量可以被其他的部分的程序所链接。
第13行:按照 1 字节对齐。 (目前看起来没啥用,因为链接脚本中也有对应的对齐指令。待测试验证)

在这里插入图片描述
第14行: 先把这段汇编码搞个名字叫做 _start 这个后面要在链接脚本中指定程序入口使用。
第15行: 通过 j 跳转到 handle_reset 函数中。
第16~28行:
后面的 .word 就是使用一个字的空间来放后面的数值。

HEX 0x00000013 == BIN 00010011  对应汇编码 NOP
HEX 0x00100073 == BIN 0001 0000 0000 0000 0111 0011 对应汇编码 ebreak

Risc-V Ebreak
在这里插入图片描述
第31~63行:
这里只截取了部分的代码,因为内容都是差不多的。
最开始的 _vector_base这个叫做 label ,在这里是作为当前位置的一个标记。
下面一行 .option norvc 这和 .option 的作用是设置某些架构的特定选项,rvc 和 norvc对应 risc-v 结构中的压缩指令的打开与关闭。
其中 .word 中断声明的顺序也是按照手册里面的顺序进行的声明。下面的截图仅截取了一部分。
在这里插入图片描述
好的继续看回代码。

在这里插入图片描述
中间涉略了一部分代码。那么,这里就有个疑问了。为什么要在 .word 声明空间时要关闭 rvc 而在 .weak 声明时有要重新打开 rvc 了呢?【TODO 欢迎各位大神评论区指正】。

补充一下 .word 是代表声明一个字的空间,并给这一个字的空间起名。

.weak伪操作用于设置符号的属性为弱,在汇编程序中,符号的默认属性为强(strong),如果此符号之前从未定义过,则同时创建此符号,并定义其属性为weak。
如果符号的属性为weak,那么它无需定义具体的内容。在链接的过程中,另外一个属性为strong的同名符号可以将此weak符号的内容强制覆盖。利用此特性,.weak伪操作常用于预先预留一个空符号,使得其能够通过汇编器语法检查,但是在后续的程序中定义符号的真正实体,并且在链接阶段将空符号覆盖并链接。

接下来的代码就是比较有逻辑的代码了,让我们慢慢分析:
在这里插入图片描述
首先是给这个段设置为 .text.handle_reset 段。
再搞一个 .weak 来声明一下 hand_reset 段。
.option push .option pop 和 .option norelax 的意思是为了达成连接器松弛的目的,先将配置项存储到站中,再取用连接器松弛,然后指定 gp 的值,最后再恢复配置项。详细可以参考Assembly Manual

在这里插入图片描述
344行: 首先就是设置栈的底部,变量 _eusrstack 是在链接脚本中进行定义的。稍后分析链接脚本的时候再进行分析。

347~350行: 看注释是将 flash 中的数据转移到 RAM 中去。
_data_lma _data_vma 以及 _edata 都是在链接脚本中定义的,分别定义了 flash 中代码的位置,RAM中代码的位置,以及代码段的结束位置。lma(load memory address),也就是存储地址。vma(virtual memory address)也就是运行地址。
这4行代码的意思就是比较 a1 寄存器内容的位置是不是大于 a2 寄存器了,如果是的话就会跳转到357行去了。

352~356行:这里的代码就是当 a1寄存器中的值(也就是当前 vma的地址)小于 a2寄存器的时候才执行的。
352行:把 a1 读到 a0 中
354行:再把 a0 写到 a1 中,这样也完成了一个字大小的内容数据从 lma 到 vma 的转移。
355行:把 a0 寄存器里面存储的地址加上 4。
356行:再把 a1 寄存器里面存储的地址加上4.
357行:看看 a1 是不是还小于 a2 寄存器的值,如果是的话就再次执行352~356行的内容。

347~356 如果转换成 C 代码如下:

uint *a0 = _data_lma;
uint *a1 = _data_vma;
uint *a2 = _edata;

while(a1 <= a2){
    *a1 = *a0;
    a0 += 1;
    a1 += 1;
}

359~365 行:对 bss(Block Start by Symbol) 段进行初始化。
这里使用的办法和上面搬运的数据的汇编代码如出一辙。利用常 0 的 zero 寄存器的值将 bss 段的内容都写成 0。 其中 _sbss 和 _ebss 都是在链接脚本中声明的。

367~368行: [TODO] 在手册上没找到关于 0xBC0 的相关定义。

371~372行: 将 0x1f = 0001 1111 写入到 CRS 特殊状态寄存器中。
在这里插入图片描述
375~376行:
将 0x6088 = 0110 0000 1000 1000写入RISC-V 架构的 mstatus(machine statues)寄存器中。
在这里插入图片描述
378行: 将 _vector_base 的地址存入 t0 寄存器
379行: 地址对齐,将 t0 寄存器中内容与上 0x3 == 11
380行: 再将 中断向量 地址存储到 mtvec 寄存器中

mtvec(Machine Trap Vector) 它保存发生异常时处理器需要跳转到的地址

382行:跳转到 SystemInit 函数中,该函数在 system_ch32v30x.c 中进行实现,里面主要是对RCC(Reset and Clock Control )系统时钟进行配置。下面图片为 SystemInit 函数的具体内容展示,不要和启动的汇编代码搞混。

在这里插入图片描述
383行: 将 main 函数的地址保存到 t0 寄存器中
384行: 将t0 寄存器的值再次存储到 mepc 寄存器中

mepc(Machine Exception PC)它指向发生异常的指令。

385行: 使用 mret 指令使得 PC 指向 mepc 中,mepc里面正好是main函数的地址。这样就可以成功的跳转到 main 函数执行了。 mret 指令具体介绍如下。

在这里插入图片描述

简单总结

就是通过以上的这么几步就完成了实现启动,是不是很简单呢~ 并且可以进入 main 函数继续进行执行。
接下来就是分析链接脚本的内容了。哪里说的不明白欢迎各位大佬留言指正,共同学习进步!!!

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值