共2条
1/1 1 跳转至页
44B0,uc,os 测试44B0上的uc/os,遇见几个奇特的问题,请教高手!
问
硬件:三星44B0X的公板,8MB RAM,2MB FLASH ROM。
第一个问题:
移植完成之后,在无用户任务的环境测试uc/os本身,发现系统最后并不能执行空闲任务,经过单步调试,发现OSStartHighRdy函数中,在最后恢复空闲任务的CPSR之后,会改变SP的内容,使得后面的R0~R12,LR,PC寄存器被弹出不期望的值,从而导致PC中的内容不是空闲任务的入口地址,以至程序飞掉。
关键代码:
1.main函数 //不创建任何任务,测试OS本身,(注:在OS_cfg.h中,已经将OS_TASK_STAT_EN定义成0,即表示也无OS_TaskStat任务,于是系统只剩一个空闲任务)
int Main(int argc, char **argv)
{
OSInit();
OSStart();
return 0;
}
////////////////////////////////////////////////////////////////
2. 来自文件:OS_CPU_A.S
描述:OSSart()函数末尾调用OSStartHighRdy过程来完成向优先级最高的任务的调度(由于没有创建用户任务,因此,此时调度的是空闲任务)。
备注:问题就出在倒数第二条语句“MSR cpsr_cxsf, r4”,使得SP被改变!
AREA |subr|, CODE, READONLY
EXPORT OSStartHighRdy
IMPORT OSTaskSwHook
IMPORT OSTCBHighRdy
IMPORT OSRunning
OSStartHighRdy
BL OSTaskSwHook ; Call user-defined hook function
LDR r4,=OSRunning ; Indicate that multitasking has started
MOV r5, #1
STRB r5, [r4] ; OSRunning = true
LDR r4, =OSTCBHighRdy ; Get highest priority task TCB address
LDR r4, [r4] ; get stack pointer
LDR sp, [r4] ; switch to the new stack
LDMFD sp!, {r4} ; pop new task s psr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} ; pop new task s r0-r12,lr & pc
/////////////////////////////////////////////////////////////////
3.来自文件OS_CPU_C.C
描述:关键代码,任务堆栈初始化函数
OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
unsigned int *stk;
opt = opt; /* 'opt' is not used, prevent warning */
stk = (unsigned int *)ptos; /* Load stack pointer */
/* build a context for the new task */
*--stk = (unsigned int) task; /* pc */
*--stk = (unsigned int) task; /* lr */
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (unsigned int) pdata; /* r0 */
*--stk = (SVC32MODE|0x40); /* cpsr FIQ disable*/
return ((OS_STK *)stk);
}
/////////////////////////////////////////////////////////////////
当完成44Binit.s初始化之后,程序开始执行main函数,main函数中的OSInit()函数调用OSTaskCreateExt函数,创建了一个空闲任务OS_TaskIdle(),此时我记下了OS_TaskIdle对应的地址值为0x0c001170。
之后,OSStart()函数便开始调用OSStartHighRdy过程,开始从任务栈中恢复上下文给空闲任务OS_TaskIdle(),使得其开始运行。
问题出在这里:
OSStartHighRdy过程的最后三条语句:
LDMFD sp!, {r4} ; 注释(1)
MSR cpsr_cxsf, r4 ; 注释(2)
LDMFD sp!, {r0-r12,lr,pc} ; 注释(3)
注释(1):当此条语句执行完成之后,SP的内容为0x0c5009a0,;
注释(2):问题出现了,当执行完此语句时,SP的内容不再是0x0c5009a0,而是另外一个0x0c7fxxxx的值。此时cpsr_cxsf的值为0x60000053,和原来的一样。
注释(3):因此,就导致LDMFD sp!, {r0-r12,lr,pc}所弹出来的值并不是需要的值,PC的内容不是空闲任务OS_TaskIdle对应的地址值0x0c001170,从而程序跑飞了。
于是我们在
MSR cpsr_cxsf, r4 的语句后添了一条测试语句:
LDR sp, =0x0c5009a0 ;添的测试语句,将sp改回来
LDMFD sp!, {r0-r12,lr,pc} ; 再恢复上下文
这下单步调试OK,程序进入了空闲任务OS_TaskIdle。
由于我对ARM这几种工作模式到底优越在什么地方并不太了解,所以这个问题让我们觉得不好解释,更为疑惑的问题还在后面:
问题2:参考各位大虾的OSStartHighRdy代码,我们认为代码本身没有问题,因为我们又做了下一个测试:去掉刚才在上面的OSStartHighRdy代码中加了那条LDR sp, =0x0c5009a0 测试语句,在main函数中创建一个简单的任务,在串口输出数据后马上调用OSTimeDly,使得空闲任务OS_TaskIdle得以运行。
而这次的测试,由于我只想单步运行来验证第一个问题,因此,我没有对时钟节拍中断进行初始化,而这个时候发现刚才还出现sp内容被更改的问题的三条语句
LDMFD sp!, {r4} ; 注释(1)
MSR cpsr_cxsf, r4 ; 注释(2)
LDMFD sp!, {r0-r12,lr,pc} ; 注释(3)
这次没有被更改,又可以正确调度task1了,即第一次执行到上面注释(3)的
LDMFD sp!, {r0-r12,lr,pc}语句后,程序进入了task1.
我留意了一下,有一个区别是:空闲任务OS_TaskIdle是由OSTaskCreateExt函数创建的,而task1是由OSTaskCreate函数创建的。
这是一个令人头疼的问题。请高手指点!
///////////////////////////////////////////////////////
第二个问题:OSSchedLock()函数的问题使得程序跑飞。
问题描述:进入任务调度器上锁函数OSSchedLock()之后,运行ARMDisableInt()没有问题,运行到ARMEnableInt()中的MOV PC,LR就发现PC并不能返回到OSSchedLock()中去,而是执行了紧跟其后的下一条指令。
后来,我完全初始化时钟中断,并按正常的流程创建两个任务,结果程序仍然运行到OSSchedLock()函数中后就飞掉了。
#define OS_ENTER_CRITICAL() ARMDisableInt()
#define OS_EXIT_CRITICAL() ARMEnableInt()
OSSchedLock()函数调用OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()来关中断和恢复cpsr。
EXPORT ARMDisableInt
ARMDisableInt
MRS r0, cpsr
STMFD sp!, {r0} ; push current PSR
ORR r0, r0, #0xC0
MSR cpsr_c, r0 ; disable IRQ Int s
MOV pc, lr //注释(4),运行到这里能正常返回到
//能正常返回到OSSchedLock()函数中去
EXPORT ARMEnableInt
ARMEnableInt
LDMFD sp!, {r0} ; pop current PSR
MSR cpsr_c, r0 ; restore original cpsr
MOV pc, lr //注释(5),而运行到这里却不能返回OSSchedLock()函数,而是执行紧跟下面的DmpStk中的指令去了!
//Dmpstk程序为网友 YJ 所添加,我移植他的代码时也没有删除此程序。
EXPORT DmpStk
DmpStk
LDR r0, =Dump_Pool
MOV r1, sp
MOV r2, sp
ADD r2, r2, #64
DmpLoop
LDR r3, [r1]
STR r3, [r0]
ADD r0, r0, #4
ADD r1, r1, #4
CMP r1, r2
BNE DmpLoop
MOV pc, lr
ALIGN
Dump_Pool DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
END
//////////////////////////////////
#define N_TASKS 5 // Number of tasks
#define TASK_STK_SIZE 1024 // Stack size, in sizeof OS_STK, or int 32bit
OS_STK TaskStk[N_TASKS][TASK_STK_SIZE]; // Tasks stacks
void Task1(void *);
int Main(int argc, char **argv)
{
int task_1 = 0;
sys_init();
OSInit();
OSTaskCreate(Task1, &task_1, &TaskStk[0][TASK_STK_SIZE-1], 1);
OSStart();
return 0;
}
// task1中,我并没有初始化时钟节拍中断,
void Task1(void * pParam)
{
Uart_Printf(" Task1 is run!\n");
while (1)
{
OSSchedLock();
Uart_Printf(" Task1 is run!\n");
OSSchedUnlock();
OSTimeDly(7);
}
}
答 1: 我想,可能问题还是出在ARM工作模式改变的问题上这两个问题出现之前都有一条MSR指令...
刚开始学习ARM不久,有许多不懂的地方,因此请各位站友指点一下小弟,碧水长天在此先行谢过。 答 2: 440还没试过,不过自己在LPC2214上成功过。关键是模式的切换和中断嵌套一定要处理好,不然会出很多莫名其妙的问题 答 3: 不清楚,给你顶吧,11 答 4: 谢谢楼上的二位站友我尝试分析了一下:
在44binit.s中,并没有设置用户模式的初始化堆栈,而我觉得ARM初始化之后进入C程序之前,一般需要将处理器设为用户模式,但这里似乎并没有这样做。
而在第一个问题中,我单步执行OSStartHighRdy程序,发现引起SP改变的MSR cpsr_cxsf,r4指令确将ARM模式更改到SVC模式(因为此时我们发现r4的内容为0X60000053),那么任务堆栈的指针就改变了。不知是否跟此有关?
还望前辈们指点。
44BINIT.s中的一些代码,初始化ARM之后进入main之前,并没有将ARM设为用户模式。
;****************************************************
;* Initialize stacks *
;****************************************************
ldr sp, =SVCStack ;Why?????????
bl InitStacks
InitStacks
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStack
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStack
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStack
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack
;USER mode is not initialized. 没有初始化
mov pc,lr ; 答 5: 今天又调试了一下进一步确定量问题的所在,反汇编OSSchedLock函数,发现其调用OS_EXIT_CRITICAL函数时存在问题,说明如下:
...
if(OSRunnig == TRUE){
...
OS_ENTER_CRITICAL();
if (OSLockNesting < 255){
OSLockNesting ++;
}
OS_EXIT_CRITICAL();
[0xe8bd4010] ldmfd r13!,{r4,r14}
[0xea000fa8] b ARMEnableInt
编译器将此处的OS_EXIT_CRITICAL()函数编译成了两条汇编语句,而我在调试时,发现其他的恢复cpsr是的 OS_EXIT_CRITICAL()函数只汇编成一条
bl ARMEnableInt指令!
正是此处的 ldmfd r13!,{r4,r14}指令使得r13被改变(但观察时发现r13从调用前的0x0x500ff8竟变成了0x0c501000!),因此cpsr恢复错误,导致ARM的工作模式从SVC意外变成了USR态,使得程序出现异常。
有高手能解释一下这个现象么,谢谢。
第一个问题:
移植完成之后,在无用户任务的环境测试uc/os本身,发现系统最后并不能执行空闲任务,经过单步调试,发现OSStartHighRdy函数中,在最后恢复空闲任务的CPSR之后,会改变SP的内容,使得后面的R0~R12,LR,PC寄存器被弹出不期望的值,从而导致PC中的内容不是空闲任务的入口地址,以至程序飞掉。
关键代码:
1.main函数 //不创建任何任务,测试OS本身,(注:在OS_cfg.h中,已经将OS_TASK_STAT_EN定义成0,即表示也无OS_TaskStat任务,于是系统只剩一个空闲任务)
int Main(int argc, char **argv)
{
OSInit();
OSStart();
return 0;
}
////////////////////////////////////////////////////////////////
2. 来自文件:OS_CPU_A.S
描述:OSSart()函数末尾调用OSStartHighRdy过程来完成向优先级最高的任务的调度(由于没有创建用户任务,因此,此时调度的是空闲任务)。
备注:问题就出在倒数第二条语句“MSR cpsr_cxsf, r4”,使得SP被改变!
AREA |subr|, CODE, READONLY
EXPORT OSStartHighRdy
IMPORT OSTaskSwHook
IMPORT OSTCBHighRdy
IMPORT OSRunning
OSStartHighRdy
BL OSTaskSwHook ; Call user-defined hook function
LDR r4,=OSRunning ; Indicate that multitasking has started
MOV r5, #1
STRB r5, [r4] ; OSRunning = true
LDR r4, =OSTCBHighRdy ; Get highest priority task TCB address
LDR r4, [r4] ; get stack pointer
LDR sp, [r4] ; switch to the new stack
LDMFD sp!, {r4} ; pop new task s psr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} ; pop new task s r0-r12,lr & pc
/////////////////////////////////////////////////////////////////
3.来自文件OS_CPU_C.C
描述:关键代码,任务堆栈初始化函数
OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
unsigned int *stk;
opt = opt; /* 'opt' is not used, prevent warning */
stk = (unsigned int *)ptos; /* Load stack pointer */
/* build a context for the new task */
*--stk = (unsigned int) task; /* pc */
*--stk = (unsigned int) task; /* lr */
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (unsigned int) pdata; /* r0 */
*--stk = (SVC32MODE|0x40); /* cpsr FIQ disable*/
return ((OS_STK *)stk);
}
/////////////////////////////////////////////////////////////////
当完成44Binit.s初始化之后,程序开始执行main函数,main函数中的OSInit()函数调用OSTaskCreateExt函数,创建了一个空闲任务OS_TaskIdle(),此时我记下了OS_TaskIdle对应的地址值为0x0c001170。
之后,OSStart()函数便开始调用OSStartHighRdy过程,开始从任务栈中恢复上下文给空闲任务OS_TaskIdle(),使得其开始运行。
问题出在这里:
OSStartHighRdy过程的最后三条语句:
LDMFD sp!, {r4} ; 注释(1)
MSR cpsr_cxsf, r4 ; 注释(2)
LDMFD sp!, {r0-r12,lr,pc} ; 注释(3)
注释(1):当此条语句执行完成之后,SP的内容为0x0c5009a0,;
注释(2):问题出现了,当执行完此语句时,SP的内容不再是0x0c5009a0,而是另外一个0x0c7fxxxx的值。此时cpsr_cxsf的值为0x60000053,和原来的一样。
注释(3):因此,就导致LDMFD sp!, {r0-r12,lr,pc}所弹出来的值并不是需要的值,PC的内容不是空闲任务OS_TaskIdle对应的地址值0x0c001170,从而程序跑飞了。
于是我们在
MSR cpsr_cxsf, r4 的语句后添了一条测试语句:
LDR sp, =0x0c5009a0 ;添的测试语句,将sp改回来
LDMFD sp!, {r0-r12,lr,pc} ; 再恢复上下文
这下单步调试OK,程序进入了空闲任务OS_TaskIdle。
由于我对ARM这几种工作模式到底优越在什么地方并不太了解,所以这个问题让我们觉得不好解释,更为疑惑的问题还在后面:
问题2:参考各位大虾的OSStartHighRdy代码,我们认为代码本身没有问题,因为我们又做了下一个测试:去掉刚才在上面的OSStartHighRdy代码中加了那条LDR sp, =0x0c5009a0 测试语句,在main函数中创建一个简单的任务,在串口输出数据后马上调用OSTimeDly,使得空闲任务OS_TaskIdle得以运行。
而这次的测试,由于我只想单步运行来验证第一个问题,因此,我没有对时钟节拍中断进行初始化,而这个时候发现刚才还出现sp内容被更改的问题的三条语句
LDMFD sp!, {r4} ; 注释(1)
MSR cpsr_cxsf, r4 ; 注释(2)
LDMFD sp!, {r0-r12,lr,pc} ; 注释(3)
这次没有被更改,又可以正确调度task1了,即第一次执行到上面注释(3)的
LDMFD sp!, {r0-r12,lr,pc}语句后,程序进入了task1.
我留意了一下,有一个区别是:空闲任务OS_TaskIdle是由OSTaskCreateExt函数创建的,而task1是由OSTaskCreate函数创建的。
这是一个令人头疼的问题。请高手指点!
///////////////////////////////////////////////////////
第二个问题:OSSchedLock()函数的问题使得程序跑飞。
问题描述:进入任务调度器上锁函数OSSchedLock()之后,运行ARMDisableInt()没有问题,运行到ARMEnableInt()中的MOV PC,LR就发现PC并不能返回到OSSchedLock()中去,而是执行了紧跟其后的下一条指令。
后来,我完全初始化时钟中断,并按正常的流程创建两个任务,结果程序仍然运行到OSSchedLock()函数中后就飞掉了。
#define OS_ENTER_CRITICAL() ARMDisableInt()
#define OS_EXIT_CRITICAL() ARMEnableInt()
OSSchedLock()函数调用OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()来关中断和恢复cpsr。
EXPORT ARMDisableInt
ARMDisableInt
MRS r0, cpsr
STMFD sp!, {r0} ; push current PSR
ORR r0, r0, #0xC0
MSR cpsr_c, r0 ; disable IRQ Int s
MOV pc, lr //注释(4),运行到这里能正常返回到
//能正常返回到OSSchedLock()函数中去
EXPORT ARMEnableInt
ARMEnableInt
LDMFD sp!, {r0} ; pop current PSR
MSR cpsr_c, r0 ; restore original cpsr
MOV pc, lr //注释(5),而运行到这里却不能返回OSSchedLock()函数,而是执行紧跟下面的DmpStk中的指令去了!
//Dmpstk程序为网友 YJ 所添加,我移植他的代码时也没有删除此程序。
EXPORT DmpStk
DmpStk
LDR r0, =Dump_Pool
MOV r1, sp
MOV r2, sp
ADD r2, r2, #64
DmpLoop
LDR r3, [r1]
STR r3, [r0]
ADD r0, r0, #4
ADD r1, r1, #4
CMP r1, r2
BNE DmpLoop
MOV pc, lr
ALIGN
Dump_Pool DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
END
//////////////////////////////////
#define N_TASKS 5 // Number of tasks
#define TASK_STK_SIZE 1024 // Stack size, in sizeof OS_STK, or int 32bit
OS_STK TaskStk[N_TASKS][TASK_STK_SIZE]; // Tasks stacks
void Task1(void *);
int Main(int argc, char **argv)
{
int task_1 = 0;
sys_init();
OSInit();
OSTaskCreate(Task1, &task_1, &TaskStk[0][TASK_STK_SIZE-1], 1);
OSStart();
return 0;
}
// task1中,我并没有初始化时钟节拍中断,
void Task1(void * pParam)
{
Uart_Printf(" Task1 is run!\n");
while (1)
{
OSSchedLock();
Uart_Printf(" Task1 is run!\n");
OSSchedUnlock();
OSTimeDly(7);
}
}
答 1: 我想,可能问题还是出在ARM工作模式改变的问题上这两个问题出现之前都有一条MSR指令...
刚开始学习ARM不久,有许多不懂的地方,因此请各位站友指点一下小弟,碧水长天在此先行谢过。 答 2: 440还没试过,不过自己在LPC2214上成功过。关键是模式的切换和中断嵌套一定要处理好,不然会出很多莫名其妙的问题 答 3: 不清楚,给你顶吧,11 答 4: 谢谢楼上的二位站友我尝试分析了一下:
在44binit.s中,并没有设置用户模式的初始化堆栈,而我觉得ARM初始化之后进入C程序之前,一般需要将处理器设为用户模式,但这里似乎并没有这样做。
而在第一个问题中,我单步执行OSStartHighRdy程序,发现引起SP改变的MSR cpsr_cxsf,r4指令确将ARM模式更改到SVC模式(因为此时我们发现r4的内容为0X60000053),那么任务堆栈的指针就改变了。不知是否跟此有关?
还望前辈们指点。
44BINIT.s中的一些代码,初始化ARM之后进入main之前,并没有将ARM设为用户模式。
;****************************************************
;* Initialize stacks *
;****************************************************
ldr sp, =SVCStack ;Why?????????
bl InitStacks
InitStacks
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStack
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStack
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStack
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack
;USER mode is not initialized. 没有初始化
mov pc,lr ; 答 5: 今天又调试了一下进一步确定量问题的所在,反汇编OSSchedLock函数,发现其调用OS_EXIT_CRITICAL函数时存在问题,说明如下:
...
if(OSRunnig == TRUE){
...
OS_ENTER_CRITICAL();
if (OSLockNesting < 255){
OSLockNesting ++;
}
OS_EXIT_CRITICAL();
[0xe8bd4010] ldmfd r13!,{r4,r14}
[0xea000fa8] b ARMEnableInt
编译器将此处的OS_EXIT_CRITICAL()函数编译成了两条汇编语句,而我在调试时,发现其他的恢复cpsr是的 OS_EXIT_CRITICAL()函数只汇编成一条
bl ARMEnableInt指令!
正是此处的 ldmfd r13!,{r4,r14}指令使得r13被改变(但观察时发现r13从调用前的0x0x500ff8竟变成了0x0c501000!),因此cpsr恢复错误,导致ARM的工作模式从SVC意外变成了USR态,使得程序出现异常。
有高手能解释一下这个现象么,谢谢。
共2条
1/1 1 跳转至页
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
vscode+cmake搭建雅特力AT32L021开发环境被打赏30分 | |
【换取逻辑分析仪】自制底板并驱动ArduinoNanoRP2040ConnectLCD扩展板被打赏47分 | |
【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用被打赏38分 | |
【换取逻辑分析仪】-基于ADI单片机MAX78000的简易MP3音乐播放器被打赏48分 | |
我想要一部加热台+树莓派PICO驱动AHT10被打赏38分 | |
【换取逻辑分析仪】-硬件SPI驱动OLED屏幕被打赏36分 | |
换逻辑分析仪+上下拉与多路选择器被打赏29分 | |
Let'sdo第3期任务合集被打赏50分 | |
换逻辑分析仪+Verilog三态门被打赏27分 | |
换逻辑分析仪+Verilog多输出门被打赏24分 |