ARM
:就是个处理器。
系统移植
:就是给硬件装系统,为了能适合板子给系统做适配
驱动开发
:向上给用户提供接口,向下驱动硬件
操作系统的作用
:向下管理硬件、向上提供接口(API)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0yfOm5gi-1681907855923)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZstG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
通过高低电平的方式代表二进制1和0,以这种方式进行数据的存储、运算和传输。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HU9eEGL0-1681907855924)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6fu5nrO-1681907855924)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252CtextZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
其它总线都要经过CPU,而DMA总线不用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rwBDKxCY-1681907855925)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmN46547tzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
高端的CPU常常有三级存储结构。
辅助存储器
:如,硬盘。
优点:容量大,价格便宜,断电数据不易丢失。
缺点:读写速度慢,按块(比如一次读取512字节)读取浪费时间,有的程序只要读几个字节即可
主存储器
:如,内存
优点:读写速度比硬盘快,能按字节读取
缺点:容量小,价格贵,数据断电丢失
Cache
:如,高速缓存
优点:读写速度最快
缺点:容量最小,价格最贵
因此,为了兼顾速度、容量和价格,一般电脑配置的都是三级存储结构。
三级存储结构的工作原理:
由于数据在硬盘中断电后不易丢失,因此数据一般存放在硬盘中。
当运行如QQ时,系统将硬盘中QQ的数据发送给内存,再有CPU进行处理。
对于常用的数据,一般存放在高速缓存中,这样CPU对它读写最快。
CPU只能访问高速缓存和内存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElzdtlIN-1681907855925)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6zZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
如下图:地址总线宽度为2位,即2根导线,当CPU要访问D这个字符时,它先通过地址总线发送11两个高电平给内存,高速内存要访问的地址,内存再将0x03地址中的字符D通过数据总线发送给CPU。但如果CPU要访问E时,由于E的地址0x04二进制是100,超过了地址总线的宽度,因此无法访问。
所以,地址总线的宽度决定了一个CPU能访问的存储空间,这个空间叫做寻址空间。
N位地址总线的CPU的寻址空间是2的N次方,如,32位系统的寻址空间时2^32=4G。
这个32位系统的系统,就是我们常说的32位Windows,64位Windows。当然操作系统不只是Windows。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHNgLsa8-1681907855926)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W78bjbIp-1681907855926)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L457650NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
简述为什么地址总线为32bit的处理器的地址空间为4G
比如地址总线为2位,其所有能表达的二进制为00、01、10、11,共2的N(位数)次方个组合,其寻址空间为2的N次方个字节(B),因此32bit的处理器的寻址空间是2的32次方B等于4G。
简述CPU执行指令的过程
取指:CPU寄存器发送地址给内存,内存找到对应地址,将指令符号发送给CPU的指令寄存器;
译码:指令寄存器将指令符号发送给译码器,译码器对指令符号进行识别,将指令符号转换成运算指令;
执行:译码器将运算指令发送给运算器中的寄存器,由其执行运算指令。运算结果写入寄存器。
CPU寄存器自动将下一条指令的地址发送给内存。
如此往复。
RM公司前身是Acorn的一个部门,专门研发Acorn公司的处理器。
RISC:精简指令集处理器
经典产品:ARM7(开始火起来)、ARM9、ARM11
从2004年起ARM命名不用数字,开始用Cortex-R\A\M系列命名,针对市场上不同需求的CPU
A系列:application,用于大型系统
R系列:real time(实时),针对军工类要求实时性较高的需求
M系列:MCU,低功耗低价格高效率,用来取代单片机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m7YgvYaC-1681907855926)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
RISC精简指令集处理器
:
随着运算需求日益增加,CPU内部的相应设计的运算电路也越来越多。精简指令集处理器就是保留那些最常用的运算电路而做成的CPU。
缺点:复杂的运算指令通过简单运算指令的组合来实现,因此要耗费更多的时间。
优点:RISC处理器在功耗、体积、价格方面有很大的优势
其它特点:指令长度(一个指令所占空间大小)固定;多为单周期指令(一个周期就能执行完)
应用场景:广泛运用于嵌入式移动端领域。
CISC复杂指令集处理器
:
不仅包含常用指令电路,还包括特殊指令电路。
优点:性能好
缺点:价格贵,体积大,功耗大
其它特点:指令长度和周期都不固定
应用场景:PC、服务器等领域
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ya3tDpfk-1681907855927)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTK4gP8n-1681907855927)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252CtenLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
指令
:CPU认识并能执行的指令,就是CPU有什么运算的电路才能做什么样的运算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvHpFjYA-1681907855927)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4u4787bmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
32位->4字节,16位->2字节
开发主要用ARM指令集
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-guBD3VCx-1681907855928)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2365436350NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
机器码:CPU能识别的1、0二进制码,转化为高低电平。但是难理解、由于不同处理器设计的运算电路不同,因此机器码不可移植。
汇编语言:是机器码的符号化。与机器码一一对应,因此也不可移植。
C语言:高级语言。既解决了机器码难理解的缺点,也解决了其不可移植的缺点。可以用不同的编译器将C语言转化成汇编语言,再转化成机器语言给不同的处理器运算。
C语言的编译原理:
编译:将C源码转化成汇编语言
汇编:将汇编语言转化成机器码
链接:和系统组件(比如标准库、动态链接库等)结合起来变成可执行文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEiFvi8H-1681907855928)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9345643643nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
ARM是如何存储指令的
什么是32位架构
:就是一次性能算的最大的数据是32位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzjNYHKt-1681907855928)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ub3e55mV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
字节序
:如果一个数据占多个字节,其在内存的分布情况。因此不用考虑像char这种只占1个字节的数据。
如下图
大端对齐
:
0x12345678, 1位最高位,8位最低位。
低地址N存放0x12高位,高地址存放低位,为大端对齐
小端对齐
:
低地址存放低位,高地址存放高位,为小端对齐
测试一下乌班图是大端对齐还是小端对齐
*pa输出的是首地址。因此乌班图是小端对齐。
PC的值必须是4的整数倍,换算成二进制的话,最后末尾两位一定是0,因此处理器处于ARM状态时,PC值的【1:0】两位是没有定义的,如果这两位不是0的话,那么强制转化为0。上图的右边数字是16进制。
ARM有8个基本的工作模式
User 非特权模式,一般在执行上层的应用程序时ARM处于该模式
FIQ 当一个高优先级中断产生后ARM将进入这种模式
IRQ 当一个低优先级中断产生后ARM将进入这种模式
SVC 当复位或执行软中断指令后ARM将进入这种模式
Abort 当产生存取异常时ARM将进入这种模式
Undef 当执行未定义的指令时ARM将进入这种模式
System 使用和User模式相同寄存器集的特权模式
Monitor 为了安全而扩展出的用于执行安全监控代码的模式
工作模式的理解
不同模式拥有不同权限
不同模式执行不同代码
不同模式完成不同的功能
ARM工作模式分类
按照权限
User为非特权模式(权限较低),其余模式均为特权模式(权限较高)
按照状态
FIQ、IRQ、SVC、Abort、Undef属于异常模式,即当处理器遇到异常后
会进入对应的模式
没有地址的意思是,比如数据变量a,是不可以取地址的
带三角的代表和普通的(通用的)不一样,是独属于某一种模式下的。
ARM一共有40个寄存器。
专用寄存器
R15就是PC,指针寄存器
PC存放指令的地址,比如加法的地址,在内存之中找到相应的指令之后,返回给指令寄存器
链接寄存器
R14 LR,比如第一百行的代码要跳到140行的另一个函数执行,等这个函数执行完了得回到101行,所以这个链接寄存器就是储存这个101行的地址的。
栈指针
,像是局部变量这些就是在栈里面的。存一个数据,栈指针移动一次,指向下一个空的空间。
CPSR寄存器
FIQ和IRQ是由外部硬件产生的,前者的相应速度比后者的响应速度更快
Software lnterrupt 软中断,对比硬(件)中断
七个异常源,五个异常模式、
CPSR(当前程序状态寄存器)
CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。
SPSR:程序状态保存寄存器(saved programstatus register),每一种处理器模式下都有一个状态寄存器SPSR, SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。原文链接:https://blog.csdn.net/LinuxWorking/article/details/78484185
这次保存的目的是便于还原之前的模式
在ARM处理器中使用 R14实现对断点和调用点的记录,即使用R14用作****返回连接寄存器*(LR)。在硬件上和指令执行上,CPU 自动完成相应返回点的记录。在ARM 汇编语言程序设计时,R14和LR通用。
ARM处理器相应异常时,会自动完成将当前的PC保存到LR寄存器。
ARM处理器执行子程序调用指令(BL )时,会自动完成将当前的PC的值减去4的结果数据保存到*LR寄存器*。即将调用指令的下紧邻指令的地址保存到LR。ARM处理器针对不同的模式,共有6个链接寄存器资源(LR* ),其中用户模式和系统模式共用一个 LR,每种异常模式都有各自专用的R14 寄存器(LR )。这些链接寄存器分别为 R14、R14_svc、R14_abt、R14_und、R14_irq、R14_fiq,
解释一下这个流程图吧。
首先呢,把当前的状态保存下来,保存到spsr。
之后改变状态,也就是改变cpsr(当前状态寄存器)。有三个地方都需要改变,1. 可能是user模式(用户程序模式)改变到irq模式;2. 因为有了irq异常了,所以需要禁止IRQ了,处理IRQ的时候不能再处理IRQ了;3. 还有还要改到ARM状态。这些先不细说了。
之后呢,把这个状态保存下来。。。M是记录下来的下一个指令的位置,以便于之后能够返回来执行程序。异常向量表不会保存异常处理程序(空间不够,除了最后一个异常,FIQ异常可以直接在后面写),只会记录异常处理程序的地址。等异常处理程序结束之后,运行模式恢复代码,状态返回到spsr,pc指向lr,IRQ模式也切换回用户模式了。这些都是之前保存的。回到M之后,然后正常地往下面运行。
你看b N 上面那一个,还有多余空间,可以储存异常处理程序,但是下面的肯定不可以,因为每一个都只有4个字节的空间。
那为什么有自己的寄存器就更快了呢?因为假如是通用的,那么当寄存器的使用者发生改变的时候(比如用户模式转到IRQ模式了,但是IRQ模式下的R0到R12寄存器都是通用寄存器,也就是说和在用户模式下用的寄存器肯定是一样的),它是需要压栈的,先把寄存器里面的内容先临时保存到栈里面,否则会被覆盖掉。(也就是你去租了一辆车,你需要把前一个车主的东西拿出来放好保存,然后自己开着车出去)。如果是FIQ,那么它可以用专用寄存器,只能自己用,自然就不需要压栈了。
到了后面,每一秒都有人干活,而且是每一秒三个人都在干活,每个人一直在干一样的事情,专人干专活。
大概分为三步,分别是取指(PC和IR),解码,执行。也就是三级,后面说的5级8级 ,也是在三级的三个部分的内部再进行细分。
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
MOV R1, #1
MOV R2, #2
.end @汇编程序的结束
点击D,开始仿真界面
反汇编
:上面这些
8位16进制换成二进制也就是32位(32为机器码,对于ARM来说,所有的指令编译完都是32位),第一列是存储地址,比如0x00000004。每一次增加4,因为上一条代码是32位,4个字节。
此时代码执行到6(还没执行完),点击左上角,箭头指向下一步
R1变成0x00000001了,一开始是0x00000000。这是因为执行了代码 MOV R1, #1
此时的PC也改变了
可以人为更改 PC的值,但是如果不是4的整数倍的话,会被强制将后两位变成00(二进制),前面有讲过
再点击d,关闭仿真界面
@ *****************************************************************
@ 汇编中的符号
@ 1.指令: 能够编译生成一条32位的机器码,且能被CPU识别和执行
@ 2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
@ 3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译(比如 #if #else if #else)
@ ARM指令
@ 1.数据处理指令: 数学运算、逻辑运算
@ 2.跳转指令: 实现程序的跳转,本质就是修改了PC寄存器
@ 3.Load/Srore指令: 访问(读写)内存
@ 4.状态寄存器传送指令:访问(读写)CPSR寄存器
@ 5.软中断指令: 触发软中断异常
@ 6.协处理器指令: 操控协处理器的指令(比如运算浮点型数据可以用协处理器)
@ *****************************************************************
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
数学运算、逻辑运算
@ 1.指令:能够编译生成一条32位的机器码,且能被CPU识别和执行
@ 1.1 数据处理指令:数学运算、逻辑运算
@ 数据搬移指令
@ MOV R1, #1
@ 也就是R1 = 1
@ MOV R2, R1
@ 也就是R2 = R1
@PC如果不人为更改,每次自增4
@ MVN R0, #0xFF
@ 因为ARM寄存器是32位的,所以0xFF实际上就是0x000000FF,而这个MVN指令会先取反,也就是0xFFFFFFF00,然后再把值放进去
@ R0 = ~0xFF
@ ~取反运算符 ,是对数值的 二进制位 进行取反
@ 立即数
@ 立即数的本质就是包含在指令当中的数,属于指令的一部分
@ 立即数的优点:取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快
@ 立即数的缺点:不能是任意的32位的数字,有局限性
@ MOV R0, #0x12345678
@ 上面这个会报错,因为ARM的一个指令总共是32位,而指令里面的数字比如0x12345678会被包含到指令中去,比如下面的这个指令到指令机器码里面就是后两位为12,所以0x12345678太大了,放不下。但是有的指令是可以放下的,但是这个MOV不可以
@ MOV R0, #0x12
@ 编译器替换
@ MOV R0, #0xFFFFFFFF
@ 这个会被自动替换成MVN R0, #0x00000000,效果是一样的,这样的话就可以放得下了。此处不报错,上面那个#0x12345678报错是因为即使取反也是1110 1101 1100 1011 1010 1001 1000 0111,还是放不下
@ 数据运算指令基本格式
@ 《操作码》《目标寄存器》《第一操作寄存器》《第二操作数》
@ 操作码 指示执行哪种运算
@ 目标寄存器: 存储运算结果
@ 第一操作寄存器:第一个参与运算的数据(只能是寄存器)
@ 第二操作数: 第二个参与运算的数据(可以是寄存器或立即数)
@ 加法指令
@ MOV R2, #5
@ MOV R3, #3
@ ADD R1, R2, R3
@ 也就是R1 = R2 + R3
@ ADD R1, R2, #5
@ 也就是R1 = R2 + 5
@ 减法指令
@ SUB R1, R2, R3
@ 也就是R1 = R2 - R3
@ SUB R1, R2, #3
@ 也就是R1 = R2 - 3
@ 逆向减法指令
@ RSB R1, R2, #3
@ 也就是R1 = 3 - R2
@ 乘法指令
@ MUL R1, R2, R3
@ 也就是R1 = R2 * R3
@ 乘法指令只能是两个寄存器相乘
@ 按位与指令
@ AND R1, R2, R3
@ 也就是R1 = R2 & R3
@ 按位或指令
@ ORR R1, R2, R3
@ 也就是R1 = R2 | R3
@ 按位异或指令
@ EOR R1, R2, R3
@ 也就是R1 = R2 ^ R3
@ 左移指令
@ LSL R1, R2, R3
@ 也就是R1 = (R2 << R3)
@ 右移指令
@ LSR R1, R2, R3
@ 也就是R1 = (R2 >> R3)
@ 位清零指令
@ MOV R2, #0xFF
@ BIC R1, R2, #0x0F
@ 第二操作数(0x0F)中的哪一位为1,就将第一操作寄存器(R2)的中哪一位清零,然后将结果写入目标寄存器。但是R2不会改变,只是借了个数据参与运算赋值给R1
@ 格式扩展
@ MOV R2, #3
@ MOV R1, R2, LSL #1
@ 也就是R1 = (R2 << 1)
@ 数据运算指令对条件位(N、Z、C、V)的影响
@ 默认情况下数据运算不会对条件位产生影响,在指令后加后缀”S“才可以影响
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x00000001
@ MOV R3, #0x00000005
@ MOV R4, #0x00000002
@ ADDS R5, R1, R3
@ 这样子一加,32位存不下了,产生进位,C位会发生改变
@ ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + 'C'
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
@ MOV R1, #0x00000001
@ MOV R2, #0x00000002
@ MOV R3, #0x00000005
@ MOV R4, #0x00000001
@ SUBS R5, R1, R3
@ SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - '!C'
@ 有个问题,为啥ARM的CPU需要有四个条件代码位呢,最下面代码块外面详解
有个问题,为啥ARM的CPU需要有四个条件代码位呢?
首先解释32位是单次运算(一条指令)数据的能力,32的处理器寄存器都是32位,那么ADD这个汇编指令(加法)可以把两个寄存器(比如R2,R3)的数据放在处理器运算然后放在另外一个寄存器(比如R1)。可以给32位的数据做运算,然后得到的结果也是32位,这就是32位处理器。如果给一个64位的数,是不能算的,比如算一个64位数据的加法,寄存器是根本存不下64位的数。如果遇到64位的数,可以拆开来算,分成两次算,所以即使它能算,但是需要分成两次。所谓的32位是指单次运算的能力,所以64位CPU计算更快。
如果给一个64位的数,是不能算的,比如算一个64位数据的加法,寄存器是根本存不下64位的数。如果遇到64位的数,可以拆开来算
下面解释为什么要有4个条件代码位
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x00000001
@ MOV R3, #0x00000005
@ MOV R4, #0x00000002
@ ADDS R5, R1, R3
@ 这样子一加,32位存不下了,产生进位,C位会发生改变
@ 如果高低位都用ADDS来加的话,CPU不会知道要发生进位给高位加1了
@ 但是如果用ADC的话,那么CPU它会看C位的状况,如果发生改变了就加上1。所以高位加法用ADC
@ 所以为什么CPU的CPSR寄存器要有四个条件代码位呢?因为在运算的时候有帮助
@ ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + 'C'
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
@ MOV R1, #0x00000001
@ MOV R2, #0x00000002
@ MOV R3, #0x00000005
@ MOV R4, #0x00000001
@ SUBS R5, R1, R3
@ SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - '!C'
如果你用的C语言这些高级语言,要不要拆开来算,数据是64位的还是32位的,这些编译器都会自动给你处理。
我们在keil里面写一个C语言吧,看编译器把它编译成了什么东西
如果我们把数据改成long long 则编译会不一样,简单看加法就多出个ADC
int - 4个字节32bits
long long - target type will have width of at least 64 bits(8个字节,即64位,超出了32位)
指令条数都更多了,运行肯定更慢了
LSR 右移一位就是除2
a/2这个除法写成a>>=1的效率会提高,因为CPU可以直接识别,可能指令会更少
能用整形数据就别用浮点型,因为ARM不能直接算浮点型(编译成一大堆代码或者用协处理器)
确实指令更少了
跳转指令:实现程序的跳转,本质就是修改了PC寄存器
方式一: 直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
@ 1.2 跳转指令:实现程序的跳转,本质就是修改了PC寄存器
@ 方式一:直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV PC, #0x18
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
方式二:不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号(B)下指令的地址
B 就是跳转
@ 方式二:不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ B FUNC 不带返回的跳转指令
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
方式三:带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器
@ 方式三:带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ BL FUNC
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ MOV PC, LR
@ 程序返回
看一下C程序的编译结果吧
跳转到function用的是BL
然后看一下function结束之后用的是什么跳转回去
用的是LR,之前BL记录了LR,LR也就是图中的R14,BX是跳转指令啊,比上面的MOV PC 好像多了些功能
@ 比较指令CMP
@ CMP指令的本质就是一条减法指令(SUBS),只是没有将运算结果存入目标寄存器,因为结果是布尔型,没必要单独弄一个寄存器存
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ BEQ FUNC
@ 执行逻辑:if(EQ){B FUNC} 本质:if(Z==1){B FUNC} @如果是等于
@ BNE FUNC
@ 执行逻辑:if(NQ){B FUNC} 本质:if(Z==0){B FUNC} @如果是不等于
@ MOV R3, #3
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
为什么CMP可以判断关系,本质是减法,然后根据NZCV做判断
这个又体现出NZCV的作用了
B是跳转符,EQ是条件码助记符后缀
比如BEQ只有满足了EQ才会执行B
比如下面的MOVGT,比如满足GT才会执行MOV
C语言的条件判断就是类似的机制
@ ARM指令的条件码
@ ARM指令集中大多数指令都可以带条件码后缀
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ MOVGT R3, #3
@ 练习:用汇编语言实现以下逻辑
@ int R1 = 9;
@ int R2 = 15;
@ START:
@ if(R1 == R2)
@ {
@ STOP();
@ }
@ else if(R1 > R2)
@ {
@ R1 = R1 - R2;
@ goto START;
@ }
@ else
@ {
@ R2 = R2 - R1;
@ goto START;
@ }
@ 练习答案
@ MOV R1, #9
@ MOV R2, #15
@ START:
@ CMP R1,R2
@ BEQ STOP
@ SUBGT R1, R1, R2
@ SUBLT R2, R2, R1
@ B START
@ STOP:
@ B STOP
下面我们来看一下C代码吧
看一下If对应的汇编语句可以看到用了CMP,然后和3去比较,a储存在R3寄存器里面
下一条语句,func()可以看到是BLEQ,要是EQ满足了,那么就执行BL,进行跳转(用了L之后还要返回)
Load/Srore指令:访问(读写)内存
代码中的数据往往是储存在内存的,计算的时候需要取到寄存器里面去。我也不知道下面这个为什么是从R3拿数据,让人感觉R3是一个内存样的,明明是一个寄存器。
后面懂了,R3储存的是一个地址,并不是内存空间本身,这个地址指向的是内存空间
我们看一下这个C代码是怎么编译的,a++,可以在汇编里面看到ADD是R2在用,所以a在R2里面,再看下前面的行,用LDR把R3指向的内存空间里面的数据放在R2里面了(也就是a),ADD 算完了之后再用STR把R2寄存器里面的数据放到R3里面去。
这里的R3用到了基址加变址寻址,后面会讲,第一行是把PC+0x0010指向的地址空间的数写到R3里面。我也不知道里面为啥是1,不重要。
@ 1.3 Load(LD……)/Srore(ST……)指令:访问(读写)内存
@ 写内存
@ MOV R1, #0xFF000000
@ MOV R2, #0x40000000
@ STR R1, [R2]
@ 将R1寄存器中的数据(0xFF000000)写入到R2指向的内存空间(0x40000000,这个地址也不是随便写的,可以在memory map(内存映射表)里面看地址的属性)
可以自己去查地址里面的东西,比如0x40000000
上面的代码执行完之后,可以看到地址内存发生了改变
插一句,这里还可以思考一个问题,就是0x40000000到0x40000001这两个数之间,对应着一个字节的空间,可以储存两个16进制位。
而且这里还有一个小端对齐的知识点(ARM都是小端对齐)
我们再验证一个知识,就是4个字节的话起始地址就要是4的整数倍
(数据是几个字节的,储存地址就得是几的整数倍),如果我们把起始地址改了,看下结果
虽然地址改成0x40000001了,但是还是从0x40000000开始储存,因为直接把后面两位变成0了。
下面看看读内存
@ 读内存
@ LDR R3, [R2]
@ 将R2指向的内存空间中的数据读取到R3寄存器
char一个字节,short两个字节,int四个字节
上面我们的例子都是把4个字节作为整体的,那C语言面对我们上面的这些char等数据类型怎么办呢?
ARM处理器会想办法的,看下面
@ 读/写指定的数据类型
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STRB R1, [R2]
@ 将R1寄存器中的数据的Bit[7:0]写入到R2指向的内存空间
@ 一个字节
@ STRH R1, [R2]
@ 将R1寄存器中的数据的Bit[15:0]写入到R2指向的内存空间
@ 两个字节
@ STR R1, [R2]
@ 将R1寄存器中的数据的Bit[31:0]写入到R2指向的内存空间
@ 四个字节
@ LDR指令同样支持以上后缀
可以看出用了LTRB,就是一个字节,B是byte, 只读取2个16进制位,也就是8个二进制位
这里是 STRH,H是HalfWord
我们用C语言写一个char,short,int看一下,编译是怎么样的
可以看出分别用到了B,H和默认
这里的a是要从内存中拿的,并不是直接用,而是从R3拿到R2里面去计算。
立即寻址就是指令当中自带数据,直接读取,最快;
直接寻址就是指令中存放的是地址,直接解析这个地址;
间接寻址就是指令中存放的是地址的地址,或者是存放地址的寄存器,最慢。
@ 寻址方式就是CPU去寻找操作数的方式
@ 立即(数)寻址
@ MOV R1, #1
@ ADD R1, R2, #1
@ 寄存器寻址
@ ADD R1, R2, R3
@ 寄存器移位寻址
@ MOV R1, R2, LSL #1
@ 寄存器间接寻址 因为寄存器里面放了地址
@ STR R1, [R2]
@ ...
@ 基址加变址寻址
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ MOV R3, #4
@ STR R1, [R2,R3]
@ 将R1寄存器中的数据写入到R2+R3指向的内存空间
@ STR R1, [R2,R3,LSL #1]
@ 将R1寄存器中的数据写入到R2+(R3<<1)指向的内存空间
@ 基址加变址寻址的索引方式
@ 前索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]
@ 将R1寄存器中的数据写入到R2+8指向的内存空间
@ 后索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2],#8
@ 将R1寄存器中的数据写入到R2指向的内存空间,然后R2自增8
@ 自动索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]!
@ 将R1寄存器中的数据写入到R2+8指向的内存空间,然后R2自增8
@ 以上寻址方式和索引方式同样适用于LDR
写到中括号中的都是地址
多寄存器内存访问指令
@ 多寄存器内存访问指令
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STM R11,{R1-R4}
@ 将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
@ LDM R11,{R6-R9}
@ 将以R11为起始地址的内存空间中的数据读取到R6-R9寄存器中
STM R11,{R1-R4}
将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
LDM R11,{R6-R9}
将以R11为起始地址的内存空间中的数据读取到R6-R9寄存器中
@ 当寄存器编号不连续时,使用逗号分隔
@ STM R11,{R1,R2,R4}
@ 不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
@ STM R11,{R3,R1,R4,R2}
@ 自动索引照样适用于多寄存器内存访问指令
@ STM R11!,{R1-R4}
STM R11,{R1,R2,R3,R4}
STM R11,{R3,R1,R4,R2}
结果还是一样的,不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
STM R11!,{R1-R4}
这个自动寻址的意思就是存完了之后R11的地址改变了,从0x40000020更新成了0x40000030,这是因为刚刚存的正好占了16个字节,所以R11的地址自动往下增了
为什么要这么设计呢?
有时候我们要往内存中存一些数据,但是下一次存的话肯定不能继续往原内存存了,而是应该往新的空余空间的地址存。
多寄存器内存访问指令的寻址方式
@ 多寄存器内存访问指令的寻址方式
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMIA R11!,{R1-R4}
@ 先存储数据,后增长地址
@ STMIB R11!,{R1-R4}
@ 先增长地址,后存储数据
@ STMDA R11!,{R1-R4}
@ 先存储数据,后递减地址
@ 这个是往地址存,比如你起始地址是0x40000020,那么往0x40000010这个方向存
@ STMDB R11!,{R1-R4}
@ 先递减地址,后存储数据
STMIA R11!,{R1-R4} 先存储数据,后增长地址
STM默认的就是STMIA,效果一样的
STMIB R11!,{R1-R4} ,先增长地址,后存储数据
为什么增加的是4个字节呢?
因为一个寄存器就是4个字节,加了一个本身的长度
STMDA R11!,{R1-R4}
先存储数据,后递减地址
这个是往低地址存,比如你起始地址是0x40000020,那么往0x40000010这个方向存
STMDB R11!,{R1-R4}
先递减地址,后存储数据
总结一下子
IA:increase after
IB:increase before
DA:decrease after
DB:decrease before
栈的本质就是一段内存,程序运行时用于保存一些临时数据
如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等
SP寄存器存栈的地址
增栈
:压栈时栈指针越来越大(SP往下面走),出栈时栈指针越来越小(SP往上面走)
减栈
:压栈时栈指针越来越小,出栈时栈指针越来越大
满栈
:栈指针指向最后一次压入到栈中的数据,压栈时需要先移动栈指针到相邻位置然后再压栈
空栈
:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后需要将栈指针移动到相邻位置
满栈进栈
是先移动指针再存;
满栈出栈
是先出数据再移动指针;
空栈进栈
先存再移动指针;
空栈出栈
先移动指针再取数据。
栈分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种
ARM处理器一般使用满减栈
比如上面讲的这个
对应的就是这四个形式
满减栈写入数据对应的就是STMDB
满减栈读取数据对应的就是LDMIA
看一个实际代码吧
压栈:STMDB
出栈:LDMIA
这样子还是太麻烦了,换来换去的
这样子和上面的效果是一样的。所以直接用STMFD和LDMFD就好啦(记得加感叹号)(在汇编的时候会编译成上面的两个指令,STMDB和LDMIA,编译器都帮做好了,很人性化),都是满减操作。
@ 栈的应用举例
@ 1.叶子函数的调用过程举例
@ 初始化栈指针
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC
@ ADD R3, R1, R2
@ B STOP
@ FUNC:
@ 压栈保护现场
@ STMFD SP!, {R1,R2}
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ 出栈恢复现场
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
这里的压栈保护的意思是,因为你跳转到FUNC函数,如果你之前的R1和R3里面的数据没有保存到栈里面的话,那么FUNC里面重新给值就会被覆盖掉,所以要保护起来。先把他们两个放在栈里面,函数用完了之后释放出来,之后再跳转出去
@ 2.非叶子函数的调用过程举例
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC1
@ ADD R3, R1, R2
@ B STOP
@ FUNC1:
@ STMFD SP!, {R1,R2,LR}
@ MOV R1, #10
@ MOV R2, #20
@ BL FUNC2
@ SUB R3, R2, R1
@ LDMFD SP!, {R1,R2,LR}
@ MOV PC, LR
@ FUNC2:
@ STMFD SP!, {R1,R2}
@ MOV R1, #7
@ MOV R2, #8
@ MUL R3, R1, R2
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 执行叶子函数时不需要对LR压栈保护,执行非叶子函数时需要对LR压栈保护
这个和上面异曲同工,只是多了一层,而且多了个LR的压栈
那解释一下什么叫叶子函数,什么叫非叶子函数
叶子函数没有function分支了(比如上面第一个例子单单就main里面调了一个function,LR不会被覆盖),不调用其他函数了,而非叶子函数有(比如上面第二个例子就function里面还有function,LR会被覆盖,需要保存)
正好借此可以思考一个问题,就是压栈出栈之后,数据其实是没有清空的,还在栈里面,所以局部变量如果不初始化的话,那么值是随机值,因为局部变量是保存在栈里面的。
比如上面的非叶子函数的过程中
function2压栈出栈之后,栈指针从0x4000000C重新回到0x40000014,如果此时在function2调用完之后再去写一个子程序,如果里面有一个局部变量比如Int a,那么当执行Int a这句话的时候,会在栈指针所指的所指的位置,给a分配空间,所以此时a会存在于0x40000010(因为ARM是满减),所以它的值会是0x14000000。但是全局变量不初始化的话是0,因为全局变量如果不初始化是放在BSS段的,编译器编译的时候操作系统会把BSS里面的全部清零。
状态寄存器传送指令:访问(读写)CPSR寄存器
@ 1.4 状态寄存器传送指令:访问(读写)CPSR寄存器
@ 读CPSR
@ MRS R1, CPSR
@ 也就是R1 = CPSR
@ 不能用MOV R1, CPSR,会报错,因为CPSR不能被随意操作。所以需要用专门的状态寄存器传送指令
@ 写CPSR
@ MSR CPSR, #0x10
@ 也就是CPSR = 0x10(此时[7:0]为00010000,此时CPU是user模式,而且FIQ和IRQ都被开启了)
@ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式
@ MSR CPSR, #0xD3
@ 虽然这个语句可以执行,但是此时CPSR并不会被改变
为什么复位以后,CPSR是0x000000D3,也就是[7:0]是11010011
CPU是SVC模式(权限高,便于初始化),CPU是ARM状态(刚上电,这个状态时默认的),禁止FIQ和IRQ(刚上电的时候,在执行一些初始化的工作,此时CPU不希望外界的网卡,按键等硬件来打断,应该禁止FIQ和IRQ)
软中断指令:触发软中断(software interrupt),这是由指令产生的
@ 1.5 软中断指令:触发软中断
@ 异常向量表
@ 一个指令占4个字节,8个指令就是32个字节,就是一个异常向量表的大小。main就到异常向量表之后了 B .是跳转到自身
@ B MAIN @ 这里是0地址(一开始的代码都是0),这里是异常向量表复位的位置,跳转到main
@ B .
@ B SWI_HANDLER @ 跳转到异常处理程序,0x000000008就是SWi异常呆的位置
@ B .
@ B .
@ B .
@ B .
@ B .
@ 应用程序
@ MAIN:
@ MOV SP, #0x40000020 @ 好像这个地址是有讲究的,不是你随便给一个的
@ 初始化SVC模式下的栈指针(栈指针用之前都要初始化的,不然就是野指针,不知道指向哪。初始化情况下,ARM就是SVC模式的)
@ MSR CPSR, #0x10
@ 切换成USER模式,开启FIQ、IRQ
@ 不可以先MSR CPSR, #0x10再MOV SP, #0x40000020,因为假如是这样子的话,那么你的SP初始化位置是在USER模式下初始化的,是user_SP,遇到SWI异常之后,CPU又切换到SVC模式,那么你SWI_HANDLER下面的STMFD SP!,{R1,R2,LR}的SP也应该是SVC_SP,这样就和你之前的user模式下的SP不一样了。本来你SWI_HANDLER下面的STMFD SP!,{R1,R2,LR}中的SP应该就是这个初始化的SP,但是现在不一样了!!!SVC模式是超级用户模式,可以做很多user模式下做不了的事情。
@ MOV R1, #1
@ MOV R2, #2
@ SWI #1
@ 触发软中断异常
@ ADD R3, R2, R1
@ B STOP
@ 异常处理程序
@ SWI_HANDLER:
@ STMFD SP!,{R1,R2,LR}
@ 压栈保护现场
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ LDMFD SP!,{R1,R2,PC}^
@ 出栈恢复现场
@ 将压入到栈中的LR(返回地址)出栈给PC,实现程序的返回
@ ‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
从这个图可以看出来,ADD的指令地址是0x00000010(看汇编部分的黄色部分)
运行SWI #1
可以发现SPSR变成了之前CPSR的样子,也就是0x00000010
而现在的CPSR变成了0x00000093,换成二进制后8位也就是10010011,后5位10011代表SVC模式,前3位100,只有1是从0变到1的,另外两位没有发生改变,也就是单单把IRQ禁止了
LR的值也变成了之前ADD的指令地址是0x00000010,PC变成0x000000008(向量表中SWI指令,上面图片中的代码有点问题,实际上0x000000008的位置应该是异常向量表的SWi,所以要改变,正确的完整代码请看上面那个代码块里面的代码)
SWI软中断的应用
比如,如图,你在APP应用层写了个程序(此时CPU肯定是user模式的),里面需要用到write,往HW磁盘硬件层写东西,那么此时你在user模式下了肯定不可以用,哪能让你随随便便读写磁盘数据啊,所以得切到SVC超级用户模式,但是user模式下你是不可以直接通过msr状态寄存器传送指令直接修改CPSR的(因为权限低),也就是不可以直接更改模式到SVC模式,那怎么切呢?write(内部会有SWI指令)指令会触发SWI软中断,会切换到SVC模式,此时就可以往磁盘里面写东西了,写完东西再通过MSR修改CPSR寄存器把CPU切换到user模式(SVC模式下是可以切换的,毕竟是超级用户,为什么要切回来,因为重要的东西比如磁盘不用的时候肯定要锁起来,不然容易被干扰)
但是不同的系统调用(比如write),底层的执行机制是有不同的,比如之前学的socket里面的write,底层操作的硬件是网卡,而不是把数据写到磁盘。但是不管什么系统调用,都是通过SWI由用户态切换到内核态(由用户模式切换到SVC模式),具体系统调用里面你内核需要执行怎么样的机制,是通过SWI后面的参数来决定的(比如SWI后面写一个 #1,比如SWI后面写一个 #2),比如写1的话可能就是写入数据到磁盘,2的话可能就是写网卡。这些还是做一些了解吧,我们最后学的是驱动,这些内核里面的调用机制都是直接写好的,也不需要管了。
@ 1.6 协处理器指令:操控协处理器的指令
@ 1.协处理器数据运算指令
@ CDP
@ 2.协处理器存储器访问指令
@ STC 将协处理器中的数据写入到存储器
@ LDC 将存储器中的数据读取到协处理器
@ 3.协处理器寄存器传送指令
@ MRC 将协处理器中寄存器中的数据传送到ARM处理器中的寄存器
@ MCR 将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
@ 2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
@ 空指令,让CPU停下来
@ NOP
@ 指令
@ LDR R1, [R2]
@ 将R2指向的内存空间中的数据读取到R1寄存器
@ 伪指令
@ LDR R1, =0x12345678
@ R1 = 0x12345678
@ LDR伪指令可以将任意一个32位的数据放到一个寄存器(MOV不可以,看之前的笔记,怕没位置。。因为似乎不是所有的位都是留给数据的,忘了,往前面翻一下。)
@ LDR R1, =STOP
@ 将STOP的地址写入R1寄存器
@ LDR R1, STOP
@ 将STOP地址中的内容(比如stop指令的机器码)写入R1寄存器
@ 注意这个和上面这个是不一样的
NOP
MOV R0, R0编译之后也是NOP
证明NOP的作用其实就是拖延一段时间,因为把R0的数据给R0,相当于什么也没干(CPU是停不下来的,只能通过类似的操作让它看起来停下来了)
LDR R1, =0x12345678
看一下这个是把PC地址所指([PC])的数(就是下图黄色行的下面第二行的打红圈的数据)存进去(不是直接把数存进去汇编指令的机器码里面去,直接把数存进去是没有位置存进去的)
根据三级流水,当CPU执行到黄色行的时候,PC指针是在下面第二行的,也就是下图打红圈的地方(此处都没有指令汇编的机器码了,而是直接一串数字)。因此LDR R1, =0x12345678是把此时指针所指的0x12345678存到R1里面去,避免了汇编指令的机器码就32位可能不能同时放下指令操作和数据的问题(因为一个汇编指令的机器码,当你操作数据的时候,里面需要包含操作指令和数据,如果你的数据就是32位了,那么你汇编指令的机器码里面没有位放操作指令了)
好好区分下面两个的区别,一个是把地址0x00000004(STOP汇编指令机器码的地址是0x00000004)放进R1,一个是把机器码本身放进R1
伪操作
伪操作不会生成代码
比如
在C语言中,带;的就是可以编译的指令语句,带#的一般就是预处理指令,不会生成代码
比如预编译
# if 0
# end if
比如宏
# define PI 3.14
不管是宏还是预编译指令,都不会生成代码,不会编译,作用就是告诉编译器怎么编译
伪操作就是类似于C语言中的带#的
@ *****************************************************************
@ 3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
@ GNU(就是LINUX)的伪操作一般都以‘.’开头
@ .global symbol
@ 将symbol声明成全局符号(这样子在其他.s文件也可以引用了)
@ .local symbol
@ 将symbol声明成局部符号(这样子就只能在当前的.s文件里面使用了)
@ .equ DATA, 0xFF
@ 这个相当于宏
@ MOV R1, #DATA
@ 这个DATA就是0xFF了
@ .macro FUNC
@ MOV R1, #1
@ MOV R2, #2
@ .endm
@ FUNC @调用的就是上面的这一段代码,相当于函数封装
@ .if 0
@ MOV R1, #1
@ MOV R2, #2
@ .endif
@ 这个的效果就是C语言的#if 0 # endif
@.rept 3
@ MOV R1, #1
@ MOV R2, #2
@.endr
@ 这个代码的意义是把中间两行MOV重复3遍,主要是起一个重复的作用
@ .weak symbol
@ 弱化一个符号,即告诉编译器即便没有这个符号也不要报错
@ .weak func
@ B func
@ 即使func没有定义也不会出错,也会继续编译,但是会编译成NOP
@ .word VALUE
@ 在当前地址申请一个字的空间并将其初始化为VALUE
@ MOV R1, #1
@ .word 0xFFFFFFFF
@ MOV R2, #2
.word VALUE
在两个指令之间填充了一个数据
@ .byte VALUE
@ 在当前地址申请一个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .byte 0xFF
@ .align N
@ 告诉编译器后续的代码2的N次方对其
@ .align 4
@ MOV R2, #2
这样子会报错,因为没有4的整数倍对齐
.align 2表示之后的代码是2^2次方对齐,也就是说后面的东西要从4的倍数开始存
如果写4呢?
正好是从16的倍数开始存,0x00000010,10转化为十进制就是16
@ .arm
@ 告诉编译器后续的代码是ARM指令
@ .thumb
@ 告诉编译器后续的代码是Thumb指令
@ .text
@ 定义一个代码段
@ .data
@ 定义一个数据段
@ .space N, VALUE
@ 在当前地址申请N个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .space 12, 0x12
@ MOV R2, #2
@ 不同的编译器伪操作的语法不同
@ *****************************************************************
这12个字节存的都是12
@ C和汇编的混合编程
@ C和汇编的混合编程原则:在哪种语言环境下符合哪种语言的语法规则
@ 1. 在汇编中将C中的函数当做标号处理
@ 2. 在C中将汇编中的标号当做函数处理
@ 3. 在C中内联的汇编当做C的语句来处理
@ 1. 方式一:汇编语言调用(跳转)C语言
@ MOV R1, #1
@ MOV R2, #2
@ BL func_c @这个是在.c文件中写了 编译器会把C语言程序的最后面编译成BX LR(R14),跳转回汇编语言
@ MOV R3, #3
编译器会把C语言程序的最后面编译成BX LR(R14),跳转回汇编语言
@ 2. 方式二:C语言调用(跳转)汇编语言 (汇编文件里面写好一个标号,然后在C语言里面调用)
@ .global FUNC_ASM @要global,否则C语言不认识FUNC_ASM,会出现链接错误
@ FUNC_ASM:
@ MOV R4, #4
@ MOV R5, #5
@ 3. C内联(内嵌)汇编
@ *****************************************************************
也就是在汇编语言调用C函数,然后C函数里面写汇编(不是调用汇编了,而是直接写汇编),asm用于调用汇编
__asm 关键字用于调用内联汇编程序,并且可在 C 或 C++ 语句合法时出现。
ATPCS协议
初识开发板硬件资源
初识电路原理图
交叉开发环境搭建
硬件控制原理
中间小的板子是核心板,大的是扩展板或者说基板
常见元器件,电阻电容二极管啥的就不介绍了
三极管
芯片,里面的电路不管,关心引脚就好了。不同的引脚有不同的名字,名字就代表功能
比如上面芯片的GND23就是丝印
DC33V(这种叫网络标号):DC直流,33V是33伏。LED粗的这端是正极,所以这根线是正极
这个温度传感器也有DC33V,说明这个器件和上面的器件是连在一起的
之前应用层开发是写代码(乌班图)–编译代码(乌班图)–乌班图执行
现在是写代码(乌班图)–编译代码(乌班图)–开发板ARM处理器执行
- 安装交叉编译工具gcc
之前我们乌班图安装了gcc,为什么现在还要安装gcc
因为之前的gcc是把C语言编译成X86CPU能够执行的代码
现在我们需要编译成ARMCPU能够执行的
把GCC交叉编译压缩包直接从Windows拖进来,mv XXXXX . 记得加点. 就是移到当前文件夹
然后tar xvf xxxxx 解压缩
然后进入到bin
这时候还是不方便,要是想使用这些工具,只能在bin目录下用。或者在其他地方用但是指明绝对路径
把交叉编译工具链的安装路径添加到全局环境变量里面,这样子在哪个文件夹里面都可以用了
编辑~下的.bashrc文件,在最后面添加
export PATH=$PATH:/home/hjubuntu/Linux_4412/toolchain/gcc-4.6.4/bin/
//这个/home/hjubuntu/是我家目录的地址
//所以后面这一串就是交叉编译工具链的绝对路径,把这个绝对路径添加到PATH里面去(在PATH本身的值的基础上再添加一个绝对路径,$PATH就是PATH本身的内容)
//然后
source .bashrc
//使得刚刚的修改生效
明明有这个文件,就是打不开
这是因为我的Ubuntu是64位的,无法打开32位的软件,要安装32位运行库
按照老师的教程,执行
$ sudo apt-get install lib32z1 lib32ncurses
发现lib32ncurses安转不上
所以尝试只安装lib32z1
sudo apt-get install lib32z1
按住CTRL点击左键看参考链接
检查下有没有用,那个安装上安装不上就不管啦
直接输入文件名有信息出来,说明可以成功打开文件了
现在我们来写一个C文件
如果用gcc编译的话,用file a.out查看它的a.out文件格式
是32位的可执行文件,只能在Intel 80386处理器运行(X86架构处理器,这个是老师的电脑写的,我写的话应该是64位的)
如果用arm的gcc编译
输入arm按一个tab就会补齐指令
再用file查看
可以看到只能在arm处理器上面执行
所以我在我的LINUX打开是不行的
- 在Windows安装SecureCRT
开发板并没有终端显示屏幕,和LINUX界面不一样,那我们输出输出(比如我想和在LINUX里面一样输入Ls显示文件目录)就不行,利用SecureCRT通过串口线把指令给开发板以及把开发板的信息传送回来(比如类似ls这种指令),这样子就可以交互了。主要就是起一个交互作用。称之为终端。
看课件里面的安转指南安装
- Windows安装USB驱动转串口程序
开发板在开发的时候需要利用串口线跟电脑互动,发送和接收信息,但是串口线插电脑上是识别不了的,因为没有驱动。
同样按照课件安装
- 下载测试程序
https://blog.csdn.net/weixin_46089486/article/details/108823917
LDR和STR(读写内存)能操控硬件
地址空间为什么是4个G呢?
4个G就是32位,就是2^32。对于STR R1, [R2] 把CPU里面R1的数据写入到R2地址所指的内存。对于这个R2这个寄存器,32位,所以范围就是4个G。STR也好,LDR也好,读写的范围是4个G。
CPU要执行一些指令,存在ROM(断电数据不丢失),所以这个也要放在这个4G的空间里面。往内存读写变量、数据,存放在RAM里面,所以这个也要放在这个4G的空间里面。CPU工作还要控制硬件,所以硬件的这些寄存器IO,所以这些寄存器也要放在这个4G的空间里面,还有一部分空间是没有用上的,是RSV。
这个4G的空间就是上面这些东西了
SOC集成了很多硬件的寄存器。这些硬件比如USB,串口,网卡等等,它们都有寄存器。CPU可以通过控制这些寄存器控制硬件。而这些寄存器要映射到CPU的4G的空间里面,否则怎么找得到对应的东西啊。之前我们讲的诸如LR,PC这些寄存器是在CPU内部的,没有地址,CPU是可以直接读写寄存器的,比如MOV R1,#1。而对于这一节的寄存器,是在CPU外的,CPU不可以直接读取,而是用过它们的地址。
比如这个网卡也有一个范围(虽然这个电脑是64位的,范围也是64位的,但是道理就是这个道理)
看一下芯片手册里面的SEC_Exynos 4412手册完整版
不同硬件对应着不同的地址
这里是内存的地址(你一定要知道这个开发板的内存是从0x40008000开始的,后面编译的代码也是放在这个地方),1.5+1.5,这个是要自己去加的,课程的开发板加了一个1G(0x00008000)的,也就是从0x40000000到0x40008000是我们自己加了的内存,0x40008000到0xFFFFFFFFF是没有用到的。所以之前让写软件的地址写的是40008000(实际上就是内存的地址。而且因为ARM是满减模式,所以是从后面往前面写的)
最后面一行写错了,右边应该是FFFFFFFF
就是来控制引脚啥的
我们现在来分析LED2
打开电路原理图,LED在基板不在核心板,先看基板吧
因为我们也不是专门设计硬件的,所以这里听不太懂也没关系
给这根线来一个高电平,灯就能亮,低则灭。
因为LED2右边接的电源正极,2那边接地,所以只需要3和2连接起来灯就能亮了,根据三极管的原理(具体我也不清楚,模电原理),这里是需要这根线来个高电平的。
通过网络标号去查找这根线连到哪(连的是核心板,要去核心板搜,换个pdf了)
这显然是芯片的引脚
看一下是什么芯片
最下面标注是4412
左边就是引脚的名称。一个引脚可以有很多名字组合在一起(多用途,名字对应着用途)。
找到连接关系了,就是连接到GPX2_7
现在打开芯片手册,看GPIO的相关部分(很多关于引脚的东西)
4组有32个引脚,每一组有8个引脚
去找哪里寄存器控制GPX2_7,直接在手册搜GPX2_7
往这个寄存器里面写东西,就可以配置GPX2类引脚(GPX2只是其中一个),后面那个寄存器是写数据
第一个控制上下拉(了解就行),第二个控制驱动能力(输出功率和电流,同样是了解)
现在我们在手册搜一下GPX2CON寄存器
最后一个就是GPX2_0, 比如写成0000就是把GPX2_0引脚配置成Input输入功能。每4位管理一个引脚。
所以我们需要找GPX2_7,也就是找GPX2CON[7] 因为之前分析连接的引脚是GPX2_7
咱们这个实验设置成什么功能呢?
0是输入功能,引脚能够检测是什么信号,现在我们是需要控制高低信号来控制LED,所以需要的是输出功能,output
所以把[31:28]设置成0001
但是它只有输出功能,控制不了具体的高低
现在我们看其他寄存器
GPX2DAT
这个只写[7:0]是它只用[7:0]不是只有[7:0],它是有32位的,高24位还没有用
看它的描述( When configuring asoutput port then pin state should be same as corresponding bit.),它这0-7位是0或者1,正好控制GPX2_0到GPX2_7引脚的低高电位
比如第0位是0,那么GPX2_0是低电位,1则高电位
所以这个点灯程序只需要设置两个寄存器:GPX2CON[7]和GPX2DAT的第七位
简单点就是往两个地址里面写内容
首先往GPX2CON写
这个R1换成二进制就是00010000 00000000 00000000 00000000
这个功能就是配置实现引脚的输出功能
现在开始实现GPX2DAT
最后面还要写一个STOP
因为PC执行完上面最后面的代码之后还会往下面执行,怕出现什么错误
写个死循环,让CPU停在这里
之后我们来编译
首先写一个makefile
我们再来熟悉一下C语言编译过程
-c是把汇编语言变成了机器语言(.o文件)
-o是表示生成的意思
ld是链接(链接机器码),.elf文件是Linux下的可执行文件(之前的a.out就是.elf格式,只是表现形式不同而已)
加上-Ttext 0x40008000 表示把编译完的.s放在内存的0x40008000(起始地址),这是因为这个开发板的内存就是从这里开始的,一定要有放在内存里面
elf要转换成开放板能够识别的文件(bin格式),需要用到objcopy工具
再写个clean,把中间文件清除掉
运行Makefile
把这个bin文件放在share文件夹,传到Windows
在SecureCRT load BIn文件
然后开发板的LED的状态就会发生改变
LED实验
让LED灯亮灭亮灭
只需要写一个点灯程序,一个关灯程序,交替运行
点灯和关灯程序都写在同一个寄存器里面
这次我们把框架搭好,便于循环
这个DELAY就是单纯拖延时间
一直让它减到0,才跳出这个DELAY函数
Makefile编译好,然后发到window上面
刷到板子里面
发现LED更暗了,是因为闪烁得太快了,看起来暗,所以把上面DELAY里面的时间改大一点,如果ARM CPU主频是1000HZ,一个时钟周期可以执行一条指令,所以1秒钟可以执行10亿条指令(理想情况,有的指令可能一个周期执行不完,或者有一些跳转指令啥的),所以我们可以把之前的数字1000000改成100000000,那么每次减1就是减一亿次。时间大概就是0.1秒)
更改之后,一秒钟可以闪个两三次
之后我们不拿汇编语言写了,还是高级语言比较简便,这次是让我们理解原理。
首先看下Interface.c
以后写C语言程序,在这里面直接写就好了
common/include里面是一些头文件,先不管
commom/src里面都是学一些.c .o .s文件,一些源代码,以后做实验可能会调用其中代码
start里面就一个start.s文件
C语言运行的时候需要使用栈,很多寄存器要在栈里面保护现场,还有些局部变量啥的。在CPU运行之前,得初始化SP,否则就是野指针了。CPU刚上电是SVC模式,需要用汇编把SVC模式改成USER模式。所以在CPU正式运行之前,需要做好上面这些工作。start.s就是做这些工作的,称之为启动代码。是运行在interface之前的。
Makefile是汇编代码
稍微了解下,直接用就好了
map.lds是链接脚本文件,负责的代码的排版(这么多文件,哪个放哪里是有规定的),认识下就好
我们来分析下启动代码吧 start.s
.text是一个伪操作,告诉编译器后面是一个代码段
_start里面是异常向量表,正好32个byte (8条指令)
分析一下reset的代码
ldr 把start的地址给r0寄存器
mcr 修改异常向量表的位置,ARM默认异常向量表的地址是0, cortex_A系列可以通过协处理器修改异常向量表的位置,把r0给p15里面的C12,这句话执行完后,异常向量表就不会跳转到默认的0地址了,而是跳转到我们自己写的异常向量表的地址。了解一下就好
关闭FIQ/IRQ模式是想不让它被外界打断
FPU:运算浮点型的协处理器
初始化栈指针(init_stack)
init_stack:初始化栈
第一行:把CPU的模式改为SVC模式
第二行:把栈空间最高的地址给了sp
其他各个模式都是类似的
之后各个模式之下的栈指针都初始化了(注意之所以要这样子重复,是因为虽然都是sp,但是不同模式下的sp是不一样的)
b mian 混合编程,跳转到C语言的main函数
_stack_svc_end稍后解释
.space 512 :这512个空间别用了,被我占掉了
这512空间就是作为栈使用的,为了确保栈的空间不被其他代码使用
那为啥要这么多512呢?因为不同模式下的代码栈指针(SP)是不一样的,需要不同的栈
因此这段代码的功能就是申请各个模式下的栈空间
_stack_svc_end,因为压栈是满减模式,所以最开始的时候栈指针应该指向占空间的最高地址,所以就得把end栈指针的起始位置计算出来
首先写Interface.c
int main()
{
return 0;
}
复习的时候直接拖到最下面看,因为这里是一个一步步封装的过程
/*
* 一.汇编语言访问存储器
* 1.读存储器
* LDR R1,[R2]
* 2.写存储器
* STR R1,[R2]
*
* 二.C语言访问存储器
* 1.读存储器
* data = *ADDR
* 2.写存储器
* *ADDR = data
*/
void Delay(unsigned int Time)
{
while(Time --);
}
#if 0
int main()
{
/*通过设置GPX2CON寄存器来将GPX2_7引脚设置成输出功能*/
*(unsigned int *)0x11000c40 = 0x10000000;
//这里Int其实也可以
while(1)
{
/*点亮LED2*/
*(unsigned int *)0x11000c44 = 0x00000080;
/*延时*/
Delay(1000000); //因为C语言编译完以后生成的代码很大了(C语言一条指令可能会被编译成好多汇编指令),肯定不能像汇编一样写几亿
/*熄灭LED2*/
*(unsigned int *)0x11000c44 = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#if 0
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
int main()
{
GPX2CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#if 0
typedef struct
{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40)
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#if 0
#include "exynos_4412.h"
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#include "exynos_4412.h"
int main()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
//因为这个的位置是[28:31],而且是要置成0001 。。。也就是28位是1。。。0-31位是从右到左排列
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7); //和10000000参与|运算,第7位变成1,其他位保持不变.看下面的笔记
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7)); //和01111111参与&运算,第7位变成0,其他位保持不变。看下面的笔记
/*延时*/
Delay(1000000);
}
return 0;
}
/*
以下都默认unsigned int a是随机值,不知道里面的数字是啥
* 1.unsigned int a; 将a的第3位置1,其他位保持不变
任何一个数和0进行|运算,保持不变,如果|上1那么一定是1。。。。可以看后面的表格看下这些符号的功能
* ******** ******** ******** ********
* ******** ******** ******** ****1***
* 00000000 00000000 00000000 00001000 //和这一串数字参加|运算能达到上面的目的
*
* a = a | (1 << 3);
*
* 2.unsigned int a; 将a的第3位置0,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****0***
* 11111111 11111111 11111111 11110111
*
* a = a & (~(1 << 3));
*
* 3.unsigned int a; 将a的第[7:4]位置为0101,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** 0101****
*
* 1).先清零(先将0101这4位清零)
* 11111111 11111111 11111111 00001111 用下面的数取反得到。
* 00000000 00000000 00000000 11110000 用下面的数左移得到。
* 00000000 00000000 00000000 00001111
*
* a = a & (~(0xF << 4)); 0xF就是 00000000 00000000 00000000 00001111
* 这样子的话,和a相与,那么其他位保持不变,0101这四位,都变成0。。。完成置零的目的了。
*
* 2).再置位
* 00000000 00000000 00000000 01010000 用下面的数左移得到。把这个数和第一步得到的进行或运算
* 00000000 00000000 00000000 00000101
*
* a = a | (0x5 << 4); 0x5就是 00000000 00000000 00000000 00000101
* 这样子的话,那么其他位都保持不变,只有0101这四位,0这里保持不变,1这里的话铁定1。。。因为上面这四位都变成0了,所以保持0101不变。
* => a = a & (~(0xF << 4)) | (0x5 << 4);
* 记住这个格式,先把需要置位的地方置零,然后置位
*/
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
UART就是串口
数据线有好多根
数据线只有一根
虽然并行的速度快,但是条线多,布线难度大,不同线之间有信号干扰,我们做项目用得多的还是串行总线。
半双工(总线就一根)和全双工(总线两根)都是双工,数据传输都是双向的。但是半双工不能同时双向传输。
波特率
举个例子,比如我们要发送0x55,也就是01010101
数据线高电平表示发送1,低电平表示0。那空闲的时候呢?串口协议规定为高电平。
在发数据之前得写一个起始位,为啥呢?那是因为空闲位是高位,为了告诉别人我要开始发送数据了,就发一个能够区分的东西,就是起始位这个低电位。
先发低位,后发高位(习惯发八位,因为内存以字节为单位,一个字节就是8位,而且char类型数据就是八位。也可以发5-8位的数)
校验位是校验数据的正确性,比如发一个123过来,校验位把123加起来为6,如果后面收到的数据加起来还是6那么数据就是正确的(当然这个校验是很简单的,和校验,还有奇偶校验。校验位只是校验不可以纠正)。
停止位是高电平
如果想要发十位,必须分成两次发
如果是发送数据00001111,那么0持续四个周期,1持续四个周期。比如说分别是4秒和4秒。如果数据特别长,可能会有很大的累计误差。比如40秒变成39。所以要这样一个字节一个字节的发,误差很小。串口只发一个字节(不超过一个字节,5-8位),就是避免累计误差。
串口通信是异步通信,一个接,一个发,他们有不同的时钟(时钟不同步),就可能有误差
同步通信的话,用的是同一个时钟基准,可以一次发多个字节。后面会讲同一个时钟的通信
串口有两根线:TXD发送数据,RXD接收数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pYCDXRv-1681907855957)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230320194527274.png)]
Exynos4412下的UART控制器
因为我们现在只有一块开发板,也做不了开发板之间的通信,所以我们用我们的fs4412开发板来和电脑通信
用USB转串口线,把USB信号转成串口信号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVJi21n3-1681907855957)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230320212230285.png)]
这个串口的接口就2和3引脚有用,2是TXD,3是RXD
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHYRMJHR-1681907855958)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230320212202177.png)]连接到了一个芯片上,这个芯片把串口信号做加强(抗干扰,增强通信距离),把TTL信号转化为232信号
所以真正连接到4412芯片上的是引脚11和12
这个标号可能会随着开发板型号变化,可以搜TXD和RXD
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtqvZoso-1681907855958)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230320214123545.png)]
根据标号,又可以发现和UART连接的地方
pin是引脚。。。。。。比如之前设置GPIO的功能,现在又需要把引脚设置到UART了
把数据写入FIFO,然后再通过串口发送出去(这个顺序和写入FIFO的顺序一致),同样RXD接收到的数据也会按照同样的顺序放入FIFO。。。。所以只需要管FIFO就行,这是一个队列,先进先出。
串口控制器(UART):发送器和接收器都包括一个队列和一个移位器(按照二进制移动位,一位一位移出去)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttD2EymP-1681907855958)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230322165745152.png)]
发送器: 移动器就像上面这个样子,把FIFO(缓冲区)里这个12345这个数据转化为二进制放在移位器里面(包括启动位,停止位啥的),然后一位一位地移给TXD(发送数据)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6W1Njsg-1681907855959)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230322170525671.png)]
接收器: 把二进制通过RXD放入到移位器,然后放入到FIFO(缓冲区)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXZN4pUe-1681907855959)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230322170949164.png)]
UART串口控制器包括控制单元(控制逻辑,control unit),发送器(transimit shifter),接收器(receive shifter),波特率产生器
我们需要改 GPA1CON这个寄存器,[0]和[1],分别是[3:0]和[7:4]
把[0]和[1]设置成0x2,这样子的话就可以变成输出和接收功能了
再看UART的寄存器,由于有5个串口(四个通用,一个GPS专用),串口2的基地址如下图红圈处。
与本次实验相关的寄存器如下图,分别是控制发送和接收的缓冲区的和控制波特率的寄存器。
(咱们不用队列了(简单些),直接接收到一个输出一个)。四个寄存器看代码,不是底下图的红框。
设置ULCONn寄存器(设置帧格式)根据下图。(咱们使用第二个串口,所以n是2)
ULCONn的含义
ULCONn | 位 | 描述 | 初始状态 |
---|---|---|---|
Reserved | [7] | 0 | |
Infra-Red Mode | [6] | 是否使用红外模式 0=正常模式(有线收发) 1=红外模式(无线收发) | 0 |
Parity Mode | [5:3] | 校验方式 0XX=无奇偶校验 100=奇校验 101=偶校验 110=校验位强制为1 111=校验位强制为0 | 000 |
Number of Stop Bit | [2] | 停止位数量 0=1个停止位 1=2个停止位 |
0 |
Word Length | [1:0] | 数据位个数 00=5bit 01=6bit 10=7bit 11=8bit;之前不是介绍了一次传输5-8位吗,这个就是用来控制这个的 | 00 |
本次实验我们统一采用,8位数据位传输,1位停止位,不用校验位,正常模式
UCONn寄存器,里面可以控制接收和发送模式
UCONn | 位 | 描 述 | 初 始 值 |
---|---|---|---|
Rx Error Status Interrupt Enable | [6] | 0:不产生接收错误中断 1: 产生接收错误中断 |
0 |
Loopback Mode | [5] | 0:正常模式 1: 发送直接传给接收方式(Loopback) |
0 |
Reserved | [4] | 0:正常模式发送 1: 发送间断信号 |
0 |
Transmit Mode | [3:2] | 发送模式选择 00:不允许发送 01:中断或查询模式 10:DMA0 请求 11: DMA1 请求 |
00 |
Receive Mode | [1:0] | 接收模式选择 00:不允许接收 01:中断或查询模式 10:DMA0请求 11:DMA1请求 |
00 |
轮询(polling):CPU时不时问队列要数据
中断:如果串口控制器收到数据,向CPU发送中断信号
DMA:直接存储器访问。把接收到的数据直接放到内存,不经过CPU
本次实验我们统一采用,轮询模式,也就是CON2[3:0]
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2 下面参数的计算公式在下面*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
//让串口输出一个字符串 ,pstr字符串的首地址,hello是5个字符也可以说是6个字符,最后是\0
{
while(*pstr != '\0')
//一直发到\0前面的字符
UART_Send_Byte(*pstr++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
/*
RecDat = UART_Rec_Byte();
if(RecDat == 0)
{
}
else
{
RecDat = RecDat + 1; //不要问为什么+1,就是用一个简单的操作代表操作,如果是字母的话那么就是ASICC码加1
UART_Send_Byte(RecDat);
}
*/
/*
UART_Send_Str("Hello World\n");
如果是这个代码结合UART_Send_Str函数,那么一直输出Hello World(因为在循环里面),但是每一次都会换行(不是顶格换。。不细讲,想细究去看视频)
*/
/*上面这个程序在开发板运行:
一开始什么数据也没有,需要开发板输入数据(直接在SECURE CRT按键盘)才行(UART_Rec_Byte会返回0,然后一直陷在while(1)里面,等获取到有效数据,+1,在屏幕输出。。比如你输入了A,输出的就是B(因为是ASCII码+1),之前如果没有这个程序,你在SECURE CRT输入一个load B 输出的也是B 因为你开发板接收到一个数据,然后再原路返回发给开发板)*/
printf("Hello World\n"); //这个的源代码是自己写的,很长,因为有很多格式要自己去写,不自己写了,直接调吧。在common里面。这个printf和应用层的printf有啥区别,之前的printf来自于LINUX的C库,开发板还没有装LINUX,所以这个printf需要自己写,自己写的肯定没有C库的好,这里的这个自己写的不能打印浮点数据。还有输出定向不一样,C库的printf是定向到显卡,自己写的printf是定向到UART。 输出重定向: 比如Linux终端 ls >out.txt 这样终端就不输出了,而是在这个txt文件。如果是输出到终端:整理信息,到显卡,再到屏幕。输出到txt是定向到磁盘。
//开发板的标准输入输出就是到串口,乌班图是定向到显卡,开发板是定向到串口
}
return 0;
}
————————————————
版权声明:本文为CSDN博主「CSDN_Xian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CSDN_Xian/article/details/118943152
将GPA1_0和GPA1_1设置成UART2的接收和发送引脚
这两个合在一起设置,就是设置成0x22
#include "exynos_4412.h" //不同的CPU这里应该不一样,需要更改
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
————————————————
版权声明:本文为CSDN博主「CSDN_Xian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CSDN_Xian/article/details/118943152
设置UART2的帧格式
8位数据位、1位停止位、无校验、正常模式
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
设置UART2的接收和发送模式为轮询模式
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
设置UART2的波特率为115200
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
看一下这个计算过程怎么来的
我们的主频是100MHZ,我们的波特率选择115200(和之前下载程序的波特率保持一致比较方便)。
取出整数部分为53给UBRDIV。。。。。。。因为这个寄存器就这一个功能,所以不要担心会不会对其他位(导致其他功能)受影响,所以直接复制就好
20*0.25=4
所以UFRACVAL取4
将发送的数据写入发送寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vsYbaWuR-1681907855963)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/8233eef4cab581cf7d947b5ac5d99616.png)]
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
————————————————
版权声明:本文为CSDN博主「CSDN_Xian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CSDN_Xian/article/details/118943152
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2bnz3eRB-1681907855963)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230323152250670.png)]
如图,如果是这样子写的话,那么其实最后面输出的未必会按 A B C D 的顺序。这是因为
CPU把数据写到UTXH2寄存器然后通过TXD被发送器发出去,发送器的波特率是115299,CPU的1GHZ ,每秒上亿条指令,CPU比发送器快很多,所以会在UTXH2之前堆积,等到发送器发完的时候,这个时候可以接收新发送来的数据,因此不是按顺序。CPU往寄存器写的速度和发送器发送的数据速度是不一样的,所以导致如此。
所以需要在发送数据之前,写一个判断语句,说明之前的数据已经发完了
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
输入输出重定向
看最上面代码块的注释
看门狗定时器
:用来监控,当软件出现故障,可向CPU发送复位信号,自动实现复位。其位置在CPU外部,SOC上
看门狗定时器
:
本质是一个计数器,给一个值,一直递减,递减至零,向CPU发送复位信号
喂狗
:
让看门狗定时器刷新一个值,使其不递减至零
正常的程序会定时喂狗,而出错的程序无法执行喂狗,则定时器会递减至零,发送复位信号。
读一下Overview、Feature知:看门狗定时器有2种工作模式,普通定时器产生中断功能(告知CPU时间已经到了)和递减定时器(发送复位功能)。
读工作原理图知:WTCON寄存器的第2位用来控制中断,WTCON寄存器的第0位用来控制复位信号的发送。
看门狗定时器的时钟源是Pclock。它的频率是100M每秒(太快了,需要分频,变慢一点),经过一个8位(2^8:0-255)的预分频器分频,可降频1~256倍(也就是除,设置成0就是1分频,因为不能做分母),由WTCON寄存器的8 ~15位来控制(具体分频多少倍)。再经过4档固定倍率的分频(16,32,64,128),由WTCON的3 ~4位控制,最后给看门狗寄存器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-puyr6DCY-1681907855964)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0HM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
以下是WDT的频率计算(就是你减个1花的时间是可以变化的)公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fYX9E8xd-1681907855965)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ib9nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7nlVAX0-1681907855965)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9LmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0VlGb7K-1681907855965)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
这个存的就是要递减的数,最大是65535。RSVD证明是不可以用的空间。
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
int main()
{
/*设置一级分频256倍,所以应该设置成255*/
WDT.WTCON = WDT.WTCON | (0xFF << 8);
/*设置二级分频128倍*/
/*WTCNT递减频率 = PLCK(100000000)/(0xFF + 1)/128 = 3052*/
WDT.WTCON = WDT.WTCON | (0x3 << 3);
/*禁止WDT产生中断信号*/
WDT.WTCON = WDT.WTCON & (~(1 << 2));
/*使WDT能产生复位信号*/
WDT.WTCON = WDT.WTCON | 1;
/*设置计数器的初始值,这种直接写不考虑其他位的是因为这里只有这个功能。。3052 * 5花5秒钟减完*/
WDT.WTCNT = (3052 * 5);
/*使能WDT,计数器开始递减*/
WDT.WTCON = WDT.WTCON | (1 << 5);
//正常程序中要不停喂狗保证不复位(重启)
//因为上面的都是在一个寄存器里面,可以一次性全部写进去,也可以一个位域一个位域写进去(这里为了教学所以这样子写)
while(1)
{
printf("WDT.WTCNT = %d\n",WDT.WTCNT);
/*喂狗*/
//WDT.WTCNT = 3052; 如果没有这个喂狗代码的话,那么就会产生下图的结果。加了这个之后Delay的这段时间肯定是不会让它减到0的(不会超过一秒),循环回来又变成了3052.所以如果无异常的话,每次循环之后都会是3052
Delay(100000); //避免打印得太快
}
return 0;
}
————————————————
版权声明:本文为CSDN博主「CSDN_Xian」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CSDN_Xian/article/details/119061230
结果是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgQQXNTI-1681907855965)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230326175239024.png)]
最后减到0,复位(重启)
加了喂狗代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVSZraQz-1681907855966)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230326180108872.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPrsEd1L-1681907855966)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
这个是之前代码CPU去看寄存器的情况,看有没有数据
这个是把这种“查看”的情况写在循环里面,让它一直循环查看,也就是轮询的方式
需求:按下按键就向SecureCRT发送一句话
在开发板上找到按键K2,在电路原理图上搜索到相应的原理图
分析可知:UART_RING这根线当K2断开时是1.8V高电平,当K2连接时是0V低电平。因为VDD1V8_EXT是开发板电源是1.8V,如果K2不接通(不按),那么VDD1V8_EXT和UART_RING连接,那么就是1.8V,如果连通,那么就被短路(右下方接地了),那么就是0V
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uhfbijf4-1681907855967)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jQ8VO54k-1681907855967)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/8653d502797241ecb32e2362576a675b.png)]
0到7位,比如第1位如果是0,那么证明是低电平,如果是1那么就是高电平。这0-7位里面,其他位都是这样。因为我们是GPX1_1,所以关心第1位就好了。
把GPX1_1(检测UART_RING引脚状态)设置成输入功能(输入功能就是感知,之前我们LED那边是输出功能,也就是控制寄存器的高低信号)
代码
#include "exynos_4412.h"
int main()
{
/*将GPX1_1设置成输入功能*/
GPX1.CON = GPX1.CON & (~(0xF << 4));
while(1)
{
/*判断GPX1_1引脚的状态,即判断按键是否按下,把DAT其它位清零,只保留第一位*/
//当按下按键,低电平,值为0,取非为1,表示按下了按键
if(!(GPX1.DAT & (1 << 1)))
{
printf("Key2 Pressed\n");
/*等待松手*/
while(!(GPX1.DAT & (1 << 1)));//如果不加这个,一按按键(虽然对于你来说可能很短,但是对于电脑来说,你按压的过程很长)的话,会打印出好多个Key2 Pressed\n,因为它在这段时间已经执行了好多个循环了。所以加这句就是为了不松手的话就不再继续打印了(不继续下一次循环)
}
else
{
}
}
return 0;
}
上面就是一个轮询的过程,因为在这个循环之中,CPU一直去判断引脚的状态。
原理:按下按键,发送中断信号,执行中断处理程序,输出一段话,再回来继续执行正常程序
通过查看芯片手册可知:GPX1CON[1]中将4 ~7位设置成0xF,就能把相应引脚设置成中断模式(虽然有这么模式了,但是还是需要激发才能引起中断)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CA8It79E-1681907855967)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/1ac02bffb0e24bc091497c09905c1f2a.png)]
现在需要进行一些细节设置
若将引脚设置成中断模式,对应的中断设置为EXT_INT之类的寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4Kyb0as-1681907855968)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NsdTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
EXT_INT41_CON(GPX_1就看带41的,GPX_0就看带40的,以此类推)详解:
EXT_INT41_CON[1]表示设置GPX1_1这个引脚的中断设置(EXT_INT41_CON[0]表示设置GPX1_0)
Low level\high level:当引脚是低、高电平时触发中断信号
Triggers Falling edge:当引脚由高电平转为低电平时触发中断信号
Triggers Rising edge:当引脚由低电平转为高电平时触发中断信号
Triggers Both edge:只要引脚有高低、低高电平转换时就触发中断信号
下降沿
(falling edge):由高变低
上升沿
(rising edge):由低变高
看一下我们本次的实验条件,如果不按K2的话,那么引脚是高电平,如果按了的话是低电平;如果设置成低电平,因为我们按一下对于电脑来说会持续很久,所以会一直产生中断,所以不太好。如果是高电平,还没按就一直中断了,所以也不好。设置成下降沿比较好,因为不管是按了多久,下降沿只有一个,所以按一次只会产生一次中断。上升沿是松开按键产生中断,也可以,就看你喜欢哪个了。我们这次实验用下降沿。
以下寄存器是用来设置滤波电路的相关设置的,使上升下降沿更明显,但这里的实际效果不大,因此不设置
MASK寄存器是用来控制引脚是否触发中断功能(虽然前面设置了功能, 但是需要触发)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtXGb3Or-1681907855968)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/3d94e26a25b64d818ae54ce9a2765d3b.png)]
PENDING寄存器用来设置中断是否挂起,若CPU正在处理其他IRQ中断时会自动屏蔽本次IRQ中断,但是我们不能让它直接屏蔽,而是应该让它挂起(挂起就是,先挂一边,等被人执行完了你再执行),因此可以选择挂起等待执行(并不是不执行了,而是需要等待)。设置成1为挂起
如果外设直接发送中断给CPU会存在下列问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2VEFb3d-1681907855969)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmVL0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
因此,三星的4412引入了中断控制器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NBnHtUj-1681907855969)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG41ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
在芯片手册第9章中断表中查到GPX1_1代表的中断9属于ID为57的中断号,后续设置57号中断寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csKKBzTA-1681907855970)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ub34mV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
0-15给了软中断(SGI),16-31号给了PPI(只能发送给一个特定的CPU),32-159给了SPI( SPI用于从整个系统可访问的各种外设发出信号中断)
第一列的号码是128个SPI内部的编号,排在第几个。第二列是总的编号,就是加上了PPI和SGI的,会大32(因为其他就是32个嘛)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28M4dJph-1681907855970)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/42e7f70b5d5b4fdc819d7d348e2816c6.png)]
第一个寄存器
RESERVED就是保留,没有用的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IVuTeEtO-1681907855970)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0LWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第二个寄存器
上面是设置总的开关,这个是设置每一个的开关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgMQTA14-1681907855970)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L090NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
因为这个CPU是四核处理器,所以上面有CPU0-3
上面说了一个位对应一个中断,具体是怎么对应的呢?看下面的尺图。0x100是第一个寄存器,包括了0-31位。第一个寄存器的第一位管理0号中断。0x0104是第二个寄存器,包括32-64。以此类推。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xZFXMfaT-1681907855971)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230327182927167.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsLu1JA6-1681907855971)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE9995fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第三个寄存器:8位用来设置分配给哪个CPU处理,一共160个中断,每个中断需要8位,一共需要1280位。每个寄存器32位,共需要这样的寄存器40个。那也就是一个寄存器管理一个4个中断。
比如00000011,发送给CPU0和CPU1处理。。哪个位就交给哪个CPU处理。。。
由于4412只有4个CPU,因此设置低4位即可设置分配给哪个CPU
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sR8p2H1A-1681907855971)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0Nlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YoCmW02d-1681907855972)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE51fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
根据地址图可知:57号中断对应的是0x838寄存器的8-15位,由于4412上电后默认使用的是CPU0,因此设置第8位(0-31位的第8位,它的8位中的最右边一位(0位))为1来分配给CPU0接收中断信号。因此设置为
对应的寄存器是ICDIPTR14_CPU0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KijLW4Qy-1681907855972)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UoxBQOMt-1681907855972)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0111L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第四个寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YHibNteE-1681907855972)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
这个的作用是用来把中断信号打开(相当于把CPU和中断控制器中间的接口连通,这样中断控制器的信号才能顺利到达CPU)。。。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ape9REph-1681907855973)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230328230038019.png)]
工程模板代码分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5H5bftwA-1681907855974)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230328233253942.png)]
不同的代码在链接的时候会有不同的位置,start.s放在最前面,.c和.s文件放在后面
看一下启动代码
reset前两行这里的代码的作用是把异常向量表的基地址设置在start这,根据不同的异常根据不同的偏移量跳到异常向量表中对应的位置
后面还有很多设置,以后要用自己来这个视频看吧,或者看源代码,或者回去之前的笔记看
总结来说,就是上电之后先执行reset,然后把异常向量表的地址重新定位,然后设置GPU的模式,设置FIQ和RIQ的开和关,设置一些协处理器啥的,然后设置栈,最后把CPU模式设置成USER,把FIQ和RIQ模式打开,然后调到main,执行C语言
然后我们看下C语言
为什么LR寄存器要减4
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqO7kAuR-1681907855974)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
三级流水线!!!当代码执行到N的时候,N+4正在译码,N+8正在取指。PC寄存器指向的正在取指的指令,所以现在PC=N+8!LR自减4的话,那么LR=N+4!所以LR保存的就是下一条指令的地址!!这就是它的原理。
上面是执行BL指令会保存地址到LR,下面是当程序运行产生IRQ中断的时候也会保存地址到LR。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIvya84O-1681907855975)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
中断来了不会立即保存返回地址,而是等指令执行完之后再去保存,但是等你这条指令执行完之后,又开始下一条指令了,所以PC也会自动增加4(变成N+12了),此时去保存PC的到LR(保存的是N+8)的话就是下下条指令了。
所以面对这两种情况,处理LR的方式需要不一样,IRQ的时候LR需要认为去修正一下子!
中断程序的跳转过程
LED2闪烁,当按下KEY2时发送异常中断信号,去执行异常处理程序,执行完后再继续闪烁
start.s
程序最开始执行,先执行启动代码,因为链接也是把启动代码放在最前面的
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
reset: //一些初始化的设置,代码很长
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
/*
* IRQ的异常处理程序,写在main后因为要确保先执行C语言的main程序,遇到IRQ异常再执行这段程序
*/
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式(main函数执行用的是USER模式)下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护(不然之前的值会被覆盖掉)
* lr返回值也压栈,非叶子函数
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq //混合编程,在C语言里面
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^ //fd代表满减,r0-r12压的谁出给谁所以前面写r0-r12,后面是压给pc,所以写pc,^代表出栈的同时把SPSR寄存器中的值恢复到CPSR
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
interface.c
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
//下面可以写160个中断,因为总的中断就是160个
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断 让8-15位等于00000001*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28); //配置成输出功能
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7)); //如果这里产生IRQ中断,那么指针到_start基地址+IRQ产生的偏移量(查看异常向量表不同异常的偏移量),IRQ是0x18,所以PC为_start+0x18
/*start前面代码
.text
.global _start
_start:
// Vector table
b reset 这是是0x00
b . 这是是0x04
b . 这是是0x08
b . 这是是0x0C
b . 这是是0x10
b . 这是是0x14
b . 这是是0x18 所以跳到这里 但是这里没有写跳转处理指令,要写一下
b . 这是是0x1c
*/
/*延时*/
Delay(1000000);
}
return 0;
}
为什么按了一下KEY2会一直打印(触发多次中断)
因为PENDING寄存器没有被清零(产生中断之后就会被挂起,变成1。。。处理完之后,返回到main,还是1),一直是挂起状态,外设一直给中断寄存器发送中断信号。所以要清零。这个寄存器比较特殊,写1清零(这个比较奇葩啊,你要清除掉它的1,你要再给它写个1)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZ1iWSOV-1681907855975)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NfghTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
让CPU识别是哪个外设发送的中断信号
因为不管是哪个外设给CPU发中断信号,只要是发送给CPU0的IRQ,CPU0都会接收然后触发中断,所以不行,我们是给按键写的专门的处理程序,因此要让CPU识别是哪个外设产生的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnNFhGcX-1681907855975)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4u5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号,只保留0-9位,其余位清零(否则会有干扰).。。比如57就是写在这0-9位的*/
IrqNum = CPU0.ICCIAR & 0x3FF; //1111111111 如果是1的话就会保留之前的
当再次有中断信号产生时,让CPU告诉中断控制器CPU已处理完之前的异常中断
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4BINfQK-1681907855975)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTdfghRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57); //先把0-9清零,然后赋值
右边是之前写的轮询,在循环里面CPU一直去检测引脚的的状态,主动地去查询
左边是当有异常了,主动地给CPU发送信号,CPU是不需要一直去问询的
所以中断的效率比轮询的效率更高!
4412里面一共有160个硬件控制器都可以产生中断!
最后面工作的时候,写中断的时候,只需要初始化中断控制器和写处理程序,其他的代码都是写好了的
FIQ比IRQ快的体现:
1. 如果IRQ和FIQ中断同时产生的时候,优先处理FIQ中断。当处理IRQ中断的时候,接到FIQ中断会立马转到IRQ中断,处理完再返回去处理IRQ。但是IRQ不可以打断FIQ。
2. FIQ的异常向量表位置在IRQ的后面,在最末,更快。为什么子啊最末就最快呢?因为可以在异常向量表后面直接写处理程序,不需要跳转。
3. 而且FIQ模式下有几个自己私有的寄存器,有了私有寄存器,就不要压栈保护了,当然如果是用FIQ的公用寄存器,也要需要压栈。
CPU只认识数字信号。ADC将外设采集到的模拟信号转换成数字信号给CPU。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ci1hP3Ae-1681907855976)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nbmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
模拟信号:(连续)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzUZaHsI-1681907855976)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
数字信号:(离散,也就是把数据转换为0101的形式,用高低电平表示)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqUGdaol-1681907855976)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nNTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
比如温度传感器根据温度可以产生0.1-1.6V的连续信号,此时需要转化为数字信号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GJnbuiQ5-1681907855977)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230330000002745.png)]
分辨率表示会触发数字量变化的最小模拟信号的变化量。分辨率又称精度,通常以数字信号的位数来表示。ADC的分辨率以输出二进制(或十进制)数的位数表示。
12位的ADC,最大值是每一位为1,即2^12-1,最小值为0,因此ADC的精度是1.8v/2 ^12,因此此ADC理论上能区分2 ^n个不同等级的输入模拟电压。
其实这个很好懂啦,就是把连续的信号细格化,分成离散的!!12位ADC最大为111111111111,最小为000000000000,用区间把模拟信号一格格分开来表示,格间距就是分辨率。
本次实验的目的:测一下滑动变阻器VR1的电压
由DEV的电路原理图可知:滑动变阻器的电压由XadcAIN3
决定,其电压范围是0-1.8v
VDD1V8就是1.8V,接地是0V,所以随着5的移动,5这根线的电压在0-1.8v变化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5oTKThR-1681907855977)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0fwerL0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
在Core board上搜索可知:引脚功能单一,因此不用设置引脚功能。
之前我们看到的引脚有很多名字,代表很多功能,需要去设置哪个功能(选择一个功能),这个只有一个名字,说明是ADC专用引脚。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUjmiKOw-1681907855978)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZGsafda4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
我们这个芯片既可以转化为10位精度的也可以转化为12位精度的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jB4y0Odk-1681907855978)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzdereZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
这个开发板的PCLK的频率是100MHz
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8SJDiTW-1681907855978)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzswfewZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
转化是需要时间的,上面是时间计算公式。
65+1是分频(+1是因为分母不能为0,所以加1,比如0其实就是分频1)(因为太高了,需要分频器降下来),5是因为有五个时钟。 ADC的时钟不能超过5M, 所以ADC的最大转化频率是1M.上图最后一行英文。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9XbkzoM-1681907855978)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzdqwfrZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
本次实验只用下面三个寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-15ntuL94-1681907855979)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRE5wwfWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
加入时钟源是100M,实际的ADC转化的时钟是5,那么分频为20,设置为19(因为会+1)。最大ADC转化时钟是5M。
standby mode 是低功耗模式
ADC转化需要触发,后面两位[0-1]就是为了设置触发,0和1位二选一。0位呢,是想让它什么时候开始就让它什么时候开始转换,只要你在第0位写了1就好了,它就会开始转换,它转换完了,第0位又会变成0。1位呢,是等你从寄存器里面都走了数据之后,那么开始下一个转换,意思就是ADC后面有一个寄存器(用来存放转换的结果),如果结果没被读取,那么相当于后路被堵了,ADC不会再转换了,只有当被读走了,路通了,就开始新的转换。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Blxwfezb-1681907855979)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/62ad8306e0804704baeae5d2bdf1acb3.png)]
R代表只读(read),不可以写入数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2OwTase-1681907855979)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmVde0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
选择转换的通道,也就是选择转化哪个引脚。ADC只有一个,但是又有四个引脚可以产生模拟信号,怎么做到的?——分时复用!也就是同一个时间点,只能转化一个,分开时间来做。
#include "exynos_4412.h"
int main()
{
unsigned int AdcValue;
/*设置ADC精度为12bit*/
ADCCON = ADCCON | (1 << 16);
/*使能ADC分频器*/
ADCCON = ADCCON | (1 << 14);
/*设置ADC分频值 ADC时钟频率=PLCK/(19+1)=5MHZ(这个是允许最大的) ADC转换频率=5MHZ/5=1MHZ*/
ADCCON = ADCCON & (~(0xFF << 6)) | (19 << 6);
/*关闭待机模式,使能正常模式*/
ADCCON = ADCCON & (~(1 << 2));
/*关闭通过读使能AD转换*/
ADCCON = ADCCON & (~(1 << 1));
//上面其实也可以一次性设置
/*选择转换通道,3通道*/
ADCMUX = 3;
while(1)
{
/*开始转换*/
ADCCON = ADCCON | 1;
/*等待转换完成*/
while(!(ADCCON & (1 << 15))); //如果第15位为0,那么整体上就是0,!0就是true,一直在循环等待,如果第15位为1,那么整体上就是非零,跳出循环,
/*读取转换结果*/
AdcValue = ADCDAT & 0xFFF;
/*将结果转换成实际的电压值mv,位数乘以精度(精度=1.8v/2^12=1800mv/2^12。。当转化结果为m时,实际电压就是m*精度。。。。也就是把电压分成好多好多份,每一份代表多少东西,你有多少份,就乘以这个东西)*/
AdcValue = AdcValue * 0.44; //单位为mv 毫伏
/*打印转换结果*/
printf("AdcValue = %dmv\n",AdcValue); //mv是单位
}
return 0;
}
转动旋钮,电压会在0-1.8V之间慢慢变化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUmY66Ki-1681907855979)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NtuTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
手机上的时间是怎么获取的?网络的时间只是一个校准的作用!
RTC自己使用一个独立的时钟,不和其他的硬件共用。电路板的纽扣电池一般就是给RTC供电的,备用电源。
BCD码
:用四位二进制数表示一位十进制数。
alarm
:闹铃功能。当设置的闹铃预期时间与实际时间相等,激发闹铃功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ExSIGk7O-1681907855980)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230419202625481.png)]
原理简述
:
晶振通过分频器分频后降到1Hz,即1秒输入信号1次,信号给秒寄存器做累加,当累加到60给分寄存器发信号让其累加,当分寄存器累加到60给小时寄存器发信号,其会累加,以此类推实现实时时钟。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rospIkD3-1681907855980)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230330160847489.png)]
BCD码:用四位二进制表示一位十进制。RTC是用BCD码存储的,所以读取之后要转化为十进制。
Leap Year:支持闰年的判断,并且做出相应的调整
Alarm:可以设定一个时间,到了时间之后产生相应的信号,比如中断信号。
independent power Pin:独立电源引脚,有备用电池的话,就得有相应的引脚。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IP4ILLgH-1681907855980)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTREadasds5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
用BCD数表示则000000100011表示023(不是用普通的二进制转十进制的方法(是35),这个是四位四位转的,不一样),四位二进制表示1位十进制数。没有千位,因为千位默认是2,有生之年不会改变没必要来浪费资源。023表示2023年。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-twyOWUV6-1681907855980)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NTRdsE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9DKDDm8f-1681907855981)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4sfdsubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
月的十位为啥只有一位,不是说四位四位表示吗?因为月的十位只有0或者1
上面有问题,0x10070080应该是表示星期的,0x1007007C表示日,这里说明书写反了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46sZy4EB-1681907855981)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZGsad4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DniUQWBz-1681907855981)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4usV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
上面这个如果锁住了就不可以改时间了。养成好的习惯。
#include "exynos_4412.h"
int main()
{
unsigned int OldSec = 0, NewSec = 0;
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息,十六进制正好也是对应4个二进制数(区别就是bcd没有字母),因此可以直接用十六进制表示bcd码*/
RTC.BCDYEAR = 0x023; //也可以直接赋值一个35
RTC.BCDMON = 0x12;
RTC.BCDDAY = 0x7; //因为手册写错了,所以把星期赋值给DAY,把天赋值给WEEk。也就是说你这个RTC.BCDDAY的地址
RTC.BCDWEEK = 0x31;
RTC.BCDHOUR = 0x23;
RTC.BCDMIN = 0x59;
RTC.BCDSEC = 0x50;
/*禁止RTC控制*/
RTCCON = RTCCON & (~(1));
while(1)
{
//实现1秒打印1次
NewSec = RTC.BCDSEC;
if(OldSec != NewSec)
{
printf("20%x-%x-%x %x %x:%x:%x\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC); //%x是十六进制格式
OldSec = NewSec;
}
}
return 0;
}
RTC除了显示时间还可以设置特点时间来执行某段程序
有源蜂鸣器
:高电平响,低电平不响。一般使用GPIO来控制给高低电平。
无源蜂鸣器
:加交变电流后,高低电平频繁变化,高电平时线圈接通电生磁,吸引永磁铁,低电平时无电生磁,排斥永磁铁,以此产生永磁铁的振动,当振动频率在20-20000Hz之间人耳能听到声音。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJmLLjuC-1681907855982)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ub0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
如果要产生蜂鸣的话,那么必然需要产生振动,就是一会儿动,一会儿不动,如此快速地反复就会产生声音了。要达成这样的效果,就需要GPIO控制一会儿高电平,一会儿低电平。。而且中间需要间隔,所以需要delay。
通过Delay和GPIO的控制产生高低电平,并产生一定的间隔。由于Delay函数需要CPU来执行,太耗资源,因此我们使用PWM的方式去产生高低电平。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQLmKBRh-1681907855982)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
PWM在SOC上,由PWM生产高低电平信号而不用使用CPU,节约CPU资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7oAyeoh-1681907855982)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
频率是周期的倒数,比如这个0.5s周期的频率就是2HZ,
电路原理图分析
:通过Dev电路原理图可知,蜂鸣器正极连着VSYS,负极通过三极管接地。只要三极管输出高电平就导通接地,输出低电平就断开接地。高低电平的控制由MOTOR_PWM
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRysh0jx-1681907855983)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6LNTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
在4412上对应的是GPD0_0,timer0
4412有5个32位的PWM,timer 0,1,2,3包括了一个能驱动外部IO信号的功能,timer0有个可选的死亡区发生器的功能,它能支持一个大电流的设备。timer4没有外部引脚。我们实验选timer0
timers使用PCLK时钟源(100M)。timer0,1共享一个可编程的8位预分频器(所以它们必然是同一个分频,因为共用),它能为PCLK提供一级分频(1-256倍)。timer2,3,4共享另一个8位的预分频器。每个timer各自有一个二级分频器,它能提供2,4,8,16倍的二级分频。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUCBKRPM-1681907855983)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9mV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
PWM的原理
:先设置两个寄存器,分别是设置整个周期的和设置高电平时间的。把周期加载给递减计数器,并将输出变成低电平。递减完低电平时间后,将输出变成高电平。当递减到0,重新加载周期给递减计数器,如此往复产生高低电平。(就是一个寄存器用来设置整个周期的时间,一个寄存器用来设置高电平的时间。。低电平的时间就是周期时间减去高电平的时间。。比如线面159的周期,109的高电平,50的低电平。。。这个时间除了和周期时间相关还和递减的频率相关)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S39fFybh-1681907855984)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG90L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
下面左边是一级分频器,再右边是二级分频器,再右边是PWM控制器。。。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMmF3DrI-1681907855984)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLm111NzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
圈出的寄存器从上到下依次是:设置1级分频、设置2级分频、设置timer控制器、PWM0的周期设置、PWM0的占空比设置(高电平的时间)、PWM0的计数读取(存储递减计数器里面递减的值)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCkchyRM-1681907855984)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L054NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
设置1级分频的寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sl6LDCEk-1681907855984)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0111NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
timer 0和timer1 的一级分频是共用的,也就是一样的
设置2级分频
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HzuQ3nTy-1681907855985)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/79b4682ca5fd4e008f09b2498111f7db76c.png)]
设置PWM控制器:
auto reload:当一个周期结束会自动加载周期的值(比如159周期的159,重新开始递减)到递减计数器中
inverter:信号反转功能(高低电平反转)
manual update:打开后会将周期设置寄存器的值给递减计数器(第一个周期的时候,需要手动的装载进去,也就是把周期的值给递减计数器,之后变成0了之后,如果开启了auto reload的话,会自动地完成一开始的操作)
start\stop:开关timer0
设置周期、占空比,读取计数器的值的寄存器(只读)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AoBLsyw0-1681907855985)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nL111mNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
需求
:让蜂鸣器以500Hz振动产生声音
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚(第六章看引脚功能),先清零后赋值*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PCLK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为500HZ 因为上面设置的递减频率是1M,也就是1/(1M)s就可以减一次, 我们想让脉冲的周期是1/500s,也就是说每过1/500s就有一个重复的高低电平,所以递减计数器需要减的次数为(1/500)s/(1/(1M))=2000*/
PWM.TCNTB0 = 2000;
/*6.设置PWM0的占空比为50%*/
PWM.TCMPB0 = 1000;
/*7.将TCNTB0中的值(也就是上面设置的2000)手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新 只需要它装载一次,后面是自动*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
while(1)
{
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON & (~(1));
Delay(1000000);
}
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jPyRWkdc-1681907855985)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230401171704359.png)]
上面这个大圈是让CPU设置PWM,之后PWM就可以自动输出脉冲信号了,CPU不需要去管了
之后CPU执行while循环,蜂鸣器也会持续性地响
如果我想让蜂鸣器间断地响,也就是滴滴滴地响
那么while循环里面写一个延时
while(1)
{
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON & (~(1));
Delay(1000000);
}
return 0;
}
开启递减计数器,关闭递减计数器,有一段比较长的间隔,就会产生滴滴滴的声音
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6VtyxyU-1681907855986)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZGE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxsi7lVz-1681907855986)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第2步,主机发送一个字节数据指明从机地址(7位),和后续字节的传送方向(第0位是0表示主机给从机发送,是1表示从机给主机发送)
第4、5步的发送器和接收器指的是第2步中指明的发送方向后主动发送数据的一方和被动接收的一方
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fT48Olmj-1681907855986)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG22229nNTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ME9WJeUZ-1681907855986)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9yjnLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第一个字节肯定是主机发给从机的,但是后续就不知道了(就看谁是发送器谁是接收器,一旦确定了就不可以中途更改了)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RUcbMsfr-1681907855987)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ib4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CP1U7rJR-1681907855987)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nL0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
高电平是1,低电平是0;
收发双方用的是同一根时间基准,也就是SCL。。所以它们不会有啥时间的误差。。所以可以发送任意个字节,和之前的串口的不一样,之前串口(UART)一下子不能发太多,不然会有误差。
数据的发送和接收存在两个问题:
IIC怎么解决问题:
使用起始信号来区分是否开始发数据
在SCL低电平时发送1位数据,在SCL高电平时接收数据,1位1位发送和接收(也就是等完成一个一位数据的发送和接收再开始下一个一位数据的发送和接收)。。。这样子的话就可以区分00和0了,因为00经历了两个低电位,然而0只经历了一个低单位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hEmcZEd-1681907855987)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WzfvHqSa-1681907855987)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNdghjnzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9OOeVMk-1681907855988)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Lydtyudy9ibG9nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
如果要变换方向,得重新发送起始信号,重新开始。但是最好别停下来(发终止信号),因为主机可能会被别人抢走
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jd56jYSN-1681907855988)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmVfad0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lyfBqykZ-1681907855988)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibGasfaesyterht9nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ywnPMK5O-1681907855988)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNfafazZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
comparator是地址比较器,从机接收到地址和不和自己的地址一样,一样的话准备进行传输
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fyLYppt0-1681907855989)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNdwdwafrzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
上面是主机作为发送器的流程
上面是主机作为接收器的流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQZP6zkT-1681907855989)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
第一个寄存器:
中断:当主机发送完一个数据或接收完一个数据要产生中断
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qedCdTD-1681907855989)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ111G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
这里讲一下这个中断,第五位,如果设置成0,关闭中断功能。当I2C发送完(接收完)一个字节数据后,会产生一个中断信号(告诉你我已经发完(接收)一个啦)。第四位,中断挂起标志位,显示有没有发生中断,建立在第五位功能打开。。发送(接收)一个字节数据后,产生一个中断信号,中断挂起,第四位自动变成1。如果读到0了,说明数据还没有接收完或者发送完(因为如果完成了,会产生中断,第四位是1)。注意,你处理完一件事之后(比如SDA接收数据,完成之后,产生中断),得手动清零,你看后面的代码你就知道为啥要这样子了。
第二个寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypLWL2z6-1681907855989)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ123G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
其它寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WLacXpv3-1681907855990)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLm124NzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
该芯片在CD卡旁边,标着U21
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nAIok8B7-1681907855990)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9147nLmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZRT8MGd-1681907855990)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLm178NzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5ZEDs19-1681907855990)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9n147LmNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
MPU6050是出厂就是设计成从机的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EZiUrQAl-1681907855991)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ147G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
W:write
R: Read
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GKLE1Dnl-1681907855991)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmN178zZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWzKBNaM-1681907855991)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/b66bc9c2fa48468a8acd1238494f780d295.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GcpGaqLv-1681907855991)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nL158mNzZG4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3czhmhT5-1681907855992)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230403015419404.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g322wM9K-1681907855992)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/watermark%252Ctype_ZmFuZ3poZW5naGVpdGk%252Cshadow_10%252Ctext_aHR0cHM6Ly9ibG9nLmNzZ148G4ubmV0L0NTRE5fWGlhbg%253D%253D%252Csize_16%252Ccolor_FFFFFF%252Ct_70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9jkmQiD1-1681907855992)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/image-20230403015335821.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsAQGi2X-1681907855992)(https://raw.githubusercontent.com/xitlo/blog_img/master/img/11bcdad254e64d00b0501075c42b0dbe.png)]
#include "exynos_4412.h"
/****************MPU6050内部寄存器地址****************/
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x18(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress 0x68 //MPU6050-I2C地址
/************************延时函数************************/
void mydelay_ms(int time)
{
int i,j;
while(time--)
{
for(i=0;i<5;i++)
for(j=0;j<514;j++);
}
}
/**********************************************************************
* 函数功能:I2C向特定地址写一个字节
* 输入参数:
* slave_addr: I2C从机地址
* addr: 芯片内部特定地址(比如内部寄存器的地址)
* data:写入的数据
**********************************************************************/
void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位(置1))*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4))); //如果第四位是1,那么while(!1),程序往下面执行。如果第四位是0,说明这个字节数据还没有发送完,那么while(!0),在循环中等这个数据发送完。
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第三个字节数据(即要写入到MPU6050内部指定的寄存器中的数据)写入发送寄存器*/
I2C5.I2CDS = data;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位(置1))*/
while(!(I2C5.I2CCON & (1<<4)));
/*发送停止信号 结束本次通信*/
I2C5.I2CSTAT = 0xD0;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时*/
mydelay_ms(10); //等待停止信号发送完
}
/**********************************************************************
* 函数功能:I2C从特定地址读取1个字节的数据
* 输入参数: slave_addr: I2C从机地址
* addr: 芯片内部特定地址
* 返回参数: unsigned char: 读取的数值
**********************************************************************/
unsigned char iic_read(unsigned char slave_addr, unsigned char addr)
{
unsigned char data = 0;
/*对时钟源进行512倍预分频 打开IIC中断(每次完成一个字节的收发后中断标志位会自动置位)*/
I2C5.I2CCON = I2C5.I2CCON | (1<<6) | (1<<5);
/*设置IIC模式为主机发送模式 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xd0;
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+写位0)*/
I2C5.I2CDS = slave_addr<<1;
/*设置IIC模式为主机发送模式 发送起始信号启用总线 使能IIC发送和接收*/
I2C5.I2CSTAT = 0xf0;
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*将要发送的第二个字节数据(即要读取的MPU6050内部寄存器的地址)写入发送寄存器*/
I2C5.I2CDS = addr;
/*清除中断挂起标志位 开始下一个字节的发送*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*等待从机接受完一个字节后产生应答信号(应答后中断挂起位自动置位)*/
while(!(I2C5.I2CCON & (1<<4)));
/*清除中断挂起标志位 重新开始一次通信 改变数据传送方向*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*将第一个字节的数据写入发送寄存器 即从机地址和读写位(MPU6050-I2C地址+读位1)*/
I2C5.I2CDS = slave_addr << 1 | 0x01;
/*设置IIC为主机接收模式 发送起始信号 使能IIC收发*/
I2C5.I2CSTAT = 0xb0;
/*等待从机接收到数据后应答*/
while(!(I2C5.I2CCON & (1<<4)));
/*禁止主机应答信号(即开启非应答 因为只接收一个字节) 清除中断标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<7))&(~(1<<4));
/*等待接收从机发来的数据*/
while(!(I2C5.I2CCON & (1<<4)));
/*将从机发来的数据读取*/
data = I2C5.I2CDS;
/*直接发起停止信号结束本次通信*/
I2C5.I2CSTAT = 0x90;
/*清除中断挂起标志位*/
I2C5.I2CCON = I2C5.I2CCON & (~(1<<4));
/*延时等待停止信号稳定*/
mydelay_ms(10);
return data;
}
/**********************************************************************
* 函数功能:MPU6050初始化
**********************************************************************/
void MPU6050_Init ()
{
iic_write(SlaveAddress, PWR_MGMT_1, 0x00); //设置使用内部时钟8M
iic_write(SlaveAddress, SMPLRT_DIV, 0x07); //设置陀螺仪采样率
iic_write(SlaveAddress, CONFIG, 0x06); //设置数字低通滤波器
iic_write(SlaveAddress, GYRO_CONFIG, 0x18); //设置陀螺仪量程+-2000度/s
iic_write(SlaveAddress, ACCEL_CONFIG, 0x0); //设置加速度量程+-2g
}
/**********************************************************************
* 函数功能:主函数
**********************************************************************/
int main(void)
{
unsigned char zvalue_h,zvalue_l; //存储读取结果
short int zvalue;
/*设置GPB_2引脚和GPB_3引脚功能为I2C传输引脚*/
GPB.CON = (GPB.CON & ~(0xF<<12)) | 0x3<<12; //设置GPB_3引脚功能为I2C_5_SCL
GPB.CON = (GPB.CON & ~(0xF<<8)) | 0x3<<8; //设置GPB_2引脚功能为I2C_5_SDA
uart_init(); //初始化串口
MPU6050_Init(); //初始化MPU6050
printf("\n********** I2C test!! ***********\n");
while(1)
{
zvalue_h = iic_read(SlaveAddress, GYRO_ZOUT_H); //获取MPU6050-Z轴角速度高字节
zvalue_l = iic_read(SlaveAddress, GYRO_ZOUT_L); //获取MPU6050-Z轴角速度低字节
zvalue = (zvalue_h<<8)|zvalue_l; //获取MPU6050-Z轴角速度
printf(" GYRO--Z :Hex: %d \n", zvalue); //打印MPU6050-Z轴角速度
mydelay_ms(100); //老师没讲,我想就是让输出的时间间隔大一点吧?不至于很密接的输出
}
return 0;
}