MSFunction原理

CydiaSubstrate,iOS7越狱之前名为 MobileSubstrate(下文简称为MS或MS框架),作者为大名鼎鼎的Jay Freeman(saurik).

MS框架为越狱iDevice提供了一个稳定的代码修改平台。开发者可以很方便的利用它进行各种插件开发工作。可以说目前很多插件都是基于MS实现,比如activator、barrel、KuaiDial等等。

目前大部分人对MS的认知也只停留在表面(API Hook 应用),却很少有人探究它的原理。今天笔者就以逆向分析的角度,去一探究竟。

 

 

准备工作

我们用Xocde创建一个空目标工程mssheep.app,并设定它的Bundle ID为com.rainyx.mssheep,再把他生成到iPhone上,这样我们就可以通过com.rainyx.mssheep这个Bundle ID对目标程序mssheep进行注入了,当然前提是要有MS的支持;p.

 

 再创一个动态库工程mswolf、用于Hook目标程序mssheep。在iOSOpenDev生成的模版中可以看到,按照MS的规则生成了两个文件,分别是/Library/MobileSubstrate/DynamicLibraries/xxx.dylib和/Library/MobileSubstrate/DynamicLibraries/xxx.plist,打开Plist在filters中加入com.rainyx.mssheep这个Bundle ID,然后同样生成到iPhone上。

 

在iOS桌面启动mssheep.app。然后SSH到iOS,GDB attach到目标进程,打info sh查看加载的库,在列表中有mswolf.dylib,说明我们的动态库成功被目标进程加载,这时候想干什么坏事就都可以干了/:阴险。

 

修改mswolf工程加入Hook部分代码,此次我们Hook fopen这个API。

FILE  * ( *old_fopen ) ( const  char  *path ,  const  char *mode ) ;

FILE  *my_fopen ( const  char  *path ,  const  char  *mode )
{
     // 这里什么都不干
     return old_fopen (path , mode ) ;
}

void Initialize ( )
{
    MSHookFunction ( & fopen ,  &my_fopen ,  ( void ** ) &old_fopen ) ;
}

我们要让mswolf在目标程序加在后运行Initialize函数,需要在Build Settings的Other Link Flags中加入-init _Initialize选项.编译后生成到iPhone上,再次运行mssheep。

 

 

入口跳转分析

目标API fopen被Hook以后,会跳转到我们自己定义的函数入口,即my_fopen,在ARM体系中,由寄存器PC来控制程序的执行顺序,换句话说,寄存器PC保存了下一条要被执行的指令的地址。MS如何改变流程,看下反汇编代码便知。

SSH到iPhone,GDB附加到目标进程,打disassemble fopen,如下:

0x3a043694 <fopen + 0> :  bx pc
0x3a043696 <fopen + 2> :  nop    ( mov r8 , r8 )
0x3a043698 <fopen + 4> : blx  0x3a4480d8
0x3a04369c <fopen + 8> : ldr r1 ,  [r1 , # 120 ]
0x3a04369e <fopen + 10> : lsls r1 , r1 , # 0
0x3a0436a0 <fopen + 12> :  mov r8 , r0
0x3a0436a2 <fopen + 14> :  mov r0 , r1
0x3a0436a4 <fopen + 16> :  mov r1 , r2
0x3a0436a6 <fopen + 18> : blx  0x3a0969ac<dyld_stub___sflags>
0x3a0436aa <fopen + 22> :  mov r5 , r0
0x3a0436ac <fopen + 24> :  movs r6 , # 0
0x3a0436ae <fopen + 26> :  cmp r5 , # 0
0x3a0436b0 <fopen + 28> : beq .0x3a04374e <fopen + 186>
0x3a0436b2 <fopen + 30> :  movs r0 , # 1
0x3a0436b4 <fopen + 32> : blx  0x3a09698c<dyld_stub___sfp>

其实fopen的函数入口的前n个字节已经被修改,下面是未Hook的fopen的前n字节。

0x3a043694 <fopen + 0> :  push  {r4 , r5 , r6 , r7 , lr }
0x3a043696 <fopen + 2> :  add r7 ,  sp , # 12
0x3a043698 <fopen + 4> :  str .w r8 ,  [ sp , # - 4 ]!
0x3a04369c <fopen + 8> :  sub  sp , # 4
0x3a04369e <fopen + 10> :  mov r2 ,  sp
0x3a0436a0 <fopen + 12> :  mov r8 , r0
0x3a0436a2 <fopen + 14> :  mov r0 , r1
0x3a0436a4 <fopen + 16> :  mov r1 , r2
0x3a0436a6 <fopen + 18> : blx  0x3a0969ac<dyld_stub___sflags>
0x3a0436aa <fopen + 22> :  mov r5 , r0
0x3a0436ac <fopen + 24> :  movs r6 , # 0
0x3a0436ae <fopen + 26> :  cmp r5 , # 0
0x3a0436b0 <fopen + 28> : beq .0x3a04374e <fopen + 186>
0x3a0436b2 <fopen + 30> :  movs r0 , # 1
0x3a0436b4 <fopen + 32> : blx  0x3a09698c<dyld_stub___sfp>

可以比较两段汇编代码发现,一直到<fopen+12>才开始匹配。那么前12个字节的会变代码,一定就是我们之前所说的跳转代码(改变寄存器PC的值)。

进行逐行分析:

0x3a043694 <fopen + 0> :  bx pc 

bx带状态切换的无条件跳转,因为ARM流水线,PC目前指向当前地址+4的位置。

0x3a043698 <fopen + 4> : blx  0x3a4480d8

blx带状态并返回的无条件跳转,此处地址为0x3a4480d8,用GDB反汇编一下此地址

(gdb) disassemble 0x3a4480d8 0x3a4480d8+20
Dump of assembler code from 0x3a4480d8 to 0x3a4480ec:

0x3a4480d8 : andeq r0 , r0 , r4
0x3a4480dc : bcc  0x3b592594
0x3a4480e0 : subcc r8 , r7 , r10 , asr # 1
0x3a4480e4 : subcc r3 , r9 , r7 ,  lsl # 14
0x3a4480e8 : andeq r0 , r0 , r2

End of assembler dump.

是一堆乱七八糟的指令,肯定是不能够运行的,那为什么会跳转到这个地址呢?别忘了
0x3a043694 <fopen+0>: bx pc 
这个是带状态转换的跳转指令,已知fopen入口为Thumb指令状态,GDB译码的时候当然是按Thumb状态下进行译码的。由于已知目前是Thumb状态而且pc指向一个非Thumb状态的地址0x3a043698,所以执行到0x3a043698这个地址的时候,要按ARM32进行译码。

得出了以上结论,我们在看看0x3a043698在ARM32状态下是什么。

 

在GDB中,敲入以下命令强制改变反汇编器的译码方式。
set arm force-mode arm
但是很不幸我的GDB(1821)并不支持这条命令,那怎么办呢?GDB在无symbol的情况下译码是通过cpsr的t位来决定译码方式的,那么我们目前t位正好是0,也就是ARM32方式译码。

 

所以说找一块无symbol的内存地址,把0x3a043698内容写入该地址然后进行反汇编就可以了。为了简单我索性就把基址拿过来用(是不是太暴力了)。
复制后两条指令内容到目标地址:
(gdb) set *0x2c000 = *0x3a043698
(gdb) set *0x2c004 = *0x3a04369c
(gdb) disassemble 0x2c000 0x2c008
Dump of assembler code from 0x2c000 to 0x2c008:

0x0002c000 <_mh_execute_header + 0> : ldr pc ,  [pc ,# - 4 ]  ; 0x2c004 <_mh_execute_header+4>
0x0002c004 <_mh_execute_header + 4> : andeq r3 , r8 ,r9 ,  lsl # 31

End of assembler dump.

 

GDB打印出正确的指令
0x0002c000 <_mh_execute_header+0>: ldr pc, [pc, #-4] ; 0x2c004 <_mh_execute_header+4>
ldr指令从读取一个内存地址的值到寄存器中,此刻pc = *(pc – 4),也就是 pc = *0x0002c004,不出意外这里应该是我们my_fopen的函数入口地址。

 

用GDB看下:
(gdb) p/x *0x0002c004
$1 = 0x83f89
(gdb) disassemble 0x83f89          
Dump of assembler code for function my_fopen:

0x00083f88 <my_fopen + 0> : movw r2 , # 120  ; 0x78
0x00083f8c <my_fopen + 4> : movt r2 , # 0  ; 0x0
0x00083f90 <my_fopen + 8> :  add r2 , pc
0x00083f92 <my_fopen + 10> : ldr r2 ,  [r2 , # 0 ]
0x00083f94 <my_fopen + 12> : ldr r2 ,  [r2 , # 0 ]
0x00083f96 <my_fopen + 14> :  bx r2

End of assembler dump.

确实为我们自定义的函数my_fopen。

目前我们完成了前12字节的跳转分析。

休息一下,思考另外一个问题:
在我们的my_fopen函数中

FILE  *my_fopen ( const  char  *path ,  const  char  *mode )
{
     // 这里什么都不干
     return old_fopen (path , mode ) ;
}

调用了old_fopen函数,那么old_fopen指针应该指向哪里?

 

 

备份分析

有些人认为应该指向这个地址:

0x3a0436a0 <fopen + 12> :  mov r8 , r0

其实不然,一个函数的调用周期关系到堆栈平衡的问题,在原函数入口和出口,进行了对堆栈平衡的操作

0x3a043694 <fopen + 0> :  push  {r4 , r5 , r6 , r7 , lr }
....
0x3a043756 <fopen + 194> :  pop  {r4 , r5 , r6 , r7 , pc }

如果直接调用fopen的第12字节,即使能调用成功,pop指令也没有匹配的push指令执行。导致堆栈失衡,程序崩溃。换句话说,调用了一个“残废”的函数,你能确保它的结果是正确的吗?除非你做了它“残废”后做不了的事儿(实际上也就是如此,不过我们还是来印证一下)。

 

还是来看一下MS到底把old_fopen指针指向哪里吧,
在mswolf工程的Initialize中稍加修改:

void Initialize ( )
{
    MSHookFunction ( & fopen ,  &my_fopen ,  ( void ** ) &old_fopen ) ;
    NSLog (@ "Pointer is 0x%x" , old_fopen ) ;
}

编译生成到iPhone,重新运行mssheep。查看日志:

Feb 27 14:15:45 xPhone mssheep[851] <Warning>: Pointer is 0×76001

地址为:0×76001,用GDB附加查看。

(gdb) disassemble 0×76001 0×76001+50
Dump of assembler code from 0×76001 to 0×76033:

0x00076001 :  push  {r4 , r5 , r6 , r7 , lr }
0x00076003 :  add r7 ,  sp , # 12
0x00076005 :  str .w r8 ,  [ sp , # - 4 ]!
0x00076009 :  sub  sp , # 4
0x0007600b :  mov r2 ,  sp
0x0007600d :  bx pc
0x0007600f :  nop    ( mov r8 , r8 )
0x00076011 : blx  0x47aa50
0x00076015 : adds r6 , # 161
0x00076017 : subs r2 , # 4

看前12字节

0x00076001 :  push  {r4 , r5 , r6 , r7 , lr }
0x00076003 :  add r7 ,  sp , # 12
0x00076005 :  str .w r8 ,  [ sp , # - 4 ]!
0x00076009 :  sub  sp , # 4
0x0007600b :  mov r2 ,  sp

有的朋友一下就看出是怎么回事了,没错,这12字节就是fopen被替换的那前12字节:

0x3a043694 <fopen + 0> :  push  {r4 , r5 , r6 , r7 , lr }
0x3a043696 <fopen + 2> :  add r7 ,  sp , # 12
0x3a043698 <fopen + 4> :  str .w r8 ,  [ sp , # - 4 ]!
0x3a04369c <fopen + 8> :  sub  sp , # 4
0x3a04369e <fopen + 10> :  mov r2 ,  sp

再看后12字节:

0x0007600d :  bx pc
0x0007600f :  nop    ( mov r8 , r8 )
0x00076011 : blx  0x47aa50
0x00076015 : adds r6 , # 161

上面我们分析的fopen入口调转如出一辙,这里就不再赘述。0×00076015-1这个地址的内容应该就是原fopen+12的地址。
看下便知:
(gdb) p/x *(0×00076015-1)
$1 = 0x3a0436a1
(gdb) disassemble 0x3a0436a1 0x3a0436a1+20
Dump of assembler code from 0x3a0436a1 to 0x3a0436b5:

0x3a0436a1 <fopen + 13> :  mov r8 , r0
0x3a0436a3 <fopen + 15> :  mov r0 , r1
0x3a0436a5 <fopen + 17> :  mov r1 , r2
0x3a0436a7 <fopen + 19> : blx  0x3a0969ac<dyld_stub___sflags>
0x3a0436ab <fopen + 23> :  mov r5 , r0
0x3a0436ad <fopen + 25> :  movs r6 , # 0
0x3a0436af <fopen + 27> :  cmp r5 , # 0
0x3a0436b1 <fopen + 29> : beq .0x3a04374e <fopen + 186>
0x3a0436b3 <fopen + 31> :  movs r0 , # 1

End of assembler dump.

如此一来,我们在调用原fopen的功能前,先经过0×76001,恢复被修改的代码,然后再跳转到原fopen函数。这样的“拼接”完成了一次完整的调用,也就不会存在堆栈失衡的情况,程序保持正常运行。

 

总结

至此我们完成了整个Hook原理的分析,完成Hook功能,要做的事情大致有两件
1.修改目标函数前N字节,跳转到自定义函数入口;
2.备份目标函数前N个字节,跳转回目标函数。

此文分析的是在Thumb状态下进行的Hook,其实ARM32、ARM64我想原理也基本一样,只是指令和寻址方式有所不同。有兴趣的朋友可以研究一下!

所用设备:
1.Mac book pro
2.iPhone5(A1429)

所用工具:
1.SSH
2.GDB(1821) for iOS
3.Xcode 5.0 with iOSOpenDev

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
摘要 图书管理系统是典型的信息管理系统(MIS),其开发主要包括后台数据库的建立和维护 以及前端应用程序的开发两个方面。对于前者要求建立起数据一致性和完整性强、数据 安全性好的数据库。而对于后者则要求应用程序功能完备,易使用等特点。结合图书馆公 共检索管理的要求,对MS SQL Server2000数据库管理系统、SQL语言原理、Power Builder 9.0应用程序设计,Power Builder 9.0数据库应用技术进行了较深入的学习和应用,主要完成对图书管理系统的需求分析、 功能模块划分、数据库模式分析,并由此设计了数据库结构和应用程序。 本设计所完成的图书管理系统软件是功能较完善的数据管理软件,数据操作方便高效 。该软件采用功能强大的数据库软件开发工具进行开发,可在应用范围较广的Windows系 列操作系统上使用。 关键词: 图书管理系统;数据库安全性;Power Builder 9.0;MS SQL Server2000 ABSTRACT Search public library management information system is a typical management information system (MIS), including the development of its database background to the establishment and maintenance, and front-end application development aspects. With regard to the former request to establish strong data consistency and integrity, data security a good database. For the latter request applications fully functional, easy to use and so on. Search the management of public libraries with the requirements of the MS SQL Server2000 database management system, SQL language principle, Power Builder 9.0 application design, Power Builder 9.0 database application technology for a more in-depth study and application of the completion of the main public library management information retrieval System requirements analysis, functional module of the database model analysis and design of this structure of the database and applications. The design by the Public Library Management Information Retrieval System Software is a function of a better system of data management software, with data to facilitate efficient operation of the advantages of the rapid. The software used a powerful database software development tools for development, has a good portability, the application of a wide range of Windows family of operating systems used. Keywords: Public library management information retrieval system, Database security; Power Builder 9.0; MS SQL Server2000 目录 摘要 I ABSTRACT II 第1章 引言 1 第2章 问题定义 2 2.1 现行系统存在的问题 2 2.2 项目目标 2 2.3 项目范围 2 第3章 可行性研究 3 3.1 现行系统调研 3 3.1.1 现行系统目标 3
1、CAN收发队列 使用内存FIFO缓冲CAN帧,适合大数据量通信;并使用内部软中断处理CAN数据,相当于事件响应,综合应该比查询方式节省不少时间,也应该比OS调度省点时间。Can.C:底层处理,Communi.C:与应用层高相关。 应用层处理流程用函数指针表的方式调用减少代码量及阅读整齐;实现CAN各种错误记录机制。已初步测试,该机制可用。 CAN处理流程: 接收:CAN1_RX0_IRQHandler_Name (void), CAN1_RX1_IRQHandler(void) 接收中断,CAN_QueueWriteQuick()将当前的有效报文压入内存FIFO,压入的数据为整个CAN邮箱数据,所以后续的处理函数可以分辨出完整的数据。 void CAN1_RX0_IRQHandler_Name (void) // CAN1_RX0_IRQHandler_Name { /* FIFO从空状态开始,在接收到第一个有效的报文后,FIFO状态变为挂号_1(pending_1), 硬件相应地把CAN_RFR寄存器的FMP[1:0]设置为’01’(二进制01b)。 软件可以读取FIFO输出邮箱来读出邮箱中的报文,然后通过对CAN_RFR寄存器的RFOM位 设置’1’来释放邮箱,这样FIFO又变为空状态了。如果在释放邮箱的同时, 又收到了一个有效的报文,那么FIFO仍然保留在挂号_1状态,软件可以读取FIFO 输出邮箱来读出新收到的报文。 如果应用程序不释放邮箱,在接收到下一个有效的报文后,FIFO状态变为 挂号_2(pending_2),硬件相应地把FMP[1:0]设置为’10’(二进制10b)。 重复上面的过程,第三个有效的报文把FIFO变为挂号_3状态(FMP[1:0]=11b)。 此时,软件必须对RFOM位设置1来释放邮箱,以便FIFO可以有空间来存放下一个有效的 报文;否则,下一个有效的报文到来时就会导致一个报文的丢失。 */ while (CAN1->RF0R & CAN_RF0R_FMP0) // message pending ? { CAN_QueueWriteQuick(&CanRxQueue;, (T_CanFrame *)&CAN1;->sFIFOMailBox[CAN_FIFO0]); CAN1->RF0R |= CAN_RF0R_RFOM0; // Release FIFO 0 output mailbox #if CAN1_SWI_HANDLE_EN > 0 /* Add by Xsky 2011-06-18 15:48 */ EXTI->SWIER |= CAN1_SWI_EXTI_LINE; /* Add by Xsky 2011-06-18 15:47 */ #endif } } void CAN1_RX1_IRQHandler (void) { while (CAN1->RF1R & CAN_RF1R_FMP1) // message pending ? { CAN_QueueWriteQuick(&CanRxQueue;, (T_CanFrame *)&CAN1;->sFIFOMailBox[CAN_FIFO1]); CAN1->RF1R |= CAN_RF1R_RFOM1; // Release FIFO 1 output mailbox #if CAN1_SWI_HANDLE_EN > 0 /* Add by Xsky 2011-06-18 15:48 */ EXTI->SWIER |= CAN1_SWI_EXTI_LINE; /* Add by Xsky 2011-06-18 15:47 */ #endif } } 接收中断响应后,触发STM32的内部软中断(EXTI->SWIER |= CAN1_SWI_EXTI_LINE;), 实现当CAN硬件中断响应完成后,触发更低优先级的中断去处理内存中的CAN数据队列,如果处理时再发生新的CAN硬件接收中断,则会先响应硬件中断,以减少或不丢失CAN FIFO邮箱数据。处理函数在Communi.C中实现。 发送,CAN_SendFrame(): 发送时如果邮箱有空则直接将数据压入邮箱,否则将数据压入内存发送队列。等待上一次数据发送完成时,在发送中断中提取FIFO发送队列中的下一帧数据并发出。 Communi.C的功能为与应用层相关度较高的函数,如发送应用层帧,记录错误。 CAN1_SWI_Handler (void) 实现CAN接收中断触发的内部软件中断,处理内存FIFO接收的CAN数据(实际编译函数名为:EXTI4_IRQHandler())。 处理过程优化:通过定义顺序的code码,查表调用处理函数列表指针可实现比较整齐并有效率的代码机制。 CAN1_SCE_IRQHandler()实现进行错误记录(g_History.SysErrors.xxx以便于统计CAN错误)及相应处理。 个人认为这种处理方式,近似于OS的多任务,同时减少调度开销,是在可重用性与效率之间的平衡用法。当然这种处理方式,也适合于做为uCOS中的底层驱动文件,已留有CAN_QUE_OS_ENTER_CRITICAL()的宏定义,更改为相应的OS开关中断函数基本即可用于uCOS。 附 CAN总线利用率及最坏时间估算.xls, 根据应用层估计的数据发送频度最大值,自动估计CAN总线上导致的最大延时是否满足应用需求。 2、UART模板 UART DMA/中断处理方式 文件模板,可仅修改头部定义实现完全配置某指定的UART端口,以实现执行效率与代码重用的折中,UART.C,UARTx.H,UARTn.C。 UARTx.H为公共代码文件,#include被包含在UART1.C,、UART2.C、……中(用UARTn.C指代)实现所有的接收、发送的中断处理函数,在UARTn.C中宏定义各中断向量函数名以及各种硬件相关参数,定义接收发送的内存缓冲区长度等。代码实现DMA及中断响处理两种方式,通过宏定义选择编译不同代码,接收使用定时器实现字符超时指示功能,DMA接收时多使用了一级DMA接收专用内存缓冲RxDMABuf,因为DMA只能按地址连续写内存。 接收发送均使用内存缓冲区,以尽量避免中断响应时间导致的接收数据丢失问题,以及避免查询等待方式的较低效率。 发送函数:UART1_SendBytes(),UART2_SendBytes(),... 检查接收缓冲区字节数:UART1_RcvdSize() 读取指定的字节数:UART1_ReadBytes() 上层使用方法:循环检测UART1_RcvdSize()是否大于0,大于则进行读取等下一步处理,也可再定义高一级的应用层帧缓冲,以实现应用层的完整帧处理,或者增加一个对接收FIFO的预读功能,即读取时对接收FIFO中的帧进行识别,如果不是完整的应用层帧则再等待数ms或者再等待数次,等待失败则超时丢弃本帧,寻找下一帧。当然也可以在中断中增加事件机制,类似CAN中断触发低优先级软件中断,多个串口可在同一个软件中断服务中处理。 调试输出DbgPrintf函数,Function.C。 已使用大量连续数据测试该机制收发均可用,UART1~5均可用。使用本方式的考虑是在执行效率与代码重用间的平衡,部分代码使用了ST的库,如初始化时不时间使用不高时,而中断处理则基本是直接操作寄存器。并且均考虑了做为uCOS的接口,直接替换UARTx_ENTER_CRITICALx()、UARTx_EXIT_CRITICALx()函数应该可以基本实现做为uCOS的底层驱动。 注:包含UARTx.H的方式,各个UARTn.C文件重用其中代码, 某些情况下编译器可能会编译出错误的问题,具体原因还不明。但方法确实是可行的,已测试STM32F103VCT6 UART1~5均可。 3、用逻辑分析仪测定过的延时函数:Delay.C,内核72MHz,具体延时时间已注释标注. 如: void Delay_Nms(unsigned long N) { long count;//=14200; while(N) { count = 7998; /* 逻辑分析仪测试, 包含引脚取反时间 14200, 80ms: 142.026ms, 10000, 80ms: 100.025ms 8000, 80ms: 80.25ms 7975, 80ms: 79.775ms 7990, 100ms: 99.907ms 7998, 150ms: 150.01ms 7997, 150ms: 149.992ms */ while(count--); /* while(count--); 0x08001236 000A MOVS r2,r1 0x08001238 F1A10301 SUB r3,r1,#0x01 0x0800123C 4619 MOV r1,r3 0x0800123E D1FA BNE 0x08001236 */ N--; } } 4、输入检测 中断定时进行输入扫描,定义有效无效电平消抖时间,且定义按下弹起事件响应函数指针,应该会比循环扫描节省很多时间IOInput.C。已测试,机制完好。 // 常量表定义 typedef void t_IOIN_EVENT_DO(T_IOEvent); typedef uint8 t_IOIN_Counter; typedef struct t_IOIN_INFO_ { uint8 ID; // 输入信号索引 T_ValidVoltage ValidVoltage; // 有效电平,高电平或者低电平有效 __IO uint32_t *IDR; // 输入引脚指针 uint32_t PinBitMsk; // 输入引脚位掩码 t_IOIN_Counter CntValid; // 有效状态计数, 注意如果改变ValidVoltage, 此相应需要修改此参数,为确认该电平输入为有效电平的计数值,相当于滤波参数 t_IOIN_Counter CntInvalid; // 无效状态计数, 注意如果改变ValidVoltage, 此相应需要修改此参数,为确认该电平输入为无效电平的计数值,相当于滤波参数 t_IOIN_EVENT_DO *EventHandler; // 执行函数指针 }t_IOIN_INFO; static const t_IOIN_INFO IOIN_InfoTbl[IOIN_NUMS] = { // index, ValidVol, GPIO->IDR, IO_MASK, CNT, CNT~, EventHandler {IOIN_Key1, VOL_Low, &GPIOB;->IDR, BIT( 0), 4, 4, NULL }, {IOIN_Key2, VOL_Low, &GPIOB;->IDR, BIT( 1), 4, 4, NULL }, {IOIN_Key3, VOL_Low, &GPIOB;->IDR, BIT( 6), 4, 4, NULL }, {IOIN_Key4, VOL_Low, &GPIOB;->IDR, BIT( 7), 4, 4, KeyESC_Event }, {IOIN_CenterSensor, VOL_High, &GPIOC;->IDR, BIT( 7),10,10, ColorSensorEvent }, // 使其常态电平5ms推迟于颜色检测线(以检测), 有效电平2ms提早于颜色线 {IOIN_VacuumHousing,VOL_Low, &GPIOE;->IDR, BIT(12), 4,10, NULL }, {IOIN_BallCounter, VOL_Low, &GPIOE;->IDR, BIT(15), 4, 4, NULL }, {IOIN_Spin, VOL_Low, &GPIOE;->IDR, BIT(14), 3, 3, NULL }, {IOIN_ColorRed, VOL_Low, &GPIOD;->IDR, BIT( 8),16, 6, NULL }, // 使常态电平3ms提前于庶断传感器, 有效电平8ms推迟于遮断传感器 {IOIN_ColorGreen, VOL_Low, &GPIOD;->IDR, BIT( 9),16, 6, NULL }, {IOIN_ColorBlue, VOL_Low, &GPIOD;->IDR, BIT(10),16, 6, NULL } }; 使用时主要修改以上这张表的指向及消科参数,并且对中断处理函数中的顺序或者扫描的最小间隔进行区分即可。 5、颜色传感器驱动 颜色传感器TCS3200D驱动ColorSensor.C。已测试,机制完好。测量频率范围 20Hz~120KHz, S0:S1:HL。 6、铁电驱动 SPI方式读写铁电,实现片写片读函数Spi_FRAM.C;参数及历史记录读写检测函数GameParam.C。 7、其它一些可参考的模板、文件、函数、或者小的方式方法等。 by Xsky 原创STM32项目处理方法(其中个别有文件为直接用的,已注明,如周立功的)。 仅供参考交流,QQ:1821587421 其它可交流方案: GPS车辆监控系统:终端原理图PCB源码整套(稳定成熟可接多个外设);平台整套源码。 LED屏:公交,出租等 原理图PCB;PC端软件等;PDA控制LED屏程序源码。 DVR:小型SD卡录像方案,可485拍照。 公交报站器,原理图PCB;PC端软件。51版,STM32版。 汽车电动台阶驱动板原理图PCB。 PDA扫描轮:条形码扫描,GPRS上传;终端原理图PCB源码整套,服务端源码 手持公交售票终端源码,可打印小票。终端价位特低。 稳定使用的固态继电器原理及PCB(光耦隔离控制双向可控硅)。 直流电机驱动板。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值