1. 汇编LED原理

为什么使用Cortex-A汇编

  • 使用汇编初始化soc外设
  • 使用汇编初始化DDR,I.MX不需要,因为它内部的96k ROM中存放了自己编写的启动代码,这些代码可以读取DDR配置信息
  • 设置sp指针,一般指向ddr,设置好C语言的运行环境

Alpah开发板原理硬件分析

LED0为低电平,DS0就会亮,再看一下LED0接到哪里

由图可知,LED0接到GPIO 3,可以查找参考手册了

2. 初始化流程

stm32初始化流程

  • 使能GPIO时钟
  • 设置复用,但是32的LED灯默认就是GPIO的功能
  • 配置GPIO的电气属性
  • 使用GPIO,输出高低电平

I.MX6ULL初始化流程

  1. 首先找到时钟控制模块CCM

这七个寄存器控制6ULL所有时钟外设使能

可以看到11就是任何模式下都使能,除了STOP

可以把这七个时钟都使能一遍(0xFFFFFFFF)(32位)

  1. MUX复用,首先找到GPIO1_IO3的复用

看一下前四位,是复用为什么管脚

由图中可以看到,需要设为GPIO的是0101

  1. PAD电气属性

  • SRE(0):压摆率,0为低压摆率,1为高压摆率。压摆率是指电平跳变所需时间。时间越小波形越抖,压摆率过高。如果你使用的产品要过EMC(电磁兼容性,定义为设备和系统在其电磁环境中能正常工作且不对环境中任何事物构成不能承受的电磁骚扰的能力)的话就要使用低压摆率,如果要是高速通信就是高压摆率。
  • 1-2:保留,没用
  • DSE(3-5) : 当IO用作输出的时候用来设置IO的驱动能力,001是R0,3.3V下R0是260欧姆,1.8v是150欧姆,DDR是240欧姆, 010电阻减半,以后会越来越小,电阻越来越小,驱动能力越来越强。内阻小,那分压就会小,这样外部负载的分压就会大,外部负载的电阻就会大,这样选择性比较好,又可以大又可以小。
  • SPEED(6.7) : 速度,没什么可说的
  • 8-10: 没用
  • ODE : 开路输出,1开启0关闭
  • PKE : 用来使能禁止上下拉保持
  • PUE : 0保持,1上下拉
  • PUS (14.15): 上下拉配置
  • HYS :电气属性寄存器
  1. 配置GPIO功能,设置输入输出

都是32位,每一个位代表一个GPIO的输入输出

  • DR:可以向DR的指定bit来实现高低电平
  • GDIR:控制设置GPIO是输入还是输出
  • PSR:只读状态寄存器
  • ICR1:中断,设置中断的触发电平,低电平,高电平,上升沿下降沿
  • ICR2:中断,因为是两个位控制一个GPIO,所以两个ICR
  • IMR:中断掩码,通过它控制中断的使能关闭
  • ISR:中断状态寄存器,判断GPIO对应的中断有没有发生

综上,对于点灯,就是GDIR的bit3设置为输出模式,DR控制高低电平。

3. ARM汇编基础

作用:

  • 恢复现场保护现场sp指针
  • 初始化DDR寄存器

我们使用的GCC交叉编译器,汇编需要符合GNU语法,GNU汇编语法适用于所有的架构,每条语句有三个可选地方:

label: instruction @ comment

label即标号,表示地址位置,有些指令前面可能会有标号,标号也可以用来表示数据地址,后面以:结尾。任何以:结尾的标志符都是一个标号

instruction是指令,也就是汇编指令或者伪指令

@标号,表示后面的是注释,comment是注释内容。

比如:

add:
MOVS R0,#0X12 @设置R0 = 0X12

add就是标号,下面就是指令和注释

会变系统预定义了一些段名:

  • .text代码段
  • .data初始化的数据段
  • .bss未初始化的数据段
  • .rodata只读数据段

我们也可以自己使用.section来定义一个段,每个段以段名开始,以下一段名或者文件结尾结束

.section .testsection @定义一个testsection段

汇编程序的默认入口标号是_start,也可以在链接脚本中ENTRY来指明入口点,比如

.global _start

_start:
ldr r0,=0x12 @r0 = 0x12

.global是伪操作,表示_start是一个全局标号,类似于C语言里面的全局变量一样,常见的伪操作有:

  • .byte 定义单字节数据,比如 .byte 0x12
  • .short 定义双字节数据, 比如 .short 0x1234
  • .long 四字节 .long 0x12345678
  • .equ 赋值语句,.equ num,0x12 等于 num = 0x12
  • .align 数据字节对齐, 比如 .align 4字节对齐
  • .end 表示源文件结束
  • .global 定义一个全局标号

GNU也支持函数:

函数名称:

​ 函数体

​ 返回语句

例如:

/*未定义中断*/
Undefined_Handler:
ldr r0,=Undefined_Handler
bx r0 @返回指令 /*SVC*/
SVC_Handler:
ldr r0,=SVC_Handler
bx r0

3.1 处理器内部数据传输指令

常见的操作有:

  • 将数据从一个寄存器传递到另一个寄存器
  • 将数据从一个寄存器传递到特殊寄存器,如CPSR和SPSR
  • 将立即数传递到寄存器

MOV指令

将R1里面的数据复制到R0中,或者一个立即数(常数)复制到R0中

MOV R0 R1
MOV R0 #0x12 @立即数前面加一个#

MRS指令

用于将特殊寄存器(如CPSR和SPSR)中的数据传递到通用寄存器,即要读取通用寄存器里面的数据必须用MRS

MRS R0,CPSR @将CPSR里面的数据读取到R0中

MSR指令

用于将通用寄存器的数据传递给特殊寄存器(如CPSR和SPSR)

MSR CPSR,R0  @将CPSR里面的数据读取到R0中

3.2 存储器访问指令

ARM中不能直接访问存储器,比如RAM中的数据,我们用汇编来配置寄存器的时候需要借助存储器访问指令,一般先写到通用寄存器中,然后借助存储器访问指令将通用寄存器中的数据写入到寄存器中,读取寄存器也是一样。

LDR

LDR主要用于存储加载数据到寄存器Rx中,LDR也可以将一个立即数加载到寄存器Rx中, LDR加载立即数的时候要用等于不是#

LDR R0, =0x0209C004 @将存储器地址加载到R0中,即R0 = 0x0209C004
LDR R1, [R0] @读取地址的数据到R1中

STR

LDR是从存储器读取数据,STR是将数据写入存储器中。

LDR R0, =0x0209C004
LDR R1, =0x20000002 @R1 = 0x20000002
STR R1, [R0] @将R1中的值写入到R0保存的地址中

如果按照字节半字就在后面加上B或者H

例子 a = b, a的地址为0x20, b为0x30

LDR R0, =0x30
LDR R1, [R0] LDR R0, =0x20
STR R1, [R0]

3.3 压栈和出栈指令

通常在A函数中调用B函数,当B函数执行完之后继续A,所以需要保存现场和恢复现场,现场就是寄存器的值,用到POP和PUSH,可以一次性操作多个存储器,他们利用当前的栈指针来生成地址

如果当前SP指针指向0x80000000

PUSH {R0~R3, R12}

此时SP就是原来-4*5 0x7FFFFFEC。

出栈也是从栈顶出栈,原理类似。

也可以使用STMFD SP!和LDMFD SP!代替上面的指令。

STM和LDM对应STR和LDR,就是多寄存器的,多个连续数据。

FD是满递减的意思。 地址是向下增长的,编号小对应低地址,编号大对应高地址。

3.4 跳转指令

  • 直接使用跳转指令B,BL,BX等
  • 直接向PC寄存器里面写入数据

B

最简单的跳转指令,B指令会将PC寄存器的值设置为跳转目标地址,一旦执行B指令,ARM就会立即跳转到指定的目标地址。如果调用的函数不会再返回到原来执行地方,可以用B指令

_start:

	ldr sp,=0x80200000
b main

常用的初始化C语言环境

BL

BL在跳转之前会在寄存器LR中保存当前PC的值,所以可以通过将LR的值重新加载到PC寄存器中来恢复现场,即保护现场,跳转到C中处理中断,再恢复现场。

push {r0, r1}
cps #0x13 @进入SVC模式,允许其他中断再次进去 bl system_irqhandler @加载函数到r2中
cps #0x12 @进入IRQ模式
pop {r0, r1}
str r0,[r1, #0x10] @写EDIR

3.5 其他运算

算数运算

逻辑运算

4. Cortex-A内核寄存器组

除了R0到R14,还有R15(PC)程序计数器,CPSR当前状态寄存器和SPSR备用状态寄存器。R13是sp,R14是LR寄存器。

LR寄存器

恢复现场:

MOV PC, LR @将LR保存的值传递给PC

或者:

PUSH {LR} @压栈
POP {PC} @弹栈,并将值赋给PC

当异常发生以后,该异常模式对应的 R14 寄存器被设置成该异常模式将要返回的地址,R14 也可以当作普通寄存器使用。

程序计数器PC

pc = 当前执行位置+8个字节

解释一下:

5. 汇编代码

.global _start 

_start:
@ 使能所有时钟
@ CCM_CCGR0的内存地址为20C_4068
ldr r0,=0x020c4068 @取得时钟0的地址
ldr r1,=0xffffffff @要赋的值
str r1, [r0] @赋值 ldr r0,=0x020c406c @取得时钟1的地址
@或者 add r0, r0, #0x04
str r1, [r0] @赋值 ldr r0,=0x020c4070 @取得时钟2的地址
str r1, [r0] @赋值 ldr r0,=0x020c4074 @取得时钟3的地址
str r1, [r0] @赋值 ldr r0,=0x020c4078 @取得时钟4的地址
str r1, [r0] @赋值 ldr r0,=0x020c407c @取得时钟5的地址
str r1, [r0] @赋值 ldr r0,=0x020c4080 @取得时钟6的地址
str r1, [r0] @赋值 /* 配置IO复用 */ @ ctrl+shift+A
ldr r0,=0x020e0068 @MUX复用地址
ldr r1,=0x5 @要赋的值,16进制,0101是二进制
str r1, [r0] @赋值 /* 电气属性
* bit0 : 0 低速率
* bit5-3 : 110 R0/6驱动能力
* bit7-6 : 10 100MHz速度
* bit11 : 0 关闭开路输出
* bit12 : 1 使能上拉保持
* bit13 : 0 保持
* bit15-14 : 00 默认100K下拉
* bit16 : 0 关闭hys
* 综上:
* 0000_0000_0000_0000_0001_0000_1011_0000
* 0x000010C0
*/
ldr r0,=0x020e02f4 @电气属性地址
ldr r1,=0x10b0
str r1, [r0] /* 设置GPIO */
@ GPIO1_GDIR的bit3
ldr r0,=0x0209c004 @GDIR
ldr r1,=0x8 @8是1000, 输入输出设置
str r1, [r0] /* 打开LED GPIO1_IO03为0 */
ldr r0,=0x0209c000 @DR
ldr r1,=0xfffffff7 @默认全是1,要配成0
str r1, [r0] /* 程序执行到这的时候,不知道会有什么事情,所以要有个死循环 */
loop:
b loop

6. 编译程序

使用arm-linux-gnueabihf-gcc把所有的.s文件编译成点o

arm-linux-gnueabihf-gcc -g -c led.s -o led.o
  • -g产生调试信息,

  • -c是选择编译源文件,并且不链接

使用arm-linux-gnueabihf-ld将所有的.o文件链接为elf格式的可执行文件

把所有的o链接到一起,要有起始位置。本实验链接的时候要链接起始地址,链接起始地址就是代码运行的起始地址,或者保存的起始地址(一定是运行的,一般运行的和保存的是一个地址)

对于6ULL,链接起始地址应该指向RAM地址。

RAM分为内部RAM和外部RAM,DDR。内部为0x90000-0x91FFFFF,外部DDR中, 对于开发板0x80000000-0xA0000000(512MB)。

起始地址为0x8780000,后面UBOOT也是这个,所以要统一起来。要使用DDR必须要初始化DDR,bin文件不能直接烧写到mmc等外置存储中启动运行,需要添加一个头部,头部包含了DDR初始化参数。bootrom会从SD卡,EMMC等外置存储中读取头部信息,初始化DDr,并且将bin拷贝到链接起始地址,bin的运行地址一定要和链接起始地址一致。位置无关代码除外。

arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
  • -Ttext指定链接起始地址

使用arm-linux-gnueabihf-objcopy将elf文件转化为bin文件

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
  • -O 以什么形式输出
  • -S 表示不要复制源文件的重定位信息和符号信息
  • -g 不要复制源文件的调试信息

使用arm-linux-gnueabihf-objdump elf转为汇编,反汇编

arm-linux-gnueabihf-objdump -D led.elf > led.dis
  • -D表示反汇编所有的段

7. 烧写

将bin文件烧写到mmc中。

在Ubuntu下烧写SD卡:

插入U盘链接

将bin文件烧写到SD卡绝对地址上,对于I.MX而言必须在bin文件添加头部,使用imxdownload

两次插拔可知

烧在sdb1上

先给imx可执行权限

烧写成功

添加了头部的.bin文件就是load.imx

8.Makefile

led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis clean:
rm -rf *.o *.bin *.elf *.dis

9.download.sh

chmod 777 imxdownload
./imxdownload led.bin /dev/sdb

led汇编点灯的更多相关文章

  1. 5.汇编实现裸机LED

    首先:操作LED就要操作GPIO  alpha的芯片是NXP的IMX6ULL  其GPIO和STM32的命名有所区别 可以看到IMX6ULL的GPIO以其功能进行命名,对应上图中PAD之后的部分 即G ...

  2. 嵌入式linux——点亮led灯(二)

    刚才在jz2440板子上写了一个点亮中间led的程序,前前后后十几分钟才好.最终代码 本节内容: 1. 汇编点灯 2. C点灯 3. 参数选择点灯 4. 按键点灯 1. 汇编点灯 .text .glo ...

  3. Part10-C语言环境初始化-一跃进入C大门lesson3

    1.跳转到c代码 因为内存中的代码来自于垫脚石SRAM,他们是相同的. 采用绝对跳转方式来完成. 因为我们是从汇编代码跳转到c语言的程序,所以我们要提前准备一个main.c文件. 修改makefile ...

  4. 002_linux之点灯(汇编深度解析)

    1.      开发板采用韦山东的开发板 2.      芯片CPU三星S3C2440A 3.  控制引脚:GPF4 4.  linux操作系统 5. 芯片手册下载地址:https://eyun.ba ...

  5. u-boot移植总结(二)LED点灯调试 和 u-boot加载地址

    (一)LED点灯调试 FL2440电路总共有4个LED0,LED1,LED2,LED3,分别接到板子GPB5,GPB6,GPB8,GPB10引脚.通过设置三个寄存器GPBCON(0x56000010) ...

  6. Tiny4412汇编流水灯代码,Tiny4412裸机LED操作[1]

    从今天开始就正式进入到tiny4412的开发学习中了,今天主要看了一下Tiny4412的启动流程及存储器映射及Exynos4412数据手册,用汇编写了一个跑马灯程序(后续会有C语言版本的出来),先说一 ...

  7. ARM入门实践(一)----Mini6410上最简单的LED点灯裸机程序

    Mini6410上最简单的LED点灯裸机程序 : 实验环境: 根据友善教程,要用ADS,据说现在都不用这个了,但是为了打开友善给的mcp工程,就下了一个,Win7下弄上兼容模式和管理员权限,再下一个S ...

  8. 四、使用汇编编写LED裸机驱动

    1. 确定硬件连接 打开OK6410底板电路图,找到LED,可以发现NLEDx为0时LED点亮. 找到LED的控制引脚,发现LED控制引脚通过连接器连到了核心板: 打开核心板电路图,找到对应的连接器中 ...

  9. 新的开始——LED灯汇编机器码的点亮方式

    在几个月前看2440视频的时候,发现太多知识欠缺,购买开发板期间补习makefile,linux,arm汇编和arm构架之后,现在重新开始学习. 先看板子LED硬件连接图: 可以看到LED 1,2,4 ...

随机推荐

  1. 题解 Hero meet devil

    题目传送门 题目大意 给出一个长度为 \(n\) 的字符串,对于每个 \(k\in [0,n]\),求出有多少个长度为 \(m\) 的字符串满足两者最长公共子序列长度为 \(k\). \(n\le 1 ...

  2. python中冒泡排序代码实现

    1.冒泡排序代码如下图: #冒泡算法l=[12,4,56,10,6,2]for i in range(0,6): for j in range(i+1,6): if l[i]>l[j]: a=l ...

  3. Noip模拟44 2021.8.19

    比较惊人的排行榜 更不用说爆零的人数了,为什么联赛会这么难!!害怕了 还要再努力鸭 T1 Emotional Flutter 考场上没切掉的神仙题 考率如何贪心,我们把黑色的条延长$s$,白色的缩短$ ...

  4. 详解DNS域名解析系统(域名、域名服务器[根、顶级、授权/权限、本地]、域名解析过程[递归与迭代])

    文章转自:https://blog.csdn.net/weixin_43914604/article/details/105583806 学习课程:<2019王道考研计算机网络> 学习目的 ...

  5. Envoy实现.NET架构的网关(二)基于控制平面的动态配置

    什么是控制平面 上一篇我们讲了文件系统的动态配置,这次我们来看看通过Control Panel来配置Envoy.控制平面就是一个提供Envoy配置信息的单独服务,我们可以通过这个服务来修改Envoy的 ...

  6. Go并发编程--Mutex/RWMutex

    目录 一.前言 二. Mutex 2.1 案例 三. 实现原理 3.1 锁的实现模式 3.2 Go Mutex 实现原理 3.2.1 加锁 3.2.2 解锁 四. 源码分析 4.1 Mutex基本结构 ...

  7. tar 解压分割压缩文件

    被分割后的压缩文件必须先合并成一个压缩文件才能正常的解压. 第一步.合并压缩文件 第二步.正常解压 $ls TINA-1.3.tar.gzaa TINA-1.3.tar.gzab TINA-1.3.t ...

  8. hdu 5083 Instruction (稍比较复杂的模拟题)

    题意: 二进制指令转汇编指令,汇编指令转二进制指令. 思路: 额,条理分好,想全,思维不能乱. 代码: int findyu(char yu[50],char c){ int l=strlen(yu) ...

  9. dns+nginx实现多虚拟主机

    借鉴于朋友的需求,公司需要启用域名访问内部的业务系统,现实情况是内部的业务系统目前使用的是单主机,单nginx多端口的方式再运行,朋友最终想实现启用域名方式问题,且域名不需要用户手工输入端口号 两种思 ...

  10. 使用jax加速Hamming Distance的计算

    技术背景 一般认为Jax是谷歌为了取代TensorFlow而推出的一款全新的端到端可微的框架,但是Jax同时也集成了绝大部分的numpy函数,这就使得我们可以更加简便的从numpy的计算习惯中切换到G ...