ARM裸机部分学习记录

来源:互联网 发布:反美颜软件app 编辑:程序博客网 时间:2024/05/17 22:01
Author: sx                                                          E-mail:598726409@qq.com

资料整理于自己的笔记(有许多的图片和代码块没有添加,后面会慢慢补齐),也有些是博客之类 ,如有侵权,请及时联系我,我会及时删除。因为都是手码的,可能会有些错别字,理解有误的地方请联系我,以便及时修改
-----------------------------------------------------------------------------------------------------------
arm裸机部分:
-----------------------------装驱动部分---------------------------

secureCRT 驱动不用装,只要在设备管理器中看到一下接口就可以了。COM口是可以更改的,更改如图。



-----------------------------
//secrueCTR的设置

协议选择serial
端口选择你需要的端口。
波特率选择115200
VIP: 右边的RTS/CTS必须去掉。


-----------------------------
//fastboot
secureCRT监视串口时,倒数三秒按下回车
输入指令: fastboot
会有如下界面:->



这时间打开windows下CMD窗口:->
找到你存放fastboot的盘(在cmd下盘符是盘符加:eg->d:
dir命令显示目录->
cd进入fastboot目录(可以用tab自动补齐)输入fastboot的命令即可操作:
fastboot devices ->查看当前连接开发板的状态,如下则是正常链接状态。
fastboot reboot -> 重启

-----------------------------
//DNW
DNW安装需要驱动
这个驱动需要自己手动安装,安装步骤,->右击更新驱动程序,手动选择浏览即可。



-----------------------------
//手动点亮LED
step(1) :找到要操作的硬件分析原理
step(2) :查阅手册,按照手册编写代码
GPIO :通用输入输出口,可以编程控制它的工作模式,也可以编程控制他的电压高低。
汇编程序:
ldr :读入寄存器
sub: 将r2的中的数与立即数进行相减,相减完成后的数存放在r2中
cmp: 会进行比较,相等会eq就会=1,不等ne就会=1
mov pc, lr 程序返回TIPs
程序中要注意,写入寄存器的值之间的关系。
这样直接写就是 | 运算




像这样的如果用或运算就是错误的,这里要用与运算



-----------------------------
//反汇编objdump
汇编:assembly
反汇编:disassembly

-----------------------------
//SRAM和重定位
(1)看门狗wcatchdogtimer WCT
在某些特定的场合,程序运行有可能会死机跑飞,这是人有可能又不在边上,那么就需要一个东西去自动复位CPU,这就是看门狗定时器,看门狗定时器就是在某个特定的时间内,需要人为的去喂狗,它接收到喂狗信息以后就会把定时时间清零,就不会去复位。如果它到了一定的时间而我们没有采取喂狗措施,那么它就判断为程序跑飞,他就自动复位。
操作看门狗之类的其实都是操作相应的SFR
-----------------------------
//设置栈
//用汇编来设置栈,实现C语言调用小·
为什么要设置栈呢?
因为C语言运行是需要环境的,(C语言运行时 runtime),C语言中的局部变量都是存放在栈中的,在复位后,ARM的状态自动跳转到SVC,此时应设置一个栈,栈就是需要一段可用的内存,那么在启动阶段DRAM还没被初始化,所以只能用SRAM中的一段内存,S5PV210的手册中,划分了一块1.5k大小的内存,给SVC_STACK所以我们只需要将PC指针写入这快地址即可

-----------------------------
//调用C语言
调用是要加 BL 跳转,否则会产生错误
(tips)-> bad instruction 一般就是不复合汇编语言的规范
-nostdlib ->:编译时不使用系统默认的库函数
分析:为什么汇编可以直接调用C语言
答案就在这里,因为链接到了一起,所以可以直接调用。
C语言运行需要设置栈,所以要在汇编启动阶段设置好栈
这就是基本的启动流程:->

-----------------------------
//Cache
什么是Cache
Cache就是高速缓存,cache又分为2种
(1) iCache
(2)dCache
icache 就是指令cache dcache 就是数据cache
指令在运行是通常先存放在DDR/flash中,然后加载到DRAM中,当CPU,需要时,寄存器从DRAM中加载,但是DRAM虽然容量大而且便宜但是速度非常慢,所以当CPU需要指令时,直接从DRAM中读取的话就会拉低CPU的运行速度,这时就需要一个缓存cache,当CPU需要某些指令时,它会将他周围的指令都缓存进来,当CPU需要的时间,现在这里找,找到后就给寄存器,找不到的话(CPU又跳转到很远的指令肯定找不到)这是cache会自己进行 清楚缓存,重新缓存 ,然后等着下次操作。


利用上面的指令进行开/关icache
技巧:->汇编对某位#(1<<n)
清零: bic 置一:orr
-----------------------------
//重定位
位置无关码:PIC 就是运行地址和链接地址无关的代码
位置有关码:就是运行地址要和链接地址相同的代码


makefile中,给定链接地址就是上图中 -Ttext 这里给定的链接地址0x0,而在DNW中,代码运行的地址是0xd0020010所以这是链接地址和运行地址不同的
三星的启动过程:
先运行BL0,然后加载bootloader中的16k(BL1)到SRAM中运行,BL1(16k的代码会去加载BL2剩下的96-16K代码),BL2初始化DRAM,然后把OS加载到DRAM里,然后开始运行。
Uboot启动:
先运行iROM里的BL0,BL0加载Uboot中前16K(BL1)到SRAM中运行)->开始运行BL1(BL1会去初始化DDR然后把剩下的Uboot加载到DDR里运行,直到启动为止)
运行时地址由什么决定
运行时地址是由运行时的需求决定的比如0xd0020010
链接地址是由什么决定
链接地址是我们预先设定的地址,用-Ttext去指定的
重定位详解:
假设我们代码链接在0xd0024000,而CPU限定这段代码必须在0xd0020010处执行,这样就造成了逻辑地址和物理地址不匹配的问题,那么我们就需要重定位,在0xd0020010处执行一段重定位代码,然后通过这段代码把位置有关码复制到0xd0024000处去执行即可。
链接脚本:
adr:短加载
VVVVVIp:本句加载的是运行地址
ldr:长加载
VVVVVIp :本句加载的是链接地址
(1)加载各个地址
-> adr 加载_start的运行地址
-> ldr 加载_start的链接地址
-> ldr 加载.bss_start的地址用来结束重定位
判断是否相等。相等则不需要重定位
( 2 )不相等
-> 进行重定位,将.bss段上面的代码全部一次拷贝到链接地址,就是在iROM重新写了一份,然后让CPU跳转到这里执行。
(3)清除.bss段
-> ldr加载_bss_start地址 ldr加载_bss_end地址
依次赋值0,完成清除.bss
-----------------------------
//SDRAM引入
SDRAM:动态RAM其实就是内存,和SRAM不同,DRAM需要初始化才能用。
2^10=1K 2^20=1M 2^30=1G 8Bit = 1Byte
210的内存大小为512MB 分为两个部分
0x2000_0000:512MB

DRAM0:0x2000_0000~0x3000_0000
DRAM1:0x4000_0000~0x5000_0000
Memory_port1 -> DRAM0
Memory_port2 -> DRAM1







每个端口都有
XMn_ADDR(0:13)根地址总线
XMn_DATA (0:32)根数据总线 (级联而成)
对SDRAM的初始化代码我是一脸懵逼,反正也看不懂,就直接用别人的把。重定位大概理解。
-----------------------------
//S5PV210的时钟简介
时钟:
就是控制各个外设协同工作的指挥者
//S5PV210使用的工作方式是:
外部晶振 + 内部时钟发生器 + 内部PLL(其实就是倍频器) + 分频器
晶振联合内部始终发生器产生一个24M左右的时钟信号,通过PLL倍频,产生比较高的工作频率(800M~1.2G)HZ,然后各个外设通过分频器各自取所需的频率
//开关外设:
主要操作的就是开关时钟
//时钟域:
MSYS:
系统组件 CPU, DMC0,DMC1, iROM,iRAM,时钟频率很高
DSYS:
多媒体组件,视屏编解码HTMI硬件编解码,时钟频率较高
PSYS:
内部外设,串口速度,时钟速度慢







上图为系统时钟的接入关系图
分各个晶振接入->通过操控寄存器->选择PLL然后返回各个组件。
//时钟域
在ARM中各个外设是挂在AMBA总线上运行的,AMBA总线包括(1)AHB(2)APB对应的是HCLK_XXX和PCLK_XXX的时钟域
MSYS
ARMCLK 系统主频
HCLK_MSYS MSSYS的高频时钟域 DMC0/DMC1
PCLK_MSYS MSSYS的低频时钟域
HCLK_IMEM IROM、IRAM (合成IMEM)
• DSYS
HCLK_DYS 高频时钟域
PCLK_DYS 低频时钟域
PSYS
HCLK_PSYS 高频时钟域
PCLK_PSYS 低频时钟域 串口等
SLK_ONENAND
• 各个时钟域的默认值

• 时钟寄存器
nPLL_LOCK
锁频周期(PLL倍频原理是一个相位环,所以它需要时间->锁定时间)
nPLL_CON
控制PLL的输出频率(需要计算)
CLK_SRC(0~6)
设置时钟来源(就是图中的mux开关)
CLK_SRC_MASK
控制时钟的开关(来源
CLK_DIV(0~6)
设置分频比例(就是控制分频的大小)
CLK_GATE_SCLK
控制特殊时钟的门也就是开关
CLK_DIV_STAT CLK_MUX_STAT
查看分频器的状态 MUX开关的状态
-------设置时钟的步骤:
第1步:先选择不使用PLL。让外部24MHz原始时钟直接过去,绕过APLL那条路
第2步:设置锁定时间。默认值为0x0FFF,保险起见我们设置为0xFFFF
第3步:设置分频系统,决定由PLL出来的最高时钟如何分频得到各个分时钟
第4步:设置PLL,主要是设置PLL的倍频系统,决定由输入端24MHz的原始频率可以得到多大的输出频率。我们按照默认设置值设置输出为ARMCLK为1GHz
第5步:打开PLL。前面4步已经设置好了所有的开关和分频系数,本步骤打开PLL后PLL开始工作,锁定频率后输出,然后经过分频得到各个频率。
-----------------------------
//通信系统
通信的概念:
信息表示、信息解析、信息传输
• 同步通信和异步通信
同步通信:就是在同一个时钟信号激励下按照同样的节拍工作。
所以需要通信双方有一个CLK。

异步通信:就是在信号开始和信号结束时加上标志,然后让接收方去判断从而通信。
• 电平信号和差分信号
电平信号:电平信号就是有一个参考电平,然后根据信号与参考电平的电压差来判断信号。
差分信号:查分信号没有参考电平,是利用一对信号线互为差分,从而判断信号


总结:电平信号抗干扰能力差,如果有电压波动,就容易产生错误信息,而查分信号,有电压波动,不会产生影响,因为它的电压差是相对的。
• 并行接口和串行接口
并行接口:是在一个二进制位发送周期发送许多位二进制信号,所以需要很多的信号线。


串行接口就是利用一条线,发送信号。一个二进制位发送周期智能发送一位二进制信号。
• 串口通信信号
RS233电平:
逻辑1 : -3~-15V 逻辑0:3~15V
电压差很高,用来传出远距离通信(15米以内)
抗干扰能力强
TTL电平:
逻辑1: 0~5V 逻辑0:0~-5V
电压差较小,用来芯片间通信
波特率:
每秒传输的二进制位,一般都是固定的
9600 、 115200
通信过程:
开始位+数据位+奇偶校验位+结束位
开始位:标志通信单元开始
数据位:一般都是8位。因为串口通信输出都是一些文字,而文字的编码都是8位ASCII编码。
奇偶校验位:就是用来标记数据位是是奇数还是偶数的,有效防止信息反码(一种冗余措施)
结束位:标志通信单元的结束
//所以一般我们需要设置的就是
(1)波特率 (2)数据位(3)奇偶校验(4)结束位
开始位都是固定的所以不需要我们设置。
• 串口通信的基本原理
三根通信线:Tx传输线、Rx接收线、GND
过程如下:
发送方发送一个开始标志,然后接受方开始接收信号,因为是双工的所以有两根线一根传输线一根接收线,这时A的Tx相对于B就是Rx
传输是以信息流的方式传输的(一帧),单位时间为1/波特率(秒),在这个单位时间里传输一位二进制数据。
DB9
DB9接口是早期的通信规约,里面有9根心
其中只用三根,其余是用来做流控的。我们一般用串口做调试,所以不需要流控,所以它的其余6跟线全部都没接。


串口工作原理:
Peripheral BUS就是APB总线,这是串口工工作时钟的来源。
我们编写串口程序其实就是对
(1)串口收发缓冲区初始化,包括波特率发生器(中级的波特率发生器其实就是分频器,它需要一个源时钟,然后送给收发器。)
(2)对串口收发缓冲区进行读写操作。
• FIFO模式
为了解决CPU要不停来缓冲区操作的问题。类似于Cache但是比较小
• DMA模式
直接内存地址模式,其实就是CPU把要操作的数据全部存放到这个模式下的内存里,然后再慢慢操作缓冲区去运行
• IDrA模式
红外通信模式,红外通信是嫁接在串口上的通信。

其实红外模式也就是变相的通信线传输。





• 串口通信与中断的关系
发送方占据主导地位,所以一般不需要中断
接受方是必须需要中断操作的,因为接受方的CPU不可能时时刻刻盯着串口,它不知道你什么时间会来信息,所以需要中断程序。当receiver buffer满了以后会报告给CPU,然后CPU进行响应的操作。处理完了CPU继续回去做它的事情。
• 串口通信的时钟设计
由之前的学习可以知道,UART的时钟是接到APB总线的。而串口的时钟域是PSYS_
UART需要时钟的原因在于波特率发生器,串口需要它产生一个固定的波特率。波特率发生器其实也就是一个分频器。
波特率的计算也就是对相应分频系数的计算和写入寄存器。





bps就是波特率

这里表示了对波特率源时钟的设置有两个方向
(1)PCLK (2)SCLK_UART
波特率发生器有2个重要寄存器:UBRDIVn和UDIVSLOTn,其中UBRDIVn是主要的设置波特率的寄存器,UDIVSLOTn是用来辅助设置的,目的是为了校准波特率的。

• 串口通信实战
串口通信程序包含两个部分
(1)串口初始化程序 uart_init
(2)串口发送程序 uart_put
串口通信程序的主要步骤:
(1)初始化Tx和Rx的相关引脚
TxD0(GPA0_1)RXD0(GPA0_0)
对应寄存器GPA0CON(0xE020_0000)
(2)初始化这几个关键寄存器
UCON0 :中断/轮询模式
ULCON0 :正常模式,0奇偶校验 ,1位停止位,8bit数据位
UMCON0 :流控之类的
UFCON0 :FIFO控制寄存器
UBRDIV0 :计算波特率系数
UDIVSLOT0:波特率余数*16
总结:乱码是寄存器设置问题
• 按键和中断
按键属于输入类设备:即人向CPU发出信号
显示屏则是输出型设备:即CPU向我们发出信号
处理按键的两种方式:
(1)轮询方式:每隔一段时间来看一次是否触发按键
(2)中断方式:CPU事先设置好ISR,当按键按下时触发,CPU中断来处理按键。
为什么FIQb比IRQ快?
(1)FIQ模式有专用的寄存器R8~R12,当中断发生时,我们需要保存r0~r12的寄存器,以便恢复状态,而FIQ模式下有专用的寄存器r8~r12所以不需要保存状态。所以比较快
(2)FIQ模式位于异常向量表的末尾,我们知道在发生终端时候,终端会依据异常向量表进行跳转,但是位于异常向量表的末尾部分,我们可以直接把处理函数存放在末尾位置,少了这次跳转,所以更快些。
整个中断的流程梳理:
整个中断的工作分为2部分:
第一部分是我们为中断响应而做的预备工作:
1. 初始化中断控制器
2. 绑定写好的isr到中断控制器
3. 相应中断的所有条件使能
第二部分是当硬件产生中断后如何自动执行isr:
1. 第一步,经过异常向量表跳转入IRQ/FIQ的入口
2. 第二步,做中断现场保护(在start.S中),然后跳入isr_handler
3. 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR
寄存器中取isr来执行即可。
4. 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。
外部中断:
既然有外部中断,那么就有内部中断,所谓内部中断就是SOC的一些内部外设产生的中断。
那么当外部设备,当对应的外部设备的GPIO引脚产生的中断就是外部中断。
触发中断的五种模式
(1)高电平触发
(2)低电平触发
(3)上升沿触发
(4)下降沿触发
(5)双边沿触发
con寄存器     配置外部中断的模式
mask寄存器 使能外部中断
pend寄存器   挂起外部中断(当产生了外部中断时,这个寄存器将会挂起,就是自动置1,以待处理完中断,当处理完中断后,又会自动置零)

定时器、PWM、看门狗:
什么是定时器?
定时器其实就是硬件的一个外设,定时器其实是由计数器实现的,定时器内部有一个计数器,这个计数机根据一个时钟来工作,每隔一个时钟周期,就会计数一次,
定时器的时间=计数器计数值*时钟周期
原理:在定时器内部有一个寄存器,TCNT,我们在TCNT中存放一个计数值(比如300),那么每当一个定时周期(比如1ms),TCNT中的值就会减一,那么当TCNT中的值为0时,就会触发定时器中断,去通知CPU,处理响应的中断。
(1)PWM定时器
产生PWM信号的定时器
(2)WDT定时器
原理上和PWM差不多,只不过到了定时时间会产生一个复位信号复位CPU.
(3)系统定时器
依据时钟周期产生时间片,用来操作系统调度进程
(4)RTC实时时钟
是时间点,产生一个确切的时间。
PWM
210有5个定时器对应包含PWM功能(包含产生内部中断)
time 0、1、2、3对应包含产生PWM功能,time 0可以产生dead_zone、time 4不对外引出引脚(即无GPIO)
PCLK即为时钟源,包含两个预分频器
预分频器是一个32位的寄存器,但是每8位对应一个分频器,以及相应的系数,所以是0~255。但是默认是1,因为分频系数不能是0,否则会可能会烧坏CPU,所以需要加1,即为1~256

又图下可知,分频系数即为1~256
五个MUX多路选择器(其实就和分频器差不多)

TCNT
TCNT:定时计数器
TCNTB
TCNTB:定时计数缓存寄存器(我们操作的就是它)
TCNO
TCNTO:定时计数观察寄存器,就是用来观察当前计数到多少了
以此实现了自动重载(当定时到了以后自动从TCNTB中装载到TCNT继续下一轮计数)

TCMPB
用来确定占空比
占空比 = TCMP / TCNTB
TCMP会和TCNT进行比较
如果TCMP中的值比TCNT中的大(小)那么会进行一次电平翻转
死区生成器
为了克服PWM波形同时是一个电平,防止电路被烧毁(整流、)
//实验代码 流程如下:
(1)查阅用户手册,找到蜂鸣器对应的GPIO引脚,设置为 TOUT_2模式,即使用定时器2驱动模式
(2)设置预分频器(注意:预分频器中分频系数默认+1,原因是因为不允许分频系数为0,会导致时钟频率无限大,有及其大的危害,所以默认➕1,比如APB总线送来的时钟源为66M,我们需要预分频成10M那么对应的应该写 65)-------->TCFG0
(3)设置MUX,根据数据手册设置即可--------->TCFG1
(4)设置CON寄存器,我们用的是TIME2,
1.先开启自动装载
2.根据占空比决定是否要这只输出翻转
3.计算需要的频率,写入TCNTB2,TCMP2
4.开启TIME2

/*
*PWM驱动蜂鸣器
*GPD0CON 0xE02000A0 (11:8) 模式:TOUT_2 0010
*TCFG0 0xE2500000 (15:8) 预分频器我们写个66进去
*TCFG1 0xE2500004 (11:8) 分频器我们设置0000
*TCON 0xE2500008
*TCNTB2 0xE2500024
*TCMPB2 0xE2500028
*/

#define GPD0CON (0xE02000A0)
#define TCFG0 (0xE2500000)
#define TCFG1 (0xE2500004)
#define TCON (0xE2500008)
#define TCNTB2 (0xE2500024)
#define TCMPB2 (0xE2500028)


#define rGPD0CON (*(volatile unsigned int *)GPD0CON)
#define rTCFG0 (*(volatile unsigned int *)TCFG0)
#define rTCFG1 (*(volatile unsigned int *)TCFG1)
#define rCON (*(volatile unsigned int *)TCON )
#define rTCNTB2 (*(volatile unsigned int *)TCNTB2)
#define rTCMPB2 (*(volatile unsigned int *)TCMPB2)


void pwm_init(void)
{
rGPD0CON &= ~(0xf << 8); //清零GPDO_2
rGPD0CON |= (0x2 << 8); //写入TOUT_2模式

rTCFG0 &= ~(0xff << 8); //清零预分频器
rTCFG0 |= (65 << 8); //分频一个66

rTCFG1 &= ~(0xf << 8); //MUX
//rTCFG1 |= (1 << 8);

//开启自动装载
rCON |= (1 << 15);
rTCNTB2 = 50; //设定PWM的时间 我们设置2khz 0.5ms
rTCMPB2 = 25; //占空比为%50

rCON |= (1 << 13); //开一下手动装载 //这里顺序不能相反,如果反了就是会手动载入,会导致PWM波形不对
rCON &= ~(1 << 13); //硬件自动装载好了,关闭掉

rCON |= (1 << 12); //开启定时器2 最后,开启
}
WDCT(看门狗定时器)
在某些特定的场合,我们的CPU运行可能会遇到一些突发情况,导致CPU卡死不工作,这时解决问题的办法就是重启系统,但是由于人工干预的响应速度很慢可能会导致意想不到的后果,前辈们就采取了看门狗定时器的这个策略:
看门狗定时器即设定了某个时间,在这个时间到来之后会产生一个中断信号或者复位信号,那么系统正常运行时是不可以随便复位的,所以就需要在程序中指定某个线程专门在一个安全时间内去喂狗(即让WDT计时时间复位),就实现了当程序跑飞时自动复位的功能。
原理如图所示:
时钟源从PCLK_PSYS引入,先预分频器,接着MUX开关,WTDAT,就是计数值,刷入WTCNT进行计数,WTCON[2][0]控制选择是否产生中断、复位信号.
WTCON:如上图,按位配置
WTDAT、WTCNT,决定了定时周期长度
代码如下
// 初始化WDT使之可以产生中断
void wdt_init_reset(void)
{
 // 第一步,设置好预分频器和分频器,得到时钟周期是128us
 rWTCON &= ~(0xff<<8);
 rWTCON |= (65<<8); // 1MHz
 
 rWTCON &= ~(3<<3);
 rWTCON |= (3<<3); // 1/128 MHz, T = 128us
 
 // 第二步,设置中断和复位信号的使能或禁止
 rWTCON &= ~(1<<2); // disable wdt interrupt
 rWTCON |= (1<<0); // enable wdt reset
 
 // 第三步,设置定时时间
 // WDT定时计数个数,最终定时时间为这里的值×时钟周期
 rWTDAT = 10000; // 定时1.28s
 rWTCNT = 10000; // 定时1.28s
 
 // 其实WTDAT中的值不会自动刷到WTCNT中去,如果不显式设置WTCON中的值,它的值就是
 // 默认值,然后以这个默认值开始计数,所以这个时间比较久。如果我们自己显式的
 // 设置了WTCNT和WTDAT一样的值,则第一次的定时值就和后面的一样了。
 //rWTDAT = 1000; // 定时0.128s
 //rWTCNT = 1000; // 定时0.128s
 
 // 第四步,先把所有寄存器都设置好之后,再去开看门狗
 rWTCON |= (1<<5); // enable wdt
}

注意BCD转码
#include "main.h"

#define RTC_BASE (0xE2800000)
#define rINTP (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define rRTCCON (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define rTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define rRTCALM (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define rALMSEC (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define rALMMIN (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define rALMHOUR (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define rALMDATE (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define rALMMON (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define rALMYEAR (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define rRTCRST (*((volatile unsigned long *)(RTC_BASE + 0x6c)))
#define rBCDSEC (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define rBCDMIN (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define rBCDHOUR (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define rBCDDATE (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define rBCDDAY (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define rBCDMON (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define rBCDYEAR (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define rCURTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define rRTCLVD (*((volatile unsigned long *)(RTC_BASE + 0x94)))


// 函数功能:把十进制num转成bcd码,譬如把56转成0x56
static unsigned int num_2_bcd(unsigned int num)
{
// 第一步,把56拆分成5和6
// 第二步,把5和6组合成0x56
return (((num / 10)<<4) | (num % 10));
}

// 函数功能:把bcd码bcd转成十进制,譬如把0x56转成56
static unsigned int bcd_2_num(unsigned int bcd)
{
// 第一步,把0x56拆分成5和6
// 第二步,把5和6组合成56
return (((bcd & 0xf0)>>4)*10 + (bcd & (0x0f)));
}


void rtc_set_time(const struct rtc_time *p)
{
// 第一步,打开RTC读写开关
rRTCCON |= (1<<0);

// 第二步,写RTC时间寄存器
rBCDYEAR = num_2_bcd(p->year - 2000);
rBCDMON = num_2_bcd(p->month);
rBCDDATE = num_2_bcd(p->date);
rBCDHOUR = num_2_bcd(p->hour);
rBCDMIN = num_2_bcd(p->minute);
rBCDSEC = num_2_bcd(p->second);
rBCDDAY = num_2_bcd(p->day);

// 最后一步,关上RTC的读写开关
rRTCCON &= ~(1<<0);
}

void rtc_get_time(struct rtc_time *p)
{
// 第一步,打开RTC读写开关
rRTCCON |= (1<<0);

// 第二步,读RTC时间寄存器
p->year = bcd_2_num(rBCDYEAR) + 2000;
p->month = bcd_2_num(rBCDMON);
p->date = bcd_2_num(rBCDDATE);
p->hour = bcd_2_num(rBCDHOUR);
p->minute = bcd_2_num(rBCDMIN);
p->second = bcd_2_num(rBCDSEC);
p->day = bcd_2_num(rBCDDAY);

// 最后一步,关上RTC的读写开关
rRTCCON &= ~(1<<0);
}

void rtc_set_alarm(void)
{
rALMSEC = num_2_bcd(23);
rRTCALM |= 1<<0;
rRTCALM |= 1<<6;
}


//中断
void isr_rtc_alarm(void)
{
static int i = 0;
printf("rtc alarm, i = %d...", i++);

rINTP |= (1<<1);
intc_clearvectaddr();
}
存储设备SD卡
(1)RAM
随机存储器,可以随机访问内存,掉电丢失,即内存
(2)ROM
只读存储器,掉电不丢失,类似于FLASH
(3)Nand类 Nandflash/Norflash
此类存储设备是早期的,所以没有一个标准,没有坏块管理机制
(4)SD卡类 SD卡、MMC卡、MicroSD、TF卡
这些卡其实内部就是Flash存储颗粒,比直接的Nand芯片多了统一的外部封装和接口。
卡都有统一的标准,譬如SD卡都是遵照SD规范来发布的。这些规范规定了SD卡的读写速度、读写接口时序、读写命令集、卡大小尺寸、引脚个数及定义。这样做的好处就是不同厂家的SD卡可以通用。
(5)flash   iNand、MoviNand、eSSD
近几年的流行态势,用于手机之类的存储,封装成了芯片的模式,内部包含了Flash坏道管理模块
(6)ssd(其实也是flash)
固态硬盘
SD卡 Secure Digital Memory Card  有读写保护,就是那个比较大的
TF卡其实就是SD卡
Micro SD才是平时手机里赛的那个SD卡,没有读写保护
SPI协议
SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。
SD协议
SD协议速度更快
steepingStrone(启动基石)
在以前的启动技术中只有Norfilash可以作为启动介质(内部总线式访问),后来产生了一种技术,可以使得Nandflash也可以做为启动介质,就是在SOC内置一块iROM,然后在启动阶段从Nandflash中加载启动代码的一部分到IROM中运行。(这种技术被称为SteepingStrone)
Nand:
Nor:常用于系统内部使用,可以直接加载运行,不需要I/O接口读写,所以不需初始化
块和扇区
一个扇区就是存储设备的一个基本单位,一般规定是512字节
一个块就是一些大于1的字节集合,也是一个基本单位
为什么可以从SD卡启动?
(1)因为采用了启动基石的技术
那么启动基石技术是怎么从SD卡读入MEM的?
在SOC内置了一些功能函数(Device Copy Function
功能函数图如下:
SD卡启动程序流程
(1)制作Makfile 利用一个统一的Makefile去makeBL1和BL2BL2
(2)烧录脚本的编写,注意路径和扇区(第0个扇区是被隔离的BL1是烧写到第1个扇区的,BL1的大小是16k即32个扇区,由于安全起见,我们把BL2烧写到45个扇区,给一些隔离)
BL1
BL1的作用就是初始化DDR,然后将BL2拷贝到DDR中(devicec copyFunction)
BL2
加载代码到DDR中0x23E00000位置运行
ubootSD卡启动过程
基本过程:
将BL1先烧写到SD卡的SEEK1中,然后将剩下的烧写到SD卡的SEEK45往后,启动时,先从SD卡中加载16K的BL1到SRAM中运行(BL1会初始化DDR,然后拷贝剩下的所有的代码到DDR中),这时就DDR就可以运行程序了,使用一个跳转从SRAM中跳转到DDR中运行剩下的程序,即可完成启动。
Uboot这种方式,比起之前的启动,可以支持任何启动。




















原创粉丝点击