嵌入式高手进 考试题解答

嵌入式高手进 考试题解答,第1张

推荐一:OS_CPUH

1、定义与编译器无光的数据类型

只是按照不同的编译器编写对应的数据类型的typedef 对应于ARM7的数据类型的编写如下

typedef unsigned char BOOLEAN;/ 布尔变量/

typedef unsigned char INT8U; / 无符号8位整型变量/

typedef signed char INT8S; / 有符号8位整型变量/

typedef unsigned short INT16U; / 无符号16位整型变量/

typedef signed short INT16S; / 有符号16位整型变量/

typedef unsigned int INT32U; / 无符号32位整型变量/

typedef signed int INT32S; / 有符号32位整型变量/

typedef float FP32; /单精度浮点数(32Bit)/

typedef double FP64; /双精度浮点数(64Bit)/

/在上面定义的数据类型中按照ARM7的堆栈宽度选择INT32U/

typedef INT32U OS_STK; / 堆栈是32位宽度/

接下来一部分是为了兼容低版本UCOS的数据类型所编写的代码,在UCOS-II中暂不考虑

2 与处理器相关的代码

先定义中断的实现方式,预先设定的中断方式有三种,在ARM7中设置为方式 2

#define OS_CRITICAL_METHOD 2/选择开,关中断的方式 /

接下来的一段是我暂时还没有完全搞懂的一部分,只知道是设定了12个软件中断的函数,当调用这

些函数之前都会执行对应中断号的事情。具体的看到后面应该能完全搞懂软件中断的实现方式,

该段代码在后面的文件中会有具体的解释,这里暂时不看

定义堆栈的生长方式,ARM7内核支持两种生长方式,但是ADS的C语言编译器只支持从上往下的生

长方式,因此:

#define OS_STK_GROWTH 1 / 堆栈是从上往下长的,0-从下往上的生长方式 /

最后几行分别定义了用户模式01和系统模式1f以及IRQ中断禁止的指令80三个立即数,方便调用

还有两个预定义往后看应该知道作用,暂不考虑,不是很重要

软中断:

中断不返回形式:

void _swi(swi_num) swi_name(arguments);

返回一个结果到R0中

int _swi(swi_num) swi_name(arguments);

最多可以返回四个结果R0-R3到一个结构struct type{ int a,b,c,d}中

type(返回类型) _value_in_regs(返回多个结果的修饰符) _swi(swi_num) swi_name(arguments);

在ARM中实现软中断的方法我在blog里面搜了很多文章也没有看到讲的通俗一点的,还是自己看

ARM的移植代码吧首先定义了一堆软中断的中断号,其中0和1的中断服务子程序是用汇编编写的,

其他的都是在c语言编写的中断服务子程序SWI_Exception中。

__swi(0x00) void OS_TASK_SW(void);

/ 任务级任务切换函数 /

__swi(0x01) void _OSStartHighRdy(void);

/ 运行优先级最高的任务 /

__swi(0x02) void OS_ENTER_CRITICAL(void);

/ 关中断 /

__swi(0x03) void OS_EXIT_CRITICAL(void);

/ 开中断 /

__swi(0x40) void GetOSAddr(int Index);

/ 获取系统服务函数入口 /

__swi(0x41) void GetUsrAddr(int Index);

/ 获取自定义服务函数入口 /

__swi(0x42) void OSISRBegin(void);

/ 中断开始处理 /

__swi(0x43) int OSISRNeedSwap(void);

/ 判断中断是否需要切换 /

__swi(0x80) void ChangeToSYSMode(void);

/ 任务切换到系统模式 /

__swi(0x81) void ChangeToUSRMode(void);

/ 任务切换到用户模式 /

__swi(0x82) void TaskIsARM(INT8U prio);

/ 任务代码是ARM代码 /

__swi(0x83) void TaskIsTHUMB(INT8U prio);

/ 任务代码是THUMB /

比如在程序运行到调用OS_TASK_SW(void)函数时,就产生软件中断,然后就进入中断服务子程序,

按照什么指令走呢?恩,就按照下面这个代码,这个代码是将软件中断异常处理程序挂接到内核

的作用的,是在启动代码中实现的:

LDR PC,SWI_Addr

SWI_Addr DCD SoftwareInterrupt

因此当产生软中断之后PC就跳到了SoftwareInterrupt,这时就算真正进入了软件异常中断处理部

分了,然后就是执行下面的汇编代码SoftwareInterrupt

LDR SP, StackSvc

/重新设置堆栈指针/

STMFD SP!, {R0-R3, R12, LR}

/保存 R0,R1,R2,R3,R12,LR(R14),注意为什么只保存这几个

寄存器呢,因为R4-R11存储局部变量,编译器自动保护他们/

MOV R1, SP / R1指向参数存储位置 /

MRS R3, SPSR /保存管理模式的状态寄存器/

TST R3, #T_bit / 中断前是否是Thumb状态 /

LDRNEH R0, [LR,#-2] / 若是,取得Thumb状态SWI号/

BICNE R0, R0, #0xff00 /THUMB指令SWI功能号为8位 /

LDREQ R0, [LR,#-4] / 为零即ARM指令取得SWI号 /

BICEQ R0, R0, #0xFF000000

/在ARM指令集中SWI功能号为24位所以高8位清零r0=SWI号/

CMP R0, #1 /

LDRLO PC, =OSIntCtxSw / 疑惑ing /

/ 功能号为0到OSIntCtxSw执行中断任务切换函数 /

LDREQ PC, =__OSStartHighRdy/SWI为1第一次任务切换/

BL SWI_Exception /否则进入c编写的中断函数 /

LDMFD SP!, {R0-R3, R12, PC}/R0-R3,R12,LR出栈 /

StackSvc

DCD (SvcStackSpace + SVC_STACK_LEGTH 4 - 4)

怎么进入c编写的中断服务子程序SWI_Exception呢?通过下面的申明

IMPORT SWI_Exception ;软中断异常处理程序

表示将c程序中的该函数挂接到此段汇编代码中,同样的道理

EXPORT __OSStartHighRdy

EXPORT OSIntCtxSw ;中断退出时的入口

参见startups中的IRQ_Handler

EXPORT SoftwareInterrupt ;软中断入口上面的申明是将该段汇编代码挂接到外面,

因此在外部可以直接调用函数名

继续看OS_CPU_AS的其他部分代码,就是两个软件异常中断处理函数OSIntCtxSw和OSStarHighRdyOSIntCtxSw代码是中断服务子程序使得更高优先级的任务进入就绪状态后,中断返回后需要切换到该任务时调用的,这是被切换的任务的CPU寄存器的值已经在响应中断后存入了堆栈中,因此,这里不需要重复保存了直接切换任务即可,具体过程看代码OSIntCtxSw

;下面为保存任务环境 ;当响应软件异常中断后进入了系统模式,在上面的代码中我们可以看到,进入系统模式时保存的堆栈结构从顶到底依次是:R0,R1,R2,R3,R12,LR,而在用户模式中任务的堆栈结构应该是:OsEnterSum,CPSR,RO-12,LR,PC,所以在进行软件中断任务切换之前先要保存原来任务的堆栈结构。

LDR R2, [SP, #20] ;获取PC

LDR R12, [SP, #16] ;获取R12

MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode)

MOV R1, LR

STMFD SP!, {R1-R2} ;保存LR,PC

STMFD SP!, {R4-R12} ;保存R4-R12 MSR CPSR_c, R0

LDMFD SP!, {R4-R7} ;获取R0-R3

ADD SP, SP, #8 ;出栈R12,PC

MSR CPSR_c, #(NoInt | SYS32Mode)

STMFD SP!, {R4-R7} ;保存R0-R3

LDR R1, =OsEnterSum ;获取OsEnterSum

LDR R2, [R1]

STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存当前任务堆栈指针到当前任务的TCB

LDR R1, =OSTCBCur

LDR R1, [R1]

STR SP, [R1] BL OSTaskSwHook ;调用钩子函数

;OSPrioCur <= OSPrioHighRdy

LDR R4, =OSPrioCur

LDR R5, =OSPrioHighRdy

LDRB R6, [R5]

STRB R6, [R4]

;OSTCBCur <= OSTCBHighRdy

LDR R6, =OSTCBHighRdy

LDR R6, [R6]

LDR R4, =OSTCBCur

STR R6, [R4]

OSIntCtxSw_1

;获取新任务堆栈指针

LDR R4, [R6]

ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP

LDR LR, [SP, #-8]

MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式

MOV SP, R4 ;设置堆栈指针 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum

;恢复新任务的OsEnterSum

LDR R3, =OsEnterSum

STR R4, [R3]

MSR SPSR_cxsf, R5 ;恢复CPSR

LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务

__OSStartHighRdy

MSR CPSR_c, #(NoInt | SYS32Mode) ;调整到管理模式

;告诉uC/OS-II自身已经运行

LDR R4, =OSRunning

MOV R5, #1

STRB R5, [R4] ;标记多任务运行标记为真 BL OSTaskSwHook ;调用钩子函数,可以运行用户自定义的函数 LDR R6, =OSTCBHighRdy ;R6存有最高优先级的就绪任务的控制块地址

LDR R6, [R6]

B OSIntCtxSw_1 ;转到前面编写的中断返回函数块的任务跳转部分的代码,因为这两个函数都要用到这部分代码,进入这段代码之前高优先级的就绪任务的任务控制快地址存在R6中。 AREA SWIStacks, DATA, NOINIT,ALIGN=2

SvcStackSpace SPACE SVC_STACK_LEGTH 4 ;管理模式堆栈空间 OSIntCtxSw_1的代码:OSIntCtxSw_1

;获取新任务堆栈指针

LDR R4, [R6] ;任务控制块的堆栈指针放在R6中,现在放在R4中

ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP

LDR LR, [SP, #-8]

MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式

MOV SP, R4 ;设置堆栈指针,R4存有没有改动过的堆栈指针 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum

;恢复新任务的OsEnterSum

LDR R3, =OsEnterSum

STR R4, [R3]

MSR SPSR_cxsf, R5 ;恢复CPSR

LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务,恢复现场,异常处理返回;中断返回指令的寄存器列表其中必须包括PC后的^符号,表示这是一条特殊形式的指令。这条指令在从存储器中装载PC的同时,CPSR也得到恢复。这里使用的堆栈指针SP是属于异常模式的寄存器,每个异常模式有自己的堆栈指针。SoftwareInterrupt

LDR SP, StackSvc ; 重新设置堆栈指针

STMFD SP!, {R0-R3, R12, LR} ;保存寄存器

MOV R1, SP ; R1指向参数存储位置 MRS R3, SPSR

TST R3, #T_bit ; 中断前是否是Thumb状态

LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号

BICNE R0, R0, #0xff00

LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号

BICEQ R0, R0, #0xFF000000

; r0 = SWI号,R1指向参数存储位置

CMP R0, #1

LDRLO PC, =OSIntCtxSw

LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换 BL SWI_Exception

LDMFD SP!, {R0-R3, R12, PC}^

StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH 4 - 4)OSIntCtxSw

;下面为保存任务环境

LDR R2, [SP, #20] ;获取PC(LR)

LDR R12, [SP, #16] ;获取R12

MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode)

MOV R1, LR

STMFD SP!, {R1-R2} ;保存LR,PC

STMFD SP!, {R4-R12} ;保存R4-R12 MSR CPSR_c, R0

LDMFD SP!, {R4-R7} ;获取R0-R3

ADD SP, SP, #8 ;出栈R12,PC

MSR CPSR_c, #(NoInt | SYS32Mode)

STMFD SP!, {R4-R7} ;保存R0-R3

LDR R1, =OsEnterSum ;获取OsEnterSum

LDR R2, [R1]

STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存当前任务堆栈指针到当前任务的TCB

LDR R1, =OSTCBCur

LDR R1, [R1]

STR SP, [R1] BL OSTaskSwHook ;调用钩子函数

;OSPrioCur <= OSPrioHighRdy

LDR R4, =OSPrioCur

LDR R5, =OSPrioHighRdy

LDRB R6, [R5]

STRB R6, [R4] ;把OSPrioHighRdy最高优先级的就绪任务传给OSPrioCur

;OSTCBCur <= OSTCBHighRdy

LDR R6, =OSTCBHighRdy

LDR R6, [R6]

LDR R4, =OSTCBCur

STR R6, [R4] ;将最高优先级的任务控制块指针传给当前任务控制块指针

关于中断和时钟节拍,UCOS-II对于ARM7通用的中断服务程序的汇编与c函数接口如下:MACRO和MEND伪指令用于宏定义,MACRO标识宏定义的开始,MEND标识宏定义的结束。定义之后在程序中就可以通过宏指令多次调用该段代码MACRO

$IRQ_Label HANDLER $IRQ_Exception_ EXPORT $IRQ_Label ; 输出的标号

IMPORT $IRQ_Exception_ ; 引用的外部标号$IRQ_Label

SUB LR, LR, #4 ; 计算返回地址

STMFD SP!, {R0-R3, R12, LR} ; 保存任务环境

MRS R3, SPSR ; 保存状态

STMFD SP, {R3, SP, LR}^ ; 保存用户状态的R3,SP,LR,注意不能回写

; 如果回写的是用户的SP,所以后面要调整SP

LDR R2, =OSIntNesting ; OSIntNesting++

LDRB R1, [R2]

ADD R1, R1, #1

STRB R1, [R2] SUB SP, SP, #43

MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式

CMP R1, #1

LDREQ SP, =StackUsr

BL $IRQ_Exception_ ; 调用c语言的中断处理程序 MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式

LDR R2, =OsEnterSum ; OsEnterSum,使OSIntExit退出时中断关闭

MOV R1, #1

STR R1, [R2] BL OSIntExit LDR R2, =OsEnterSum ; 因为中断服务程序要退出,所以OsEnterSum=0

MOV R1, #0

STR R1, [R2] MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切换回irq模式

LDMFD SP, {R3, SP, LR}^ ; 恢复用户状态的R3,SP,LR,注意不能回写

; 如果回写的是用户的SP,所以后面要调整SP

LDR R0, =OSTCBHighRdy

LDR R0, [R0]

LDR R1, =OSTCBCur

LDR R1, [R1]

CMP R0, R1 ADD SP, SP, #43 ;

MSR SPSR_cxsf, R3

LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不进行任务切换

LDR PC, =OSIntCtxSw ; 进行任务切换

MEND二:OS_CPU_CC 个文件中要求用户编写10个简单的C函数,但是只有1个函数是必要的,其余的函数必须声明,但不一定要包含任何代码,大致看了一下作用好像是用来调试之类的。唯一要编写的是OSTaskStkInit() OSTaskStkInit()函数的功能是初始化任务的栈结构,任务的堆栈结构与CPU的体系结构、编译器有密切的关联。从ARM的结构可以写出如下的栈结构:程序计数器PC,程序链接器LR,R12-R1,R0用于传递第一个参数pdata,CPSR/SPSR,关中断计数器(用于计算关中断的次数,这样就实现了中断的嵌套),返回的地址指针是指向的最后一个存入的数据,而不是一个空地址。软件中断异常SWI服务程序C语言部分 void SWI_Exception(int SWI_Num, int Regs):参数SWI_Num对应前面文件中定义的功能号,其中0、1号的功能在后面的文件中定义,这里只定义了其他10个功能。 2、3分别对应关中断和开中断 关中断:MRS R0, SPSR //在软件中断的时候直接对程序状态保存寄存器SPSR *** 作也就是对CPSR的 *** 作

ORR R0, R0, #NoInt //在汇编语言中对寄存器的对应位置位用ORR,清零用BIC

MSR SPSR_c, R0 //SPSR_c表示的是只改变SPSR的控制段的8位代码,其他三段_f,_s,_x中标志位在_f段,其他为保留位 开中断:MRS R0, SPSR //在开中断中基本与上面相同,只是ORR改成BIC清零

BIC R0, R0, #NoInt

MSR SPSR_c, R 由于需要实现中断嵌套,所以只有当关中断的计数器减为0的时候才能够开中断,而且每次关中断的时候该计数器都应该加1。另外,插入汇编语言时用_asm指令。 80、81、82、83分别对应系统模式、用户模式、ARM指令集、THUMB指令集 系统模式:MRS R0, SPSR

BIC R0, R0, #0x1f //先将控制模式的低5位清零

ORR R0, R0, #SYS32Mode //设置成系统模式的1F

MSR SPSR_c, R0 用户模式:MRS R0, SPSR

BIC R0, R0, #0x1f

ORR R0, R0, #USR32Mode //设置成用户模式的10

MSR SPSR_c, R0 ARM指令集与THUMB指令集的代码如下: ptcb = OSTCBPrioTbl[Regs[0]];

if (ptcb != NULL)

{

ptcb -> OSTCBStkPtr[1] &= ~(1 << 5);

} ptcb = OSTCBPrioTbl[Regs[0]];

if (ptcb != NULL)

{

ptcb -> OSTCBStkPtr[1] |= (1 << 5);

} 昨天就是看到这里,出现了一个意识到是不能忽悠的地方就是UCOS里面的任务控制块OS_TCB的概念,因此今天的任务就是把这部分看看。。。 大概回忆了一下昨天晚上的工作,开始今天的工作吧

一点一点来,什么不会就学什么,都不会就都学。。。没有问题只要你肯努力。。。。。。__OSStartHighRdy

MSR CPSR_c, #(NoInt | SYS32Mode) ;MSR:在ARM中只有MSR能够直接设置状态寄存器CPSR或SPSR,可以是立即数或者源寄存器,NoInt是禁止中断,SYS32Mode是系统模式

;告诉uC/OS-II自身已经运行

LDR R4, =OSRunning ;OSRunning正在运行多任务的标志,=OSRunning是把OSRunning的地址加载到R4,R4里存的是一个地址。。。

MOV R5, #1

STRB R5, [R4] ;将R5存储到R4存的地址的变量即OSRunning中,也就是将OSRunning置1 BL OSTaskSwHook ;调用钩子函数,OSTaskSwHook 是用于扩展的,在任务切换的时候执行用户自己定义的功能。 LDR R6, =OSTCBHighRdy ;OSTCBHighRdy指向最高优先级任务的控制块TCB的指针!!!将放指针的地址放到R6中。

LDR R6, [R6] ;将R6地址处的数据读出即OSTCBHighRdy的地址放到R6中

B OSIntCtxSw_1 ;跳转到OSIntCtxSw_1 AREA SWIStacks, DATA, NOINIT,ALIGN=2

SvcStackSpace SPACE SVC_STACK_LEGTH 4 ;管理模式堆栈空间继续昨天没有看完的代码OSIntCtxSw

;下面为保存任务环境

LDR R2, [SP, #20] ;获取PC,放入R2

LDR R12, [SP, #16] ;获取R12,//R12存的什么东西啊???

MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode) ;进入系统模式并禁止中断

MOV R1, LR ;R1放LR值

STMFD SP!, {R1-R2} ;保存LR,PC,将R1,R2存入SP

STMFD SP!, {R4-R12} ;保存R4-R12,将R4-12存入SP MSR CPSR_c, R0 ;再回到之前的模式

LDMFD SP!, {R4-R7} ;获取R0-R3

ADD SP, SP, #8 ;出栈R12,PC

MSR CPSR_c, #(NoInt | SYS32Mode)

STMFD SP!, {R4-R7} ;保存R0-R3

LDR R1, =OsEnterSum ;获取OsEnterSum

LDR R2, [R1]

STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存当前任务堆栈指针到当前任务的TCB

LDR R1, =OSTCBCur

LDR R1, [R1]

STR SP, [R1] BL OSTaskSwHook ;调用钩子函数

;OSPrioCur <= OSPrioHighRdy

LDR R4, =OSPrioCur

LDR R5, =OSPrioHighRdy

LDRB R6, [R5]

STRB R6, [R4]

;OSTCBCur <= OSTCBHighRdy

LDR R6, =OSTCBHighRdy

LDR R6, [R6]

LDR R4, =OSTCBCur

STR R6, [R4]

OSIntCtxSw_1

;获取新任务堆栈指针

LDR R4, [R6] ;把OSTCBHighRdy指向最高优先级任务的控制块TCB的指针给R4

ADD SP, R4, #68 ;17寄存器:CPSR,OsEnterSum,R0-R12,LR,SP

LDR LR, [SP, #-8] ;取出LR放到LR

MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式并且保持禁止中断

MOV SP, R4 ;设置堆栈指针 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum。LDMFD数据出栈,放入R4,R5

;恢复新任务的OsEnterSum

LDR R3, =OsEnterSum ;OsEnterSum的地址存入R3

STR R4, [R3] ;把R4的值赋给OsEnterSum

MSR SPSR_cxsf, R5 ;恢复CPSR;在管理模式里是修改SPSR

LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务 ,恢复现场,异常处理返回

一、位带 *** 作

在学习51单片机的时候就使用过位 *** 作,通过关键字sbit对单片机IO口进行位定义。但是stm32没有这样的关键字,而是通过访问位带别名区来实现,即将每个比特位膨胀成一个32位字,通过位带别名区指针指向位带区内容。

支持位带 *** 作的两个内存区的范围是:

0x2000_0000‐0x200F_FFFF(SRAM 区中的最低 1MB)

0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB)

位带别名区地址=(A&0xF0000000)+0x2000000+(A&0xFFFFF)

其中A为位带区地址,n为该字节的第几位。

这里再不嫌啰嗦地举一个例子:

1 在地址 0x20000000 处写入 0x3355AACC

2 读取地址0x22000008。本次读访问将读取 0x20000000,并提取比特 2,值为 1。

3 往地址 0x22000008 处写 0。本次 *** 作将被映射成对地址 0x20000000 的“读-改-写” *** 作(原子的),把比特2 清 0。

4 现在再读取 0x20000000,将返回 0x3355AAC8(bit[2]已清零)。

注:如果用到位带 *** 作,可以把各个引脚进行位带宏定义,封装在一个头文件里,方便使用引脚。

二、中断

中断其实就是当CPU执行程序时,由于发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(中段子程序)。下面是中断的示意图。

部分中断可屏蔽,部分中断不可屏蔽,每个中断通道都具备自己的中断优先级控制字,分别控制抢占优先级和响应优先级。只有当抢占优先级相同时,响应优先级高低决定哪个中断被处理(响应式优先级的高低没法中断正在执行的中断程序)。

stm32中的NVIC(嵌套向量中断控制器)属于内核的一个外设,控制着芯片的中断相关功能。

注:中断和事件的区别:

可以这样简单的认为,事件机制提供了一个完全有硬件自动完成的触发到产生结果的通道,不要软件的参与,降低了CPU的负荷,节省了中断资源,提高了响应速度(硬件总快于软件),是利用硬件来提升CPU芯片处理事件能力的一个有效方法。

三、printf重定向

我们知道C语言中printf函数默认输出设备是显示器,如果要实现在串口或者LCD上显示,必须重定义标准库函数里调用与输出设备相关的函数。一般是在fputc函数里把输出对象改为指向串口或者LCD,这一过程叫做重定向。

四、随机数发生器RNG

STM32F4芯片内部含有一个硬件随机数发生器(RNG),RNG处理器是一个以连续模拟噪声为基础的随机数发生器,提供了一个32位的随机数。使能后,需要检查标志位,判断其是否稳定,稳定后才能使用。RNG结构图如下图所示:

五、AD/DA

STM32的ADC模块,请允许我用如此通俗的语言:普通话 来介绍STM32ADC模块的特色

1、1MHz转换速率、12位转换结果(12位、记住这个12位哈、因为2^12=4096 ,也请记住4096哈)

STM32F103系列:

在56MHz时转换时间为:1μs

在72MHz时转换时间为:117μs

2、转换范围:0~33V (33v---->当你需要将采集的数据用电压来显示的话:设你采集的数据为:x[0~4095],此时的计算公式就为:(x / 4096) 33))

3、ADC供电要求:24V~36 V(可千万别接到 5V 的石榴裙子底下呀)

4、ADC输入范围:VREF-≤ VIN ≤VREF+ (VREF+和VREF-只有LQFP100封装才有)

5、双重模式(带2个ADC的设备): 8种转换模式

6、最多有18个通道:16个外部通道,2个内部通道:连接到温度传感器和内部参考电压(VREFINT = 12V)。通道可以分为规则通道和注入通道,其中规则通道组最多有16路,是一种规规矩矩的通道,具有DMA功能,而注入通道组最多有4路,转换过程中可以中断,还可以在规则通道转换过程中插队。

其转换步骤如下:

• 使能端口时钟和ADC时钟

• 设置ADC通道控制器CCR(ADC模式(独立、双重、三重)、输入时钟分频等)

• 初始化ADC(分辨率、转换模式(单次、连续)、数据对齐方式等)

• 开启ADC

• 读取ADC转换值

测交流电信号要把交流电分压到5V 以内,再把负半周提升到零点以上,用运放做一个加法器(有专用交流电检测用的高精度运放,好像是MCP的),把零点抬高到25V,这样最低点也大于零,最高点不超过5V ,再用单片机在交流电一周期内采40个点,存入内存,计算出各离散值的均方根就是交流电的有效值。

或者把交流信号经(电压跟随器 + 全波整流 ),将负半周期信号反转到正半轴,得到半波交流信号,再用ADC进行测量。

我们来看看STM32之DAC的Resume(简历简介):

● 2个DAC转换器:每个转换器对应1个输出通道

● 8位或者12位单调输出

● 12位模式下数据左对齐或者右对齐

● 同步更新功能

● 噪声波形生成

● 三角波形生成

● 双DAC通道同时或者分别转换

● 每个通道都有DMA功能

● 外部触发转换

● 输入参考电压VREF+

注:PWM+RC滤波可以模拟DAC的输出方式。

在对DAC的输出频率要求不是太高的情况下,我们一般可以采用PWM+二阶RC滤波来模拟DAC,进行输出。PWM的占

空比来模拟幅值,二阶RC低通滤波电路用来截止谐波(一般只考虑基波即可),通过直流,具体详情如下:

在8位分辨条件下,我们一般要求1次谐波对输出电压的影响不要超过1个位的精度,也就是33/256=001289V。假设VH为33V,VL为0V,那么一次谐波的最大值是233/π=21V,这就要求我们的RC滤波电路提供至少-20lg(21/001289)=-44dB的衰减。STM32的定时器最快的计数频率是168Mhz,部分定时器只有84MHz,以84M为例,8为分辨率的时候,PWM频率为84M/256=328125Khz。如果是1阶RC滤波,则要求截止频率为207Khz,如果为2阶RC滤波,则要求截止频率为2614Khz。

1、PWM频率为328125Khz,那么一次谐波频率就是328125Khz;

2、1阶RC滤波,幅频特性为:-10lg[1+(f/fp)^2];fp为截止频率。

所以对一阶滤波来说,要达到-44dB的衰减,必须-10lg[1+(f/fp)^2]=-44; 得到f/fp=158486,即fp=328125/158486=207Khz。

3、2阶RC滤波,幅频特性为:-20lg[1+(f/fp)^2];fp为截止频率。

所以对二阶滤波来说,要达到-44dB的衰减,必须-20lg[1+(f/fp)^2]=-44; 得到f/fp=12549,即fp=328125/12549=2614Khz。

得到截止频率以后,我们可以紧接着求出R、C的值。如下图所示,R35C68=R36C69,fp=1/2πRC。

六、DMA 

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM 与I/O设备开辟一条直接传送数据的通路,能使CPU 的效率大为提高。

STM32F4中 有两个DMA,每个DMA控制器对应8个数据流,每个数据流又对应8个通道,其映射图如下图所示。DMA挂载的时钟为AHB总线,其时钟为72Mhz,所以可以实现高速数据搬运。

DMA的结构框图如下图所示,可以看出DMA各数据流需要仲裁优先级,且拥有FIFO先进先出缓存区(解决目标地址和原地址数据宽度不一致的情况)。

七、高性能计算能力

如下图所示,stm32F4自带DSP处理器,我们可以采用DSP库函数进行浮点运算等计算问题。

我们平常所使用的CPU为定点CPU,意思是进行整点数值运算的CPU。当遇到形如11+11的浮点数运算时,定点CPU就遇到大难题了。对于32位单片机,利用Q化处理能发挥他本身的性能,但是精度和速度仍然不会提高很多。

现在设计出了一个新的CPU,叫做FPU,这个芯片专门处理浮点数的运算,这样处理器就将整点数和浮点数分开来处理,整点数交由定点CPU处理而浮点数交由FPU处理。我们见到过TI的DSP,还有STM32F4系列的带有DSP功能的微控制器。前者笔者没有用过,不作评论,而后者如果需要用到FPU的浮点运算功能,必须要进行一些必要的设置。

八、加密

采用加密处理器CRYP,下面是CRYP的结构框图。

加密算法有AES加解密算法、DES/TDES加解密算法等

九、ART加速

由于生产工艺的限制,当CPU主频显著提高时,Flash的存取速度却只能处于一个较低的水平。

STM32F4有存储器加速(ART),可以使 CPU 频率高达 168 MHz 时在闪存中以 0 个等待周期执行程序。其秘密在于,STM32内部的Flash我记得是128bit的。这样每次读取时ART会把后面3条指令放到队列里。而Flash等待为5WS的话,每执行4条指令则必须停一次,再继续执行后4条指令。反正是性能跟同频的X86差不多顶个70%的样子(指Dothan处理器,纯逻辑运算)。超频我最高测过250MHz,还算比较稳定,结果没出错。再高结果就不对了。

不会。松下plc,是一种用于数字计算的可编程序控制器,是最新松下PLCFP-X0系列,专用于工业环境中。fp-x松下plc与触摸屏通讯中断不会报警。触摸屏(TouchPanel)又称为“触控屏”、“触控面板”,是一种可接收触头等输入讯号的感应式液晶显示装置。

先科普一下什么是OO什么是FP

OO:

OO(Object Oriented,面向对象)是当前计算机界关心的重,它是90年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。

OO方法(Object-Oriented Method,面向对象方法,面向对象的方法)是一种把面向对象的思想应用于软件开发过程中,指导开发活动的系统方法,简称OO (Object-Oriented)方法,是建立在“对象”概念基础上的方法学。对象是由数据和容许的 *** 作组成的封装体,与客观实体有直接对应关系,一个对象类定义了具有相似性质的一组对象。而每继承性是对具有层次关系的类的属性和 *** 作进行共享的一种方式。所谓面向对象就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建相应的软件系统。

FP:

FP全名为(Functional Programming函数式程序设计 )

函数式程序设计是一种设计、编制和调试函数式程序的技术。函数式程序是由一些原始函数、定义函数和函数型组成的函数表达式。

传统程序设计语言中的赋值等概念,在函数式程序设计语言中消失。函数式程序的一个最本质的特性,就是函数值唯一地由其参数值所确定。只要使用相同的参数值,对此程序的不同的调用总是得到相同的结果。这种性质称为引用透明性,有助于程序的模块化。函数式程序设计语言具有较强的组织数据结构的能力,可以把某一数据结构(如数组)作为单一值处理;可以把函数作为参数,其结果也可为函数,这种定义的函数称为高阶函数。这些由函数表达式所表示的程序简明、紧凑和易于维护。

过去,这种程序设计称为应用性程序设计。1977年,J巴克斯提出函数式程序设计的概念。一般认为表处理语言(LISP)是最早的函数式程序设计语言。但是,LISP的重点是将函数应用于对象,以产生新的对象,必要时再上升为函数。巴克斯所提出的函数式程序设计,则是引用函数型产生新函数,程序设计时从一般的对象空间上升到函数空间,因而具有优越的数学性质,有助于程序的理解、推理和验证。

人云亦云嘛,类似的例子太多了,什么东西都有人吹

兄弟! 是这样的!

程序开始将1,2,3,4,5,6写入"d2.dat”中,以换行符\n分隔。然后打开"d2.dat”,读取其中的数。

注意:for(i=0;i<3;i++)fscanf(fp,"%d%d",&k,&n);这段程序的意思是循环读取数字到k,n中。即1,2→3,4→5,6循环赋值给k,n。最后k,n的就是5,6。希望你能够明白!

fscanf(fp,"%d %d %d\n",a[0],a[1],a[2]);

==>

fscanf(fp,"%d %d %d\n",&a[0],&a[1],&a[2])//没有输入地址转换符&

你好。试试这个#include 

#include 

#include 

#include 

#include 

#include 

#include 

#define ESC 0x011b

#define UP 0x4800

#define DOWN 0x5000

#define LEFT 0x4b00

#define RIGHT 0x4d00

#define SPACE 0x3920

#define Y 0x1579

#define N 0x316e

#define clearkbd(); while(bioskey(1)) bioskey(0); /清空键盘缓冲队列/

void update();

void messagebox();

void process();

void initremove();

void initinfo();

void initbox();

void initposition();

void next_shape();

typedef struct shape /形状单一状态的记录/

{ int attr;

int co[8];

}shape;

typedef struct RE_AB /相对,绝对坐标记录/

{ int Rx,Ry;

int x1,x2,y1,y2;

}RE_AB;

RE_AB RA;

shape p[19]={ { RED,0,1,1,0,1,1,2,1 }, /数组中保证y最大的在最后,以便initposition使用/

{ RED,0,1,1,0,1,1,1,2 },

{ RED,0,0,1,0,2,0,1,1 },

{ RED,0,0,0,1,1,1,0,2 },

{ GREEN,0,0,1,0,2,0,3,0 },

{ GREEN,0,0,0,1,0,2,0,3 },

{ CYAN,0,0,0,1,1,0,1,1 },

{ BROWN,0,0,1,0,1,1,2,1 },

{ BROWN,1,0,0,1,1,1,0,2 },

{ BLUE,1,0,2,0,1,1,0,1 },

{ BLUE,0,0,0,1,1,1,1,2 },

{ MAGENTA,0,0,0,1,0,2,1,2 },

{ MAGENTA,2,0,0,1,1,1,2,1},

{ MAGENTA,0,0,1,0,1,1,1,2 },

{ MAGENTA,0,0,0,1,1,0,2,0 },

{ YELLOW,0,2,1,0,1,1,1,2 },

{ YELLOW,0,0,1,0,2,0,2,1 },

{ YELLOW,1,0,0,0,0,1,0,2},

{ YELLOW,0,0,0,1,1,1,2,1 },

};

int nback,nleft,nright,r_f[12][22],rs1,rs2,xcors,xcorb,ycors,ycorb; 

/检查方快有没有左,右,下接触,游戏区内所有格子有无颜色记录数组,rs1形状记录,rs2为提示框用,记录小格子在游戏区中的位置,按键存储/

void interrupt (oldint)(); /系统定时中断/

int count_down=0,count_other=0; /中断记时/

void interrupt newint() /设置新的中断程序/

{ count_down++;

count_other++;

oldint();

}

void intenable() /设置中断向量表,启动新的中断程序/

{ oldint=getvect(0x1c);

disable();

setvect(0x1c,newint);

enable();

}

void intrestore() /恢复中断向量/

{ disable();

setvect(0x1c,oldint);

enable();

}

void HZ12(int x0,int y0,int w,int color,char s) /根据字模,在dos下显示汉字/

/横坐标,纵坐标,字间隔,汉字颜色,汉字字符串/

{ FILE fp;

register char buffer[24];

register char str[2];

unsigned long fpos;/fpos为最终偏移动量/

register int i,j,k;

fp=fopen(hzk12,r);/打开1212汉字苦/

while(s)/一直到字符串结束为止/

{

if(s<0)/汉字输出/

{ str[0]=(s)-0xa0;

str[1]=(s+1)-0xa0;

fpos=((str[0]-1)94+(str[1]-1))24L;/计算汉字在hzk12的偏移量/

fseek(fp,fpos,SEEK_SET);/指针移动到当前位置/

fread(buffer,24,1,fp);/读取一个汉字到数组中/

for(i=0;i<12;i++)/12行/

for(j=0;j<2;j++)/两个字节/

for(k=0;k<8;k++)/8位/

if (((buffer[i2+j]>>(7-k))&0x1)!=NULL)/是一就画点/

putpixel(x0+8j+k,y0+i,color);

s+=2;/一个汉字占两个字节,现在将指针移动两个字节/

x0+=w;/显示坐标也按照间隔移动/

}

else/显示非汉字字符/

{ settextstyle(0,0,1);

setcolor(color);

str[0]=s;str[1]=0;

outtextxy(x0,y0+3,str);/显示单个字符/

x0+=w-7;/显示单个字符后的x坐标变化/

s++;/指针移动到下一个字节/

}

}

fclose(fp);

}

void translation() /把相对坐标解释为绝对坐标/

{ if(RARx==1)

{ RAx1=1; RAx2=16; }

else

{ RAx1=16(RARx-1); RAx2=16RARx; }

if(RARy==1)

{ RAy1=1; RAy2=16; }

else

{ RAy1=16(RARy-1); RAy2=16RARy; }

}

int check_b() /检查是否到达低部/

{ int x,y,i,zf=0; /zf为是否有颜色填充记录/

for(i=0;i<7;i++,i++)

{ x=RARx+p[rs1]co[i];

y=RARy+p[rs1]co[i+1];

if(y>=6)

zf+=r_f[x-15][y-6+1];

}

if(zf==0)

return 1;

else

return 0;

}

int finish()

{ int tfull=0,i; /判断顶层空间是否有填充/ 

for(i=1;i<11;i++)

tfull+=r_f[i][1];

if(tfull!=0)

return 1; /告诉judge()可以结束了/

}

int check_l() /检查形状是否与左接触/

{ int x,y,i,zf=0;

for(i=0;i<7;i++,i++)

{ x=RARx+p[rs1]co[i];

y=RARy+p[rs1]co[i+1];

if(y>6)

zf+=r_f[x-15-1][y-6];

if(y<=6&&x==16)

zf+=1;

}

if(zf==0)

return 1;

else

return 0;

int check_r() /检查形状是否与右接触/

{ /zf为是否有颜色填充记录/

int x,y,i,zf=0; /zf为是否有颜色填充记录/

for(i=0;i<7;i++,i++)

{

x=RARx+p[rs1]co[i];

y=RARy+p[rs1]co[i+1];

if(y>6)

zf+=r_f[x-15+1][y-6];

if(y<=6&&x==25)

zf+=1;

}

if(zf==0)

return 1;

else

return 0;

}

void check_touch()

{ nback=check_b();

nleft=check_l();

nright=check_r();

}

void draw(int cb) /画形状,cb=1以填充色画形状,cb=2以背景色画形状,cb=3以白色画形状/

{ int i,recordx=RARx,recordy=RARy;

for(i=0;i<7;i++,i++)

{ RARx+=p[rs1]co[i];

RARy+=p[rs1]co[i+1];

if(RARy<=6)

{ RARx=recordx;

RARy=recordy;

continue;

}

translation();

if(cb==1)

setfillstyle(1,p[rs1]attr);

else

if(cb==2)

setfillstyle(1,BLACK);

else

if(cb==3)

{ setfillstyle(1,WHITE);

r_f[RARx-15][RARy-6]=1; /置对应数组标记元素/

}

bar(RAx1+1,RAy1+1,RAx2-1,RAy2-1);

RARx=recordx;

RARy=recordy;

}

}

void mov(int key) /向下,左,右移动方块/

{ draw(2);

if(key==LEFT&&nleft)

RARx--;

else

if(key==RIGHT&&nright)

RARx++;

else

RARy++;

nback=check_b();

if(nback) /判断形状有没有到达底部,有就将其颜色变为白色/

draw(1);

else

draw(3);

}

void change() /变换形状/

{ int status=rs1,buffer,i,x,y,zf=0;

if(p[rs1]attr==p[rs1+1]attr)

rs1++;

else

while(p[rs1]attr==p[rs1-1]attr)

rs1--;

for(i=0;i<7;i++,i++) /检查变化形状后是否与已存形状发生冲突/

{ x=RARx+p[rs1]co[i];

y=RARy+p[rs1]co[i+1];

if(y>6)

zf+=r_f[x-15][y-6]; 

}

if(zf!=0) 

rs1=status; 

buffer=rs1;

rs1=status;

status=buffer;

draw(2);

buffer=rs1;

rs1=status;

status=buffer;

nback=check_b(); /判断变化后的形状是不是到达了低部,这个检查是十分必要的/

if(nback) 

draw(1);

else

draw(3);

}

void accelerate()

{ if(count_down>=1)

{ check_touch(); /消除上一步动作对方块状态的影响/

count_down=0;

if(nback) /0表示到达底部,1表示没有到达/

mov(DOWN);

}

}

void drawbox() /画方块所在方框/

{ int xcor,ycor;

for(xcor=xcors;xcor<=xcorb;xcor++)

for(ycor=ycors;ycor<=ycorb;ycor++)

{ if(xcor==xcors||xcor==xcorb||ycor==ycors||ycor==ycorb)

{ RARx=xcor;

RARy=ycor;

translation();

setfillstyle(1,DARKGRAY);

bar(RAx1+1,RAy1+1,RAx2-1,RAy2-1);

}

}

}

void erasure(int k)

{ int i,j,recordx=RARx,recordy=RARy;

{ j=k-1;

for(;j>0;j--)

{ for(i=1;i<11;i++)

{ r_f[i][j+1]=r_f[i][j];

RARx=i+15;

RARy=j+1+6;

translation();

if(r_f[i][j+1]==1)

setfillstyle(1,WHITE);

else

setfillstyle(1,BLACK);

bar(RAx1+1,RAy1+1,RAx2-1,RAy2-1);

RARx=recordx;

RARy=recordy;

}

}

}

}

void pause()

{ HZ12(450,400,15,BLACK,正常);

HZ12(450,400,15,GREEN,暂停);

for(;;)

if(bioskey(1)&&bioskey(0)==SPACE)

{ clearkbd();

HZ12(450,400,15,BLACK,暂停);

HZ12(450,400,15,RED,正常);

return;

}

}

void judge() 

{ int i,j,full=0; /full等于10说明某一行满,该消除了/ 

if(finish()) /判断游戏是否该结束了/

messagebox(); /win编程里有这个函数/ 

for(j=1;j<21;j++) /判断某一行是否满了/

{ for(i=1;i<11;i++)

full+=r_f[i][j];

if(full==10)

erasure(j); /消除这行/ 

full=0;

}

}

void update() /使程序可以重新运行/

{ cleardevice();

setbkcolor(BLACK);

initinfo(); /提示信息初始化/

initbox(); /游戏框架初始化/

srand((unsigned)time(NULL)); /随机器函数的初始化/

rs1=random(19);

rs2=random(19);

next_shape();

initposition(); /方块最开始的出现位置/

initremove(); /记录每个方格有无颜色填充数组初始化/

HZ12(450,400,15,RED,正常);

process();

}

void EXIT()

{ closegraph();

intrestore(); /恢复中断向量/

exit(0);

}

void initremove()

{ int i,j;

for(i=0;i<12;i++)

for(j=0;j<22;j++)

if(i==0||i==11||j==0||j==21)

r_f[i][j]=1;

else

r_f[i][j]=0;

}

void initinfo()

{ char aStr[2];

setcolor(RED);

outtextxy(450,100,This game's writer is:);

HZ12(450,140,15,RED,该程序作者:NULL);

outtextxy(525,110,NULL);

outtextxy(450,180,FUNCTION FOR KEYS:);

outtextxy(450,200,UP:change the shape);

outtextxy(450,210,DOWN:accelerate);

outtextxy(450,220,LEFT:move left);

outtextxy(450,230,RIGHT:move right);

outtextxy(450,240,ESC:exit this game);

outtextxy(450,250,SPACE:pause);

HZ12(450,260,20,RED,上:);

HZ12(450,280,20,RED,下:);

HZ12(450,300,20,RED,左:);

HZ12(450,320,20,RED,右:);

HZ12(450,340,20,RED,ESC:退出);

HZ12(450,360,15,RED,空格: 暂停/开始);

HZ12(450,380,15,RED,目前状态:);

HZ12(20,200,15,RED,下一个形状);

aStr[0]=24;

aStr[1]=0;

aStr[6]=0;

HZ12(480,260,12,GREEN,aStr);

HZ12(500,260,12,GREEN,( 变形 ));

aStr[0]=25;

aStr[1]=0;

HZ12(480,280,12,GREEN,aStr);

HZ12(500,280,12,GREEN,( 加速 ));

aStr[0]=27;

aStr[1]=0;

HZ12(480,300,12,GREEN,aStr);

HZ12(500,300,12,GREEN,向左);

aStr[0]=26;

aStr[1]=0;

HZ12(480,320,12,GREEN,aStr);

HZ12(500,320,12,GREEN,向右);

}

void messagebox()

{ int key;

setcolor(GREEN);

setfillstyle(1,DARKGRAY);

rectangle(220,200,420,300);

bar(221,201,419,299);

HZ12(280,210,15,GREEN,GAME OVER);

HZ12(275,230,15,GREEN,重新游戏: Y); 

HZ12(275,270,15,GREEN,退出游戏: N); 

HZ12(450,400,15,BLACK,正常);

HZ12(450,400,15,GREEN,GAME OVER);

for(;;)

if(bioskey(1))

{ key=bioskey(0);

if(key==Y)

{ clearkbd();

update();

}

else

if(key==N)

{ clearkbd();

EXIT();

}

else

clearkbd();

}

}

void initbox()

{ xcors=15; /画游戏框/

xcorb=26;

ycors=6;

ycorb=27;

drawbox();

xcors=2; /画提示框/

xcorb=7;

ycors=6;

ycorb=11;

drawbox();

}

void initposition()

{ RARx=18;

RARy=6-p[rs1]co[7];;

RAx1=0;

RAx2=0;

RAy1=0;

RAy2=0;

}

void next_shape() /画下一形状提示框/

{ int recordx=RARx,recordy=RARy,buffer;

RARx=3;

RARy=7;

draw(2);

buffer=rs1;

rs1=rs2;

rs2=buffer;

draw(1);

RARx=recordx;

RARy=recordy;

buffer=rs1;

rs1=rs2;

rs2=buffer;

}

void process() /游戏过程/

{ for(;;)

{ check_touch();

if(!nback)

{ rs1=rs2;

rs2=random(19); /产生另一种方块的码数/ 

initposition();

judge(); /判断某一行是否满了和这个游戏是否可以结束了/

draw(1);

next_shape();

}

if(count_other>=1) 

{ count_other=0; 

if(bioskey(1)) /对按键的处理/

{ int key=bioskey(0);

clearkbd(); /清除键盘缓冲队列/

if(key==ESC)

EXIT();

if(key==LEFT&&nleft&&nback)

mov(LEFT); 

if(key==RIGHT&&nright&&nback)

mov(RIGHT);

if(key==UP&&nback) 

change();

if(key==SPACE)

pause();

if(key==DOWN)

accelerate();

}

}

if(count_down>=4)

{ check_touch(); /消除上一步动作对方块状态的影响/

count_down=0;

if(nback) /0表示到达底部,1表示没有到达/

mov(DOWN);

}

}/for/

}

main()

{ int gdriver=DETECT,gmode=0; 

initgraph(&gdriver,&gmode,d:turboc); /启动图形与中断部分/

intenable();

update();

}

以上就是关于嵌入式高手进 考试题解答全部的内容,包括:嵌入式高手进 考试题解答、stm32内部功能最强的定气是、fp-x松下plc与触摸屏通讯中断会报警吗等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/10157807.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存