文章目录
重点讲新知识点 ——.align 4(四字节对齐)
对比关 / 开中断的代码(一眼分清)
新手怎么选寄存器?(通用原则)
动态演示(0-6 循环的完整流程)
明确sp的本质:栈指针是 “RAM 级别的临时存储管家”
项目名字:GPIO-ASM-CH573-251119_zzz
文末附上最后的项目代码
.include "includes.inc" /* 包含汇编总头文件(需定义灯引脚/状态宏) */
比较负责 回头看源代码详细研究
/* 定义数据段(RAM区域,变量使用)=================== */
.section .data
hello_information: /* 功能提示字符串 */
.ascii "\n"
.ascii "------------------------------------------------------\n"
.ascii "★金葫芦提示★ \n"
.ascii "【工程名称】RISC-V三色灯组合颜色控制 \n"
.ascii "【程序功能】实现红/绿/蓝单色、二组合、三组合颜色切换 \n"
.ascii "【硬件连接】红/绿/蓝灯分别接对应GPIO引脚(见user.inc) \n"
.ascii "【操作说明】观察硬件板上的三色灯,串口打印当前颜色组合 \n"
.ascii "------------------------------------------------------\n\0"
/* printf格式符定义(仅保留新逻辑需要的) */
printf_format_count: /* 颜色切换次数提示 */
.ascii "颜色切换次数mColorCount = %d\n\0"
printf_format_color: /* 当前颜色组合提示 */
.ascii "当前显示颜色:%s\n\0"
/* 颜色名称字符串(与状态值一一对应) */
color_name_red: .ascii "Red (single)\0"
color_name_green: .ascii "Green (single)\0"
color_name_blue: .ascii "Blue (single)\0"
color_name_yellow: .ascii "Red+Green (yellow)\0"
color_name_purple: .ascii "Red+Blue (purple)\0"
color_name_cyan: .ascii "Green+Blue (cyan)\0"
color_name_white: .ascii "Red+Green+Blue (white)\0"
.align 4 /* 四字节对齐 */
mMainLoopCount: /* 主循环计数(达到阈值切换颜色) */
.word 0
mColorChangeCount: /* 颜色切换总次数 */
.word 0
mColorState: /* 颜色状态标志(0-6对应7种颜色组合) */
.byte 0 /* 0=红 1=绿 2=蓝 3=黄 4=紫 5=青 6=白 */
.section .data /*给汇编器发 “分区指令” —— 告诉汇编器:“接下来我写的内容,要放到程序运行时内存的「指定区域」里”。*/
汇编里的标签格式是「标签名 + 冒号」,作用是标记这段字符串在内存里的起始位置
补充个小知识点:RISC-V 里和字符相关的伪指令还有.asciz(z=zero,会自动在字符串末尾加\0)、.byte(存单个字节)
用/* */包裹的内容是 “多行注释”
/* printf格式符定义(仅保留新逻辑需要的) */
printf_format_count: /* 颜色切换次数提示 */
.ascii "颜色切换次数mColorCount = %d\n\0"
printf_format_color: /* 当前颜色组合提示 */
.ascii "当前显示颜色:%s\n\0"
标签printf_format_color::给 “当前颜色的打印模板” 贴名字,标记模板的起始位置。
→ 定义颜色提示的打印模板,拆解:
当前显示颜色::固定文字(不变);
%s:字符串占位符 → 程序运行时,把实际的颜色名称(比如 “红色”“绿蓝组合”“红绿蓝组合”)填到这个位置;
\n:换行符 → 打印完颜色后换行;
\0:结束符 → 标记模板结束。
color_name_cyan: .ascii "Green+Blue (cyan)\0"
color_name_white: .ascii "Red+Green+Blue (white)\0"
.align 4 /* 四字节对齐 */
先抛核心结论:.align 4告诉汇编器:把后续要存放的数据,放到 “4 的整数倍” 的内存地址上(比如地址 0x1000、0x1004、0x1008…,而不是 0x1001、0x1002)。
这段颜色字符串后面,大概率会定义 “灯状态变量”“GPIO 引脚配置” 等4 字节的整数型数据(比如color_state是 32 位整数,占 4 字节),加.align 4能保证这些数据存到对齐的地址上,CPU 访问时更快、更稳定
mMainLoopCount: /* 主循环计数(达到阈值切换颜色) */
.word 0
mColorChangeCount: /* 颜色切换总次数 */
.word 0
mColorState: /* 颜色状态标志(0-6对应7种颜色组合) */
.byte 0 /* 0=红 1=绿 2=蓝 3=黄 4=紫 5=青 6=白 */
.word/.byte 的读写指令区别:
- 读
.word变量:用lw 寄存器, 标签(lw=Load Word,加载 4 字节);
- 写
.word变量:用sw 寄存器, 标签(sw=Store Word,存储 4 字节);
- 读
.byte变量:用lb 寄存器, 标签(lb=Load Byte,加载 1 字节); 2**(1*8)=256 (范围0~255)
- 写
.byte变量:用sb 寄存器, 标签(sb=Store Byte,存储 1 字节);
算了 这汇编等设备好了再学吧 设备被我搞废了
行了 按了六下开关 将设备重置了 现在又能运行了
/* 定义代码段(Flash区域,存放代码)=========================== */
.section .text
.type main function
.global main
.align 2
这段代码的核心就是配置 main 函数的 “存储位置” 和 “入口身份”:
.section .text:定位置(Flash 代码区);
.type main function:定类型(函数);
.global main:定入口(CPU 启动先执行);
.align 2:做优化(4 字节对齐,读指令更快)。
/* 主函数入口 */
main:
/*【1】启动初始化========================================== */
/* 1.1 申请栈空间 */
addi sp,sp,-48
/* 1.2 关总中断(修正LI为li) */
li t0, 0x8
csrc mstatus, t0
addi是咱们最早学的 “立即数加法指令”,格式:addi 目标寄存器, 源寄存器, 立即数 → 这里就是:sp = sp + (-48) → 等价于 sp = sp - 48
- 初始时
sp指向一个高地址(比如 0x80010000);
- 执行
addi sp,sp,-48后,sp会往低地址挪 48 字节(比如到 0x8000FFD0);
- 这 “挪出来的 48 字节区间(0x8000FFD0 ~ 0x80010000)” 就是程序申请的 “临时工作台”,后续 main 函数执行时,临时变量、保存的寄存器值都存在这里。
mstatus:RISC-V 的机器状态寄存器(Machine Status Register)——CPU 的 “核心状态开关”,里面有多个二进制位,控制 CPU 的关键行为,比如 “是否允许中断”;
MIE位:mstatus 寄存器的第 3 位(bit3)(二进制从 0 开始数),是 “总中断使能位”:
- MIE=1 → 开总中断(CPU 会响应各种中断信号);
- MIE=0 → 关总中断(CPU 无视所有中断信号);
csrc:RISC-V 的特权指令(只能在机器态执行),全称 “Clear Status Register”—— 作用是 “把指定寄存器的位,清 0 到目标状态寄存器里”(简单说:用 t0 里的值当 “掩码”,把 mstatus 里对应位清 0)。
初始化阶段(比如配置 GPIO、初始化串口、设置变量初始值)是程序 “最脆弱” 的阶段 —— 如果此时有中断(比如定时器中断、串口中断),CPU 会暂停初始化,跑去处理中断,导致:
- GPIO 还没配置好,中断处理函数里操作 GPIO 会出错;
- 变量还没初始化(比如 mColorState 还是随机值),中断里读变量会乱套;
- 栈空间刚申请好,中断处理函数用栈时可能覆盖 main 函数的栈数据;
等所有初始化完成(比如 GPIO 配好、变量设好初始值),程序会再执行csrs mstatus, t0(置位 MIE 位),重新打开总中断。
RISC-V 汇编里,指令大小写不敏感(LI和li都可以),但规范写法是小写li
/* 串口打印工程提示 */
la a0,hello_information
call printf
用 la 指令把纯提示字符串的地址传给 printf 的第一个参数(a0),再用 call 指令调用 printf 函数,完成串口打印
/* 1.3 初始化三色灯GPIO(红/绿/蓝均设为输出,初始灭) */
/* 初始化红灯 */
li a0,LIGHT_RED
li a1,GPIO_OUTPUT
li a2,LIGHT_OFF
这段代码的核心是按 RISC-V 传参规则,给 GPIO 初始化函数传递三个参数:
- a0:要配置的引脚(红灯);
- a1:引脚模式(输出);
- a2:初始状态(灭);
这是嵌入式 RISC-V 控制硬件的 “标配操作”—— 先初始化引脚(定模式、定初始状态),后续才能通过修改引脚电平来控制灯的亮灭~
/* 1.4 开总中断(修正LI为li) */
li t0, 0x8
csrs mstatus, t0
核心指令:csrs(Set Status Register,“状态寄存器置位指令”)—— 和关中断的csrc(Clear,清位)是反义词:
嵌入式 RISC-V 程序的 “标准流程”:关中断→初始化→开中断→进入业务逻辑,既保证初始化稳定,又能响应外部事件
/*【2】主循环(计数→切换颜色→控制灯亮灭)=================================== */
main_loop:
/* 2.1 主循环计数+1,判断是否达到阈值(MAINLOOP_COUNT在user.inc定义) */
la a5,mMainLoopCount /* 读取主循环计数 */
lw t1,0(a5)
addi t1,t1,1
sw t1,0(a5)
li t2,MAINLOOP_COUNT /* 判断是否达到阈值 */
bltu t1,t2,main_loop /* 未到阈值,继续主循环 */
这段代码的核心是实现 “计数 - 判断 - 循环” 的定时逻辑:
- 读
mMainLoopCount的当前值→+1→写回;
- 对比当前值和阈值
MAINLOOP_COUNT;
- 没到阈值就跳回循环起点,到了就退出循环去切换颜色。
| 指令 |
全称 |
核心逻辑 |
通俗解释 |
三色灯场景举例 |
beq |
Branch if Equal |
寄存器 1 == 寄存器 2 → 跳转 |
两个数相等,就跳去标签 |
beq t0, t1, reset_color
(t0 = 当前颜色状态,t1=6(白灯),相等则跳去 reset_color 重置为 0) |
bne |
Branch if Not Equal |
寄存器 1 != 寄存器 2 → 跳转 |
两个数不等,就跳去标签 |
bne t0, zero, light_on
(t0 = 灯状态,zero=0,不等则跳去 light_on 开灯) |
| 指令 |
全称 |
核心逻辑 |
通俗解释 |
适用场景 |
blt |
Branch if Less Than |
寄存器 1 < 寄存器 2 → 跳转(有符号) |
有符号数,前者小于后者则跳 |
温度 < 0℃,触发加热 |
bge |
Branch if Greater or Equal |
寄存器 1 ≥ 寄存器 2 → 跳转(有符号) |
有符号数,前者≥后者则跳 |
电压≥3.3V,触发断电保护 |
| 指令 |
全称 |
核心逻辑 |
通俗解释 |
三色灯场景举例 |
bltu |
Branch if Less Than Unsigned |
寄存器 1 < 寄存器 2 → 跳转(无符号) |
无符号数,前者小则跳 |
你之前的bltu t1,t2,main_loop(计数 < 阈值则循环) |
bgeu |
Branch if Greater or Equal Unsigned |
寄存器 1 ≥ 寄存器 2 → 跳转(无符号) |
无符号数,前者≥后者则跳 |
bgeu t1,t2,change_color(计数≥阈值则切换颜色) |
- 临时计算 / 判断 → 优先用 t0~t6;
- 函数传参 → 优先用 a0~a3;
- 临时存地址 / 非核心参数 → 用 a4~a7;
- 长期保留的值 → 用 s0~s11;
- 绝对不动 ra/sp/zero。
/* 2.4 读取当前颜色状态,切换到下一个状态(0-6循环) */
la a5,mColorState
lb t0,0(a5) /* t0=当前状态值(0-6) */
addi t0,t0,1
li t1,7
bltu t0,t1,state_ok /* 未到7,状态有效 */
li t0,0 /* 超过6,重置为0(循环) */
state_ok:
sb t0,0(a5) /* 保存新状态 */
用 “状态 = 6(白灯)” 举例,看代码怎么把它切回 0:
lb t0,0(a5) → t0=6;
addi t0,t0,1 → t0=7;
li t1,7 → t1=7;
bltu t0,t1,state_ok → 7 < 7?不成立,不跳转;
li t0,0 → t0=0;
state_ok: → 执行到这;
sb t0,0(a5) → mColorState=0(红灯)。
再用 “状态 = 5(青灯)” 举例:
lb t0,0(a5) → t0=5;
addi t0,t0,1 → t0=6;
li t1,7 → t1=7;
bltu t0,t1,state_ok → 6 < 7?成立,跳去 state_ok;
- 跳过
li t0,0,直接执行sb t0,0(a5) → mColorState=6(白灯)。
/* 2.5 根据新状态控制三色灯亮灭 */
/* 先全灭所有灯(避免残留状态) */
li a0,LIGHT_RED
li a1,LIGHT_OFF
call gpio_set
li a0,LIGHT_GREEN
li a1,LIGHT_OFF
call gpio_set
li a0,LIGHT_BLUE
li a1,LIGHT_OFF
call gpio_set
/* 分支判断:不同状态对应不同灯亮灭 */
la a5,mColorState
lb t0,0(a5)
li t1,0
beq t0,t1,color_red /* 0=红 */
li t1,1
beq t0,t1,color_green /* 1=绿 */
li t1,2
beq t0,t1,color_blue /* 2=蓝 */
li t1,3…………
这段代码的核心是用beq指令实现 “状态值→颜色标签” 的一一映射:
- 读状态值→逐个对比 0/1/2/3…;
- 匹配则跳转到对应颜色标签,点亮目标灯;
- 逻辑直观、易维护,适配 7 种颜色的简单状态机;
- 结合之前 “先全灭所有灯” 的操作,能精准点亮单一 / 组合色,无混色 bug。
/* 颜色分支:单色-红 */
color_red:
li a0,LIGHT_RED
li a1,LIGHT_ON
call gpio_set
la a1,color_name_red /* 颜色名称地址 */
j print_color_info /* 打印颜色信息 */
/* 打印颜色切换信息 */
print_color_info:
/* 打印切换次数 */
la a0,printf_format_count
la a5,mColorChangeCount
lw a1,0(a5)
call printf
/* 打印当前颜色组合 */
la a0,printf_format_color
call printf /* a1已存放颜色名称地址,直接调用 */
/* 回到主循环 */
j main_loop
上面的代码有明显的错误 不知道各位有没有看出来
正确的应该是这样的
/* 打印颜色切换信息 */
print_color_info:
/* 保存到栈指针 */
sw a1,0(sp)
/* 打印切换次数 */
la a0,printf_format_count
la a5,mColorChangeCount
lw a1,0(a5)
call printf
/* 打印当前颜色组合 */
la a0,printf_format_color
lw a1,0(sp)
call printf /* a1已存放颜色名称地址,直接调用 */
/* 回到主循环 */
j main_loop
这里解释一下为啥用栈指针
sp(Stack Pointer,栈指针寄存器)的核心作用是指向程序在 RAM 中申请的栈空间,栈空间的特性是:
- 持久存储:栈在 RAM 中(而非 CPU 内部寄存器),写入的值除非主动改写,否则不会丢失;
- 先进后出:适合 “暂存 - 恢复” 的场景(先存进去,用完再取出来);
- 规范分配:main 函数开头已通过
addi sp,sp,-48申请了 48 字节栈空间,有足够的空间存放寄存器值(1 个寄存器占 4 字节)。
下面是RGB灯光变换代码
/*=====================================================================
//文件名称:main.s
//功能概要:汇编语言编程,调用构件GPIO,实现三色灯单色/二组合/三组合颜色切换
//版权所有:苏大嵌入式(sumcu.suda.edu.cn)
//版本更新:20241204
//=====================================================================*/
.include "includes.inc" /* 包含汇编总头文件(需定义灯引脚/状态宏) */
/* 定义数据段(RAM区域,变量使用)=================== */
.section .data
hello_information: /* 功能提示字符串 */
.ascii "\n"
.ascii "------------------------------------------------------\n"
.ascii "★金葫芦提示★ \n"
.ascii "【工程名称】RISC-V三色灯组合颜色控制 \n"
.ascii "【程序功能】实现红/绿/蓝单色、二组合、三组合颜色切换 \n"
.ascii "【硬件连接】红/绿/蓝灯分别接对应GPIO引脚(见user.inc) \n"
.ascii "【操作说明】观察硬件板上的三色灯,串口打印当前颜色组合 \n"
.ascii "------------------------------------------------------\n\0"
/* printf格式符定义(仅保留新逻辑需要的) */
printf_format_count: /* 颜色切换次数提示 */
.ascii "颜色切换次数mColorCount = %d\n\0"
printf_format_color: /* 当前颜色组合提示 */
.ascii "当前显示颜色:%s\n\0"
/* 颜色名称字符串(与状态值一一对应) */
color_name_red: .ascii "Red (single)\0"
color_name_green: .ascii "Green (single)\0"
color_name_blue: .ascii "Blue (single)\0"
color_name_yellow: .ascii "Red+Green (yellow)\0"
color_name_purple: .ascii "Red+Blue (purple)\0"
color_name_cyan: .ascii "Green+Blue (cyan)\0"
color_name_white: .ascii "Red+Green+Blue (white)\0"
.align 4 /* 四字节对齐 */
mMainLoopCount: /* 主循环计数(达到阈值切换颜色) */
.word 0
mColorChangeCount: /* 颜色切换总次数 */
.word 0
mColorState: /* 颜色状态标志(0-6对应7种颜色组合) */
.byte 0 /* 0=红 1=绿 2=蓝 3=黄 4=紫 5=青 6=白 */
/* 定义代码段(Flash区域,存放代码)=========================== */
.section .text
.type main function
.global main
.align 2
/* 主函数入口 */
main:
/*【1】启动初始化========================================== */
/* 1.1 申请栈空间 */
addi sp,sp,-48
/* 1.2 关总中断(修正LI为li) */
li t0, 0x8
csrc mstatus, t0
/* 串口打印工程提示 */
la a0,hello_information
call printf
/* 1.3 初始化三色灯GPIO(红/绿/蓝均设为输出,初始灭) */
/* 初始化红灯 */
li a0,LIGHT_RED
li a1,GPIO_OUTPUT
li a2,LIGHT_OFF
call gpio_init
/* 初始化绿灯 */
li a0,LIGHT_GREEN
li a1,GPIO_OUTPUT
li a2,LIGHT_OFF
call gpio_init
/* 初始化蓝灯 */
li a0,LIGHT_BLUE
li a1,GPIO_OUTPUT
li a2,LIGHT_OFF
call gpio_init
/* 1.4 开总中断(修正LI为li) */
li t0, 0x8
csrs mstatus, t0
/*【2】主循环(计数→切换颜色→控制灯亮灭)=================================== */
main_loop:
/* 2.1 主循环计数+1,判断是否达到阈值(MAINLOOP_COUNT在user.inc定义) */
la a5,mMainLoopCount /* 读取主循环计数 */
lw t1,0(a5)
addi t1,t1,1
sw t1,0(a5)
li t2,MAINLOOP_COUNT /* 判断是否达到阈值 */
bltu t1,t2,main_loop /* 未到阈值,继续主循环 */
/* 2.2 达到阈值,重置主循环计数 */
la a5, mMainLoopCount
li t1,0
sw t1,0(a5)
/* 2.3 颜色切换次数+1 */
la a5,mColorChangeCount
lw t1,0(a5)
addi t1,t1,1
sw t1,0(a5)
/* 2.4 读取当前颜色状态,切换到下一个状态(0-6循环) */
la a5,mColorState
lb t0,0(a5) /* t0=当前状态值(0-6) */
addi t0,t0,1
li t1,7
bltu t0,t1,state_ok /* 未到7,状态有效 */
li t0,0 /* 超过6,重置为0(循环) */
state_ok:
sb t0,0(a5) /* 保存新状态 */
/* 2.5 根据新状态控制三色灯亮灭 */
/* 先全灭所有灯(避免残留状态) */
li a0,LIGHT_RED
li a1,LIGHT_OFF
call gpio_set
li a0,LIGHT_GREEN
li a1,LIGHT_OFF
call gpio_set
li a0,LIGHT_BLUE
li a1,LIGHT_OFF
call gpio_set
/* 分支判断:不同状态对应不同灯亮灭 */
la a5,mColorState
lb t0,0(a5)
li t1,0
beq t0,t1,color_red /* 0=红 */
li t1,1
beq t0,t1,color_green /* 1=绿 */
li t1,2
beq t0,t1,color_blue /* 2=蓝 */
li t1,3
beq t0,t1,color_yellow /* 3=黄(红绿) */
li t1,4
beq t0,t1,color_purple /* 4=紫(红蓝) */
li t1,5
beq t0,t1,color_cyan /* 5=青(绿蓝) */
li t1,6
beq t0,t1,color_white /* 6=白(红绿蓝) */
/* 颜色分支:单色-红 */
color_red:
li a0,LIGHT_RED
li a1,LIGHT_ON
call gpio_set
la a1,color_name_red /* 颜色名称地址 */
j print_color_info /* 打印颜色信息 */
/* 颜色分支:单色-绿 */
color_green:
li a0,LIGHT_GREEN
li a1,LIGHT_ON
call gpio_set
la a1,color_name_green
j print_color_info
/* 颜色分支:单色-蓝 */
color_blue:
li a0,LIGHT_BLUE
li a1,LIGHT_ON
call gpio_set
la a1,color_name_blue
j print_color_info
/* 颜色分支:二组合-黄(红绿) */
color_yellow:
li a0,LIGHT_RED
li a1,LIGHT_ON
call gpio_set
li a0,LIGHT_GREEN
li a1,LIGHT_ON
call gpio_set
la a1,color_name_yellow
j print_color_info
/* 颜色分支:二组合-紫(红蓝) */
color_purple:
li a0,LIGHT_RED
li a1,LIGHT_ON
call gpio_set
li a0,LIGHT_BLUE
li a1,LIGHT_ON
call gpio_set
la a1,color_name_purple
j print_color_info
/* 颜色分支:二组合-青(绿蓝) */
color_cyan:
li a0,LIGHT_GREEN
li a1,LIGHT_ON
call gpio_set
li a0,LIGHT_BLUE
li a1,LIGHT_ON
call gpio_set
la a1,color_name_cyan
j print_color_info
/* 颜色分支:三组合-白(红绿蓝) */
color_white:
li a0,LIGHT_RED
li a1,LIGHT_ON
call gpio_set
li a0,LIGHT_GREEN
li a1,LIGHT_ON
call gpio_set
li a0,LIGHT_BLUE
li a1,LIGHT_ON
call gpio_set
la a1,color_name_white
j print_color_info
/* 打印颜色切换信息 */
print_color_info:
/* 保存到栈指针 */
sw a1,0(sp)
/* 打印切换次数 */
la a0,printf_format_count
la a5,mColorChangeCount
lw a1,0(a5)
call printf
/* 打印当前颜色组合 */
la a0,printf_format_color
lw a1,0(sp)
call printf /* a1已存放颜色名称地址,直接调用 */
/* 回到主循环 */
j main_loop
/*【3】栈恢复(兜底,运行不到但保留规范)=========================================== */
addi sp, sp, 48
ret
Comments NOTHING