故恒无欲以观其妙恒有欲以观其徼。——《老子》老子认为需从“无”中观察事物的本源奥妙从“有”中观察事物的边界与规律。学CPU也是这样。如果我们只学C语言、Python这些有现成的工具就看不到底层真正在发生什么。只有回到无——也就是没有编译器帮你翻译、没有库函数帮你封装的时代亲手写一条一条的指令才能看清CPU到底是怎么工作的。今天我们就从最原始的汇编编程入手亲手点亮LED。一、用汇编程序点亮LED1.1回顾昨天昨天我们弄明白了三件事指令就是“操作码操作数”高5位告诉CPU“做什么”低11位告诉CPU“对谁做”CPU不断循环取指令→译码→执行→PC指向下一条指令→取指令……控制硬件 往地址写数GPIO等都是通过写特定地址来控制的今天我们要验证昨天的理论——操作码和操作数真的能让硬件工作。1.2 GPIO操作原理查看Cortex-M3单片机手册GPIO通用输入输出口有16个管脚可以通过写寄存器来控制管脚的电平。通过控制GPIO引脚的电平高电平3.3V低电平0V就可以控制外部设备的开关。需要操作2个寄存器来控制步骤寄存器地址作用①0x40010010GPIO配置寄存器bit0写1表示将管脚0设为输出模式②0x40010004GPIO输出寄存器bit0写1表示输出高电平什么是bit0bit0是一个二进制数的最低位最右边那一位。一个8位寄存器的bit0就是第0位值为1或0。写1就是打开写0就是关闭。因此要点亮GPIO的bit0对应的管脚处的LED需要对上面三个寄存器写入的值步骤寄存器地址要写的值说明①0x400100101bit0写1配置管脚0为输出模式②0x400100041bit0写1输出高电平 → LED亮就是这么简单把2个数字写入2个地址灯就亮了。每个寄存器都有固定的地址芯片设计时就定死了。写程序控制硬件本质就是往特定地址写特定的值——这是昨天强调的核心概念。1.3完整汇编代码.section .text .word 0x20001000 .word _start .thumb_func _start: ldr r0, 0x400100100 mov r1, #1 str r1, [r0] ldr r0, 0x40010004 mov r1, #1 str r1, [r0] halt: b halt1.4逐行拆解第1行告诉汇编器这是代码.section .text告诉汇编器“下面的内容是属于代码段的。”这是给汇编器看的指示不是CPU执行的指令。第2-3行上电复位地址.word 0x20001000 .word _start告诉汇编器“这里放2个数第一个数是0x20001000第二个数是_start”。_start在下面定义编译程序会把这个数紧挨着第一个数放置这是Cortex-M3上电复位时CPU读数赋值PC并跳转的地方。第一个值与堆栈有关我们暂时用不到后面会专门介绍今天先不用管它。第4-5行程序入口.thumb_func _start:指明下面是一个thumb函数的入口并给这个入口起一个标号名字编译时会用实际的地址取代这个标号名字。ARM公司定义了多种指令集thumb指令集是其中一种我们这款单片机要用到这个指令集。第6-8行配置GPIO管脚0为输出模式ldr r0, 0x40010010 ; r0 0x40010010 mov r1, #1 ; r1 1bit01 str r1, [r0] ; 将r1的值1写入0x40010800 → 管脚0设为输出第9-11行GPIO管脚0输出高电平LED亮ldr r0, 0x40010004 ; r0 0x40010004 mov r1, #1 ; r1 1bit01 str r1, [r0] ; 写入0x4001080C → 输出高电平LED亮第12-13行死循环halt: b halthalt定义了一个标号代表当前指令所在的地址b halt表示要跳到这个地址即死循环。为什么要死循环在单片机上程序不能结束。普通电脑上的程序运行完就退出操作系统会回收资源。但单片机没有操作系统程序就是全部——如果CPU执行完最后一条指令后继续往下走它会把后面随机的垃圾数据当成指令来执行导致不可预知的行为这叫跑飞。所以我们要用一个死循环把CPU钉在这里灯就一直亮着。二、编译和下载GNU国际组织和ARM公司开发好了编译工具用于将汇编程序翻译成机器码本质上就是查表用链接程序将机器码放在存储空间对应的位置。我们选择的高云半导体FPGA GW1NSR-4C内嵌了Cortex-M3硬核高云半导体提供了开发工具GMD IDE专用于单片机程序开发。这个IDE自身集成了ARM公司的编译器、链接器等工具链。2.1安装GMD IDE从高云半导体官网(https://www.gowinsemi.com.cn/)下载GMD IDE安装包官网首页点击页面顶端菜单产品中心 / GOWIN MCU DESIGNER进入软件下载页面点击Windows版本下载。建议在某个硬盘上创建GMD目录下载解压到这个目录无需安装即可运行。双击GMD.exe文件第一次会要求你设置一个workspace目录可以在硬盘上创建一个workspace目录以后的工程项目都放在这个工作目录下面。GMD IDE解压完成后ARM工具链as、ld、objcopy等位于安装目录下的toolchain子目录中。2.2配置环境变量可选为了方便在命令行中使用as、ld、objcopy等命令可以将GMD的ARM工具链目录添加到系统PATH环境变量中右键“此电脑” → 属性 → 高级系统设置 → 环境变量在“系统变量”中找到Path双击编辑点击“新建”添加GMD的ARM工具链目录如D:\GMD\GMD\toolchain\gowin\gcc_arm\arm-none-eabi\bin点击“确定”保存如果不想配置环境变量也可以在命令行中直接使用完整路径如D:\GMD\GMD\toolchain\gowin\gcc_arm\arm-none-eabi\bin。2.3准备源文件将上面的汇编代码保存为led.s创建一个文件夹D:\test将led.s放入其中。2.4编译程序方法一手动输入命令打开命令提示符WinR → 输入cmd→回车依次敲入cd C:\test进入到文件夹D:\test再敲入如下命令as -mcpucortex-m3 -mthumb -o led.o led.s ld -Ttext0x0 -o led.elf led.o objcopy -O binary led.elf led.bin objdump -S led.o led1.s方法二创建批处理文件推荐在D:\test文件夹中创建一个build.bat文件内容如下echo off as -mcpucortex-m3 -mthumb -o led.o led.s ld -Ttext0x0 -o led.elf led.o objcopy -O binary led.elf led.bin objdump -S led.o led1.s echo Build complete! pause双击build.bat即可自动完成编译。三条命令逐行解释命令输入输出作用as -mcpucortex-m3 -mthumb -o led.o led.sled.sled.o汇编器把汇编翻译成机器码ld -Ttext0x0 -o led.elf led.oled.oled.elf链接器分配地址生成ELFobjcopy -O binary led.elf led.binled.elfled.bin提取纯机器码用于烧录objdump -S led.elf led1.sled.elfled1.s反汇编查看机器码对应的汇编打开led1.s文件的内容如下led.elf: file format elf32-littlearm Disassembly of section .text: 00000000 _start-0x8: 0: 20001000 .word 0x20001000 4: 00000009 .word 0x00000009 00000008 _start: 8: 4803 ldr r0, [pc, #12] (18 halt0x4) a: 2101 movs r1, #1 c: 6001 str r1, [r0, #0] e: 4803 ldr r0, [pc, #12] (1c halt0x8) 10: 2101 movs r1, #1 12: 6001 str r1, [r0, #0] 00000014 halt: 14: e7fe b.n 14 halt 16: 0000 .short 0x0000 18: 40010010 .word 0x40010010 1c: 40010004 .word 0x40010004反汇编结果看起来和我们写的代码有些出入其实都是汇编器在帮忙善后。差异一_start变成奇数0x00000009的最低位是1这是Cortex-M3的Thumb模式标记——汇编器自动把_start地址加1表示“这是个Thumb函数”。你写.thumb_func它就这么做。差异二ldr r0, 0x40010010变成ldr r0, [pc, #12]ldr r0, 常数是一条伪指令不是CPU真正执行的指令。它只是告诉汇编器“帮我把这个数存到附近然后生成一条真正的LDR去加载”。汇编器照做了。差异三mov变成movs在Thumb指令集里这条指令的标准写法就是movs。汇编器生成机器码时自动匹配了标准格式。三处差异都是汇编器自动处理的没有一处需要你修改代码。反汇编只是告诉你“汇编器实际生成了什么”你写代码时仍按原始写法即可。实际的机器码程序led.bin如下可用HxD软件查看可网上免费下载从0x0开始顺序存放地址: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 数据: 00 10 00 20 09 00 00 00 03 48 01 21 01 60 03 48ARM的存储器以8位字节为最小单位低位字节放在地址小的方向高位字节放在地址大的方向这种存贮方式叫小端存贮Little Endian。比如0x20001000拆成字节为0x20-00-10-00最低位字节00放在地址0处10放在地址1处00放在地址2处20放在地址3处。在CPU的眼里只有数字没有汇编没有Python没有Java等语言。2.5下载程序led.bin到开发板按如下操作步骤进行程序烧录使用 USB 数据线连接开发板与电脑在 GMD IDE 顶部菜单栏选择 Gowin → Programmer打开内置烧录工具工具默认存在一条器件配置条目修改器件参数设备系列与型号选择 GW1NSR-4C若无对应型号可通过Edit → Add Device添加器件右键该配置条目打开Device Configuration配置窗口按以下参数设置Access ModeMCU ModeOperation勾选 Firmware、Erase、ProgramFile name选用配套 FPGA 工程文件已提前预制获取途径下文有交待Firmware/Binary File选中上一步编译生成的机器码文件led.bin设置完成后点击 Save 保存配置执行烧录点击菜单栏Edit → Program/Configure或直接点击工具栏绿色三角快捷按钮启动烧录流程。烧录结束后开发板板载 LED 将点亮代表程序下载成功。配套开发板本款开发板兼具 FPGA 开发与内嵌 ARM 单片机开发能力本教程全部实验案例均基于此硬件实现。若尚未购置开发板可以关注公众号《嵌入式开发之道》私信发送“开发板”获取订购信息。无实物硬件虽不影响理论原理理解但缺少实操环节学习体验会大打折扣。FPGA程序获取本芯片的 ARM 单片机内核内嵌于 FPGA 逻辑内部必须搭配绑定管脚资源的 FPGA 工程文件单片机才能正常驱动外设。我已提前完成配套 FPGA 程序制作将单片机 GPIO0 引脚映射 LED、GPIO1 引脚映射按键。获取方式关注本公众号通过私信、公众号留言或微店客服留言即可索取文件。进阶学习建议如果你想自己生成这个FPGA程序可以学习《零基础FPGA开发入门系列7天亲手“造”一台计算机》的第九天内容那里详细讲解了如何将ARM内核与FPGA管脚绑定。三、今日核心收获概念一句话解释控制硬件往地址写值——2个地址2个值LED就亮了操作码ldr、mov、str、b——告诉CPU“做什么”操作数r0、r1、#4——告诉CPU“对谁做”汇编器as把汇编源码翻译成机器码查表链接器ld分配地址生成可执行文件objcopy从ELF提取纯机器码.binobjdump把elf文件反汇编成汇编文件烧录把.bin写入单片机Flash四、明天预告用C语言重写点灯程序——同样的效果代码更短、更接近人类语言。