ARM裸机部分学习记录

xiaoxiao2021-02-28  116

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; //占空比为P 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这种方式,比起之前的启动,可以支持任何启动。
转载请注明原文地址: https://www.6miu.com/read-48184.html

最新回复(0)