裸机开发和单片机的区别:
|--------------|-------------------------------------------------------------------------|----------------------------------------------------------|
| 对比维度 | ARM 裸机开发(以 IMX6ULL 等 Cortex-A 系列为例) | 单片机开发(以 51/STM32 等 Cortex-M 系列为例) |
| 核心架构 | 基于 Cortex-A 系列,带MMU(内存管理单元)、Cache 缓存,支持虚拟内存,复杂多核 / 多总线架构 | 基于 Cortex-M/8051 等,无 MMU、无 Cache,单总线极简架构,纯实时控制 |
| 启动流程 | 必须手动编写启动文件(start.S),完成关看门狗、时钟树配置、栈初始化、重定位数据段、清 BSS 段后,才能跳转到 main 函数 | 上电直接执行,无需手动写启动文件,复位后直接进入 main 函数,流程极简 |
| 时钟配置 | 必须手动配置PLL、总线分频、时钟树,所有外设时钟需单独使能,否则外设无法工作 | 多数外设默认时钟已配置,仅需简单分频 / 使能,时钟树结构简单 |
| 开发方式 | 几乎无官方标准库,全程寄存器操作,所有驱动(UART/GPIO/ 定时器等)需从零编写 | 官方提供完善 HAL/LL 库 / 标准库,直接调用库函数开发,无需手动操作寄存器 |
| 工具链与编译 | 必须使用交叉编译工具链(如 arm-linux-gnueabihf-gcc),生成 bin 文件,通过 SD 卡 / USB 下载 | 直接使用 IDE(Keil/STM32CubeIDE)本地编译,通过 ST-Link/J-Link 一键下载调试 |
| 资源与寻址 | 4GB 完整寻址空间,片上资源丰富(大 Flash/RAM、多外设),外设地址分散,需复杂地址映射 | 小地址空间,Flash/RAM 容量小,外设直接挂内核,地址映射简单 |
| 应用场景 | 底层原理学习、Linux 驱动开发前置、高端嵌入式产品裸机开发 | 工业控制、物联网终端、小家电等实时控制类小项目 |
| 学习门槛 | 门槛高,需掌握 ARM 架构、汇编、时钟原理、链接脚本等底层知识 | 门槛低,上手快,专注应用层控制,无需深入架构原理 |
| 调试方式 | 多为离线调试,依赖串口打印、JTAG/SWD 调试成本高 | 支持在线实时调试,断点、变量查看等功能完善,调试便捷 |
| 可扩展性 | 可直接移植 Linux 系统,为上层系统开发打基础 | 仅支持裸机 / RTOS,无法移植完整 Linux 系统 |
DAY1
1 .RISC和CISC 是什么?
RISC(精简指令集)
指令少,简单,长度固定·
执行快,功耗低
常见代表:ARM,RISC-V
比如imx6ull /ARM9/Cortex-A都是RISC
CISC(复杂指令集)
指令多,复杂,长度不固定
功能强,但慢,费电
代表:x86(Intel / AMD )
总结ARM = RISC CPU = CISC
- pc lr sp cpsr spsr 寄存器的作用
pc 存下一条指令的地址。
lr 函数调用的时候,存返回地址----函数跑完,回通过他回到原来的位置
sp 栈顶指针,指向栈的顶部-----存临时变量,函数现场
cpsr 当前程序状态寄存器 -------存标志位(正负,进错位),工作模式,中断开关
spcr 进入异常时,备份程序的当前运行状态
3 .什么是MMU?
作用 :
管理虚拟内存到物理内存的映射
裸机必须关闭
linux驱动必须打开
4 . 什么是cache icache dcache ?
cache: CPU内部高速缓存
解决:CPU很快,内存很慢-----用cache 缓冲
icache :存指令
dcahe:存数据
cache 就是CPU的临时小书桌,比内存快几十倍
5 .单总线 VS多总线
单总线:
所有设备用一根总线
简单,便宜,慢,会堵塞
多总线;
高速设备一条,低速设备一条
快,不堵,结构复杂
总结:一个不堵,一个堵
6,AHB总线 VS APB总线
AHB:高速总线
给CPU RAM LCD 网卡
APB:低速总线
给 串口,定时器,GPIO, SPI, 12C
7 .编译程序需要哪几个步骤?
预处理:头文件,宏的展开
编译:生成汇编代码.s
汇编;生成机器码.o
链接:生成可执行程序
8 . imx6ull 内核与指令集
内核:cortex-A7
指令集:ARMv7-A
- ARM有几种工作模式?
User:用户工作模式
FIQ:紧急中断
IRQ:外部中断
SVC; 复位或软中断
abort:存储异常
undef 执行未被定义的指令
system 系统模式----用户模式的特权模式
Linux 内核跑在 SVC
应用程序跑在 User
- ARM的异常处理流程
备份cpsr到spsr
lr保存返回地址
关闭中断(视为异常类型)
跳去异常向量表
执行异常处理
恢复spcr到cpsr---跳回lr继续执行
11 . ARM9 和 Cortex-A 各有多少寄存器?
ARM9: 37个
Cortex-A : 40个
DAY2
- 立即数如何判断?
数值范围在0-0xFF之间的8位基础数,并且最右边含有偶数个0.
- b / bl / bx 指令的区别?
b 无条件跳转,不保存lr.正负32MB,用于死循环,长跳转(不返回)
bl 带链接跳转,保存下一条指令地址存入正负32MB,用于函数调用(需要返回)
bx 带状态切换跳转(blx:可保存lr),任意32位地址,用于切换ARM状态,跳转绝对地址。
- 栈的分类及特性?
满递减栈:由高到低存储,sp指向最后一个有效数据 ARM默认 *--psr,先减再写数据。
空递减栈:由高到低存储,sp指向下一个空闲的位置 少见
满递增栈:由低到高存储 sp指向最后一个有效数据 x86常用
空递增栈:由低到高存储 sp指向下一个空闲位置 少见 先写数据,再挪动指针
- ARM汇编调用C函数,C函数调用汇编函数有什么规则?
ARM汇编调用C函数时:
import main
bl main
规则1:传参
前4个参数:用r0-r3(超过的话使用栈来传递)
返回值:用r0返回
规则2:栈操作规范
调用前:压栈保护现场:stmfd sp!,{r1-r12,lr}
掉用后:出栈恢复现场: ldmfd sp!,{r1-r2,lr}
C函数调用汇编里面的函数的时候
在main.c中的声明:
extern int asm_max(int a,int b);
_as,max(10,20)
在汇编程序中:
export asm_max
规则1:寄存器使用规范
参数接收:r0-r3接收C传递的前4个参数
返回值:用 r0 返回
返回指令:用bx lr返回,恢复C程序的执行
SOC:
核心定位:核心板的【大脑+主控】,相当于电脑的CPU
·本质:是把CPU,GPU,总线控制器,外设接口(UART,SPI,GPIO,I2C)等全部集结在一颗芯片里面的【超级芯片】
·我们所写的汇编启动代码,C语言程序,Linux系统,全部都在SOC中运行,他是整个系统的绝对核心。
·我们所写的代码里:中断向量表,复位函数,栈初始化,时钟配置,都是给soc写的,用来初始化这颗主控芯片。
RAM / DDR3 512MB (运行内存)
·核心定位:核心板的(运行内存),相当于内存条------读写速度快,掉电数据丢失
·SOC可以直接从内存上面读取数据
·用来存放程序的临时变量,栈空间,堆空间(代码里面的 ldr sp, =0x84000000),栈就放在DDR3里面
·Linux在运行的时候 ,所有进程,内存数据都存在这里。
EMMC / NAND FLASH 8GB(存储芯片 )
核心定位:核心板的【硬盘】,相当于电脑的SSD/机械硬盘
·本质:掉电数据不丢失,永久存储数据,文件,系统。
核心作用:
·用来存放我的裸机程序,uboot,linux内核,设备树,根文件系统
·相当于系统的仓库,芯片上电后,从EMMC里把程序加载到DDR3中运行
·8GB 容量用来存系统,文件,数据。
关键特性:
·属于NAND FLASH中的一种(EMMC是封装好的NAND Flash ,带主控)
·读/ 写速度比DDR3慢,掉电数据不丢失
核心板接口(邮票孔/ 插针接口)
本质:·把SOC的所有外设引脚(GPIO , UART , SPI , I2C , LCD , 网口)引出来,用来接底板或外设。
开始进行代码操作:
·配制异常向量表
·所有时钟初始化R0-R6-----打开所有的外设,相当于提前开
初始化led的模式
置电平,来控制led
复位入口:
SOC 通过读取控制灯的寄存器来点灯
DAY3
|------------------------------------------|-------------------------------|-----------------------------------------------|
| 工具 | 定义 | 核心作用 |
| gcc(arm-linux-gnueabihf-gcc) | GNU 交叉编译器,用于将 C / 汇编源码编译为目标文件 | 对.c/.S源码执行语法检查、代码生成,输出.o目标文件,仅编译不链接 |
| ld(arm-linux-gnueabihf-ld) | GNU 链接器,用于整合多个目标文件 | 按照链接脚本规则,将多个.o文件链接为 ELF 格式可执行文件,分配内存地址、解决函数引用 |
| objcopy(arm-linux-gnueabihf-objcopy) | GNU 格式转换器,用于 ELF 与二进制互转 | 将带符号 / 调试信息的 ELF 文件,转为裸机可烧录的纯二进制.bin文件,去除冗余信息 |
| objdump(arm-linux-gnueabihf-objdump) | GNU 反汇编器,用于代码分析调试 | 将 ELF 文件反汇编为汇编代码,用于查看代码逻辑、验证链接地址、排查错误 |
- 链接脚本 imx6ull.lds的核心配置
·程序入口:ENTRY_(_start),指定芯片上电第一行执行的代码(汇编启动文件的_start标签)
·代码段地址:-Ttext 0x87800000,指定代码在 IMX6ULL DDR 中的运行地址。
·段分配:定义 . text(代码段)分为:
rodata(只读数据段)
data(数据段),
bss(未初始化的数据)
·链接顺序:指定 start.o 优先链接,保证复位入口在代码最前端。
·栈地址:配置C语言运行所需的栈的空间地址,为C语言执行提供基础
- beep和led 实验
·led实验:配置GPIO_IO03的MUX(复用功能),PAD(电气属性),再通过GPIOS输出高低电平来控制。
·beep实验:配置对应的GPIO的MUX,PAD,再通过GPIO输出低电平,导通PNP三极管,驱动蜂鸣器发声。
完整的流程:编译(gcc)----->链接(ld)------->格式转换(objcopy)------>烧录到SD卡上,上电后程序运行,灯亮,器响。
DAY4:
上电复位
↓
进入 start.S 的 _start 中断向量表入口
↓
跳转到 _reset_handler 复位初始化
↓
【1. 配置 CP15 协处理器】
关闭 ICache、配置内存相关
→ 目的:保证中断向量表不被缓存干扰
↓
【2. 设置 IRQ 模式栈】
cps #0x12
ldr sp, =0x86000000
↓
【3. 设置 System 模式栈(C语言用)】
cps #0x1F
ldr sp, =0x84000000
↓
【4. 开启 CPU 总中断】
cpsie i
↓
【5. 清空 bss 段(全局变量清0)】
bl _bss_clear
↓
【6. 进入 C 语言 main 函数】
b main
↓
=====================================
进入 main
↓
【1. 中断系统初始化】
system_interrupt_init
↓
├─ 设置中断向量表基地址 VBAR = 0x87800000
└─ 初始化 GIC 中断控制器
↓
【2. 打开所有外设时钟】
enable_clocks
↓
【3. 初始化 LED、蜂鸣器】
led_init、beep_init
↓
【4. 按键中断初始化(核心配置)】
key_init
↓
├─ 配置 GPIO1_IO18 复用为 GPIO
├─ 配置引脚电气属性
├─ 设置为输入模式 GDIR
├─ 设置中断触发方式 ICR2
├─ 打开 GPIO 中断屏蔽 IMR
├─ 设置 GIC 中断优先级
└─ 使能 GIC 对应中断号
↓
【5. 主循环等待中断】
while(1) { delay(); }
=====================================
↓
【外部事件:按键按下】
↓
GPIO1_IO18 产生中断信号
↓
中断发送到 GIC 中断控制器
↓
GIC 仲裁优先级 → 发给 CPU
↓
CPU 检测到 IRQ 中断请求
↓
硬件自动跳转到 start.S 的 _irq_handler
↓
=====================================
【中断响应流程】
↓
- 修正返回地址
sub lr, lr, #4
↓
- 保护现场(压栈 R0~R12, LR)
stmfd sp!, {r0-r12, lr}
↓
- 从 GIC 读取当前中断号
↓
- 进入 System 模式,方便调用 C 函数
↓
- 保存 LR,调用 C 语言中断处理函数
bl system_interrupt_handler
↓
=====================================
【C 语言中断服务流程】
↓
- 判断中断号是否为 GPIO1 组中断
↓
- 判断是否是 GPIO1_IO18 触发的中断
↓
- 执行中断动作:led_on()
↓
- 清除中断标志位 ISR
(必须,否则会重复进中断)
↓
中断函数执行完毕,返回汇编
=====================================
↓
【中断返回流程】
↓
- 通知 GIC 中断处理完成
↓
- 恢复现场(出栈 R0~R12, PC)
ldmfd sp!, {r0-r12, pc}
↓
- CPU 自动恢复 CPSR、返回主程序断点
↓
回到 main 的 while(1) 继续等待下一次中断
DAY5:
- 什么时PPL, Prescaler, FPD,他们各自的用途是什么?
·PPL:锁相环电路-----倍频
·Prescaler: 预分频器------分频
·PFD:相位频率检测器----也可以分频也可以倍频
- IMX6ULL中有几个锁相环,几个相位分数分配器?
·7个独立的PPL:PPL1--PPL7
·PLL2和PLL3各含有4个相位频率检测器(PFD)
- 简述ARM PLL的配置
- IMX6ULL 中的EPIT和GPT的工作原理是什么?
|-------------|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| 对比项 | EPIT 定时器 | GPT 定时器 |
| 全称 | Event Periodic Interrupt Timer事件周期性中断定时器 | General Purpose Timer通用定时器 |
| 计数方式 | 固定向下计数从装载值开始,减到 0 触发事件 | 可向上 / 向下计数更灵活,支持自由运行模式 |
| 核心工作原理 | 1. 选择时钟源(通常为 IPG_CLK) 2. 经过预分频得到计数时钟 3. 从 LR 寄存器加载初值并向下计数 4. 计到 0 时自动触发中断5. 自动重载 LR 值,循环定时 | 1. 可选多种时钟源 2. 经预分频后开始计数 3. 可配置为自由运行或比较匹配 4. 支持输入捕获、输出比较 5. 溢出或匹配时触发中断 |
| 主要功能 | 专注做周期性定时中断简单、稳定、适合系统节拍 | 功能更全:定时、输入捕获、PWM、脉冲测量 |
| 自动重载 | 支持,计到 0 自动重装 | 可自由配置是否重载 |
| 低功耗特性 | 支持停止模式下运行 | 一般不支持低功耗独立运行 |
| 典型用途 | 周期性任务、系统滴答、延时 | 测频率、测脉宽、输出方波 / PWM |
| 与你代码关系 | 依赖配置好的 IPG_CLK_ROOT | 同样使用 AHB/IPG 时钟,依赖时钟初始化 |
|--------------|----------------------------------------------------|----------------------------|
| 对比项目 | 同步通信 | 异步通信 |
| 是否有时钟线 | 有独立时钟线(SCLK、SCK、SCL) | 无时钟线,只有数据线 ± 地线 |
| 时钟来源 | 主机统一提供时钟,收发共用同一时钟 | 收发双方各自使用内部时钟,相互独立 |
| 时钟使用方式 | 以时钟边沿为基准,时钟一跳传一位 | 按约定波特率,用内部定时器定时采样 |
| 同步机制 | 时钟线同步:时钟决定何时采样、何时发送 | 帧格式同步:靠起始位对齐,之后按时间间隔采样 |
| 时钟线的作用 | 1. 统一收发时序,保证步调一致2. 明确告诉对方 "此刻数据有效"3. 从根本上避免数据错位、误码 | 无时钟线,依靠事先约定的波特率代替时钟功能 |
| 对时钟精度要求 | 极高,必须严格同源或高度同步 | 较低,只要频率接近、误差在容忍范围即可 |
| 数据传输特点 | 连续传输,效率高,适合高速通信 | 按帧传输,每帧带起始位 / 停止位,有额外开销 |
| 优点 | 速度快、协议简单、抗干扰强、不易出错 | 线路少、实现简单、适合远距离、布线方便 |
| 缺点 | 多一根线,布线复杂,远距离时钟易畸变 | 速度相对较低,帧结构占用额外带宽 |
| 典型硬件接口 | SPI、I2C、同步 UART、CAN (量化同步)、各类总线 | 标准 UART(串口)、蓝牙、USB (含特殊同步) |
1,单工:固定了通信的发送方和接收方。
半双工:通信双方都可以发送和接收数据,但不能同一时刻进行发送和接收。
全双工:通信双方可以同时进行数据的接手和发送。本质是收发分离。
2.串口通信:用一条线进行数据的传输,单次接收一长串的bit。
并行通信:用多条线进行数据的传输,多条线同时发送一个bit。
3,异步通信:通信双方按照起始位和约定好的波特率,利用各自时钟进行通信,不需要时钟线。
同步通信:通信双按照一条时钟线的频率,来进行特定周期通信。
4,串口通信属于异步串行全双工通信。
5,串口通信的电器表达:
电平标准:
TTL电平
RS-232电平
接口形式:
UART:仅需TX,RX,GND 三根线。
波特率的范围常见:115200,9600 频率越高抗干扰能力越差。
I2C时序传输:
问题 1:描述 IIC 通信的时序
IIC 是双线同步通信,核心时序有四个:起始是 SCL 高时 SDA 高变低,停止是 SCL 高时 SDA 低变高;数据在 SCL 低时变化、高时采样,高位在前;每字节后紧跟应答位,ACK 为低表示继续,NACK 为高表示停止。通信分为写操作(发地址 + 数据)和读操作(先写地址定位,再发重复起始读数据),通过 ACK 保证传输可靠。
问题 2:AT24C02 实验代码调试要点
首先完成硬件调试,检查上拉电阻、接线和电源,用逻辑分析仪验证 100kHz 时钟和时序正常;然后调试代码,实现 I2C 基础时序函数和 AT24C02 的字节写、页写、随机读、连续读驱动,添加 ACK 校验和写周期延时;最后验证读写功能,确保数据掉电不丢失,页写不溢出,连续读逻辑正确。
流程细节:
总线空闲时:
时钟线SCL和数据线SDA都为高电平,谁先在数据线上产生一个低电平0,谁就赢得了总线的控制权(总线抢占),即认为发送了一个start信号,通信开始,作为主机(发送方);
主机发送数据时:
遵循MSB优先原则
在SCL为低电平时,只能发送方改变SDA,接收方不能采样SDA
SCL为高电平时,只能接收方采样SDA,发送方需保证SDA稳定
每一次通信都是先发送从机地址(7位)+数据流向位(0:主发从接;1:从发主接)
发送完8位数据时,在第9个时钟周期必须发送应答类型(ACK/NACK)
数据传输:(都先要主机发送从机地址+数据流向位(0:主发从接)+要写、读的寄存器地址)
写时序(主机发送,从机应答):主机发送一字节数据,从机回复一个应答类型,从机接收到ACK就继续向从机发送数据,直到收到NACK应答类型
读时序(从机发送,主机应答):重新发送start信号;再次发送从机地址,主机切换数据流向位(1:从发主接),开始连续读取数据(先应答,再读取数据,发ACK告诉从机我开始读了,你发送数据),直到主机读到倒数第二个数据时,发给从机NACK,告诉从机,下一个数据是最后一个了,你不用再发送数据了),读取结束发送stop
stop停止:SCL保持高电平时,SDA从低到高,并保持稳定,视为通信停止
- 什么是ADC?
把模拟信号转换成数字信号的转换器。
- 什么是ADC的基准电压?
是ADC进行模数转换时的参考电压,他决定了ADC输入电压的量程范围。
- ADC的工作原理是什么?
·采样:在特定的时间点对于模拟信号进行采取,获取该时刻的电压值。
·保持:把采集到的电压保持一段时间,便于后续处理。
·量化:根据参考电压的值和保持的电压进行比较,并且把他转换成离散的数字等级。
·编码:把量化后的等级转换为对应的二进制数字输出。
逐次逼近法:
核心是把连续变化的电压或电流,转换成离散的二进制代码。
- 什么是ADC的分辨率?常见的分辨率有哪些?
ADC的分辨率指的是ADC能够分辨的最小模电压变化量,分辨率越高对于,量化的误差越小,转换结果越接近真实的模拟电压值。
常见的分辨率有:8位,10位,12位,14位,16位,24位等。
·8位:可以区分 2^8 = 256 个不同的电压等级。
- 假设采用12位分辨率,基准电压为3.3v,量化结果为n时的实际电压应该如何计算?
n * 3.3 / 2^12 = n/4096 *3.3