Github项目地址:AS608
项目实现了官方用户开发手册中所有列出的功能,函数声明在 as608.h
中。用户可直接调用相应的函数与AS608模块进行通信。
另外,项目中有一个命令行程序,可以在终端下通过命令与模块进行交互。
几天前在某宝上买了的AS608
模块。店家给了相关资料,有一个AS60x指纹识别SOC用户手册V10
,里面详细介绍了模块的工作方式和通信过程。简单来说,通过串口发给模块相应的指令(指令包,由指令头+参数+检校和
构成),模块返回应答包,读取应答包的内容取出指令执行的结果。
先是通过usb转ttl
工具(如下图),连接到笔记本电脑上。(PS. 就这玩意儿,我以前一位就是usb转3.3v或5v,临时当电源用呢,谁知道竟然是usb转TTL,用于串口通信的,长知识了)
通过软件(下图左)与AS608模块通信,确认模块工作正常。又通过XCOM
软件(下图右)调试模块,并结合官方开发手册,深入理解了模块的工作流程。
但是店家并没有给树莓派使用该模块的使用说明和资料,我在网上也没有找到任何相关的资料,单片机的资料倒是不少,但根本不适用树莓派。可能很少有人在树莓派上使用该模块吧。
所以我就自己开发了一套库函数,封装了通信流程,使用时,直接调用函数即可,更为直观简便。
本来只是想在主函数中写简单的代码,测试一下函数是否正常工作,后来一想,不如直接开发一个程序,实现模块的所有功能,于是就有了项目中的命令行程序fp
(fingerprint的缩写)。
更多功能,参见Github项目:AS608
与AS608模块的通信方式,就是向它发送16进制数据流。
以最简单的为例,读系统的基本参数,官方手册给的指令包如下
那吗,怎么构造指令包,用什么变量呢?
一、"EF01FFFFFFF0100030F0013"
二、{ 0xEF,0x0x,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x0F,0x00,0x13 }
当然,第二种是正确的,而且等价于字符串"\xEF\x0x\xFF\xFF\xFF\xFF\x01\x00\x03\x0F\x00\x13"
,实际上也是这么构造的
unsigned char order[12] = { 0xEF,0x0x,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x0F,0x00,0x13 }
// 或者
const unsigned char* order = "\xEF\x0x\xFF\xFF\xFF\xFF\x01\x00\x03\x0F\x00\x13";
char
(或者 unsigned char
)本质上是一种整数
类型,只是同时又能表示字符而已。
第一种的"EF01FFFFFFF0100030F0013"
在实际上存储的是{ 0x45,0x46,0x30,0x31,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x30,0x31,0x30,0x30,0x30,0x33,0x30,0x46,0x30,0x30,0x31 }
,所以,如果这样写的话,发送的给模块的显然是错误的指令,甚至是不能识别的。
由于芯片地址不是固定的,用户可以随时更改,而且指令包中可以带有参数,所以指令肯定不能写死,如:
const unsigned char* order = "\xEF\x0x\xFF\xFF\xFF\xFF\x01\x00\x03\x0F\x00\x13";
需要这样写,
unsigned char order[12] = { 0 };
// 然后填充order数组的每一位
// do something
在as608.c
文件中,有一个辅助函数,定义大致如下:
/*
* 把一个无符号整数,拆分为多个单字节整数
* 如num=0xa1b2c3d4,拆分为0xa1, 0xb2, 0xc3, 0xd4,
* 存入 buf+0, buf+1, buf+2, buf+3 位置处
*/
void Split(uint num, uchar* buf, int count) {
for (int i = 0; i < count; ++i) {
*buf++ = (num & 0xff << 8*(count-i-1)) >> 8*(count-i-1);
}
}
如何使用呢,比如,需要把芯片地址写入指令字符串order
中,芯片需要占用四个字节,也就是数组的四个元素位置(order[2] order[3] order[4] order[5]
)。而芯片地址存储在全局变量PS_CHIP_ADDR
(unsigned int
类型)中。unsigned int
占4个字节,unsigned char
占1个字节。如何把PS_CHIP_ADDR
存入order
指定位置中呢。
只需调用函数 Split(PS_CHIP_ADDR, order + 2, 4)
再来看看函数是如何实现的。假设,芯片地址为0xA1B2C3D4
,那么处理结果应该是
order[2] = 0xA1;
order[3] = 0xB2;
order[4] = 0xC3;
order[5] = 0xD4;
由位运算规则可知,
0xA1B2C3D4 & 0xFF000000 ---> 0xA1000000 // 提取前两位16进制数字,即八位2进制数字
0xA1000000 >> 24 ---> 0x000000A1 (即 0xA1) // 右移24位,得到0xA1,赋值给order[2]
同理,
0xA1B2C3D4 & 0x00FF0000 ---> 0x00B20000 // 提取前第3~4位16进制数字,即八位2进制数字
0x00B20000 >> 16 ---> 0x00000B2 (即 0xB2) // 右移16位,得到0xB2,赋值给order[3]
0xA1B2C3D4 & 0x0000FF00 ---> 0x0000C300 // 提取前第5~6位16进制数字,即八位2进制数字
0x0000C300 >> 8 ---> 0x00000C3 (即 0xC3) // 右移8位,得到0xC3,赋值给order[4]
0xA1B2C3D4 & 0x000000FF ---> 0x000000D4 // 提取前第7~8位16进制数字,即八位2进制数字
0x000000D4 >> 0 ---> 0x00000D4 (即 0xD4) // 右移0位,得到0xD4,赋值给order[5]
另外:还有一个与该函数相反操作的函数,用于从 模块的应答包 中取出数据,如把用两个unsigned char
存储的数值赋值给一个int
类型数据。
/*
* 把多个单字节整数(最多4个),合并为一个unsigned int整数
* 如0xa1, 0xb2, 0xc3, 0xd4, 合并为0xa1b2c3d4
*/
bool Merge(uint* num, const uchar* startAddr, int count) {
*num = 0;
for (int i = 0; i < count; ++i)
*num += (int)(startAddr[i]) << (8*(count-i-1));
return true;
}
PS. 这里返回
true
看似无意义,实际是为了将函数能够写在 && 和 || 两边。(返回类型为void的不能)程序中经常需要执行一连串的函数,任何一个被调函数返回 false ,则主调函数就返回false, 如:
bool foo(int* val) {
// do something ....
return (RecvReply(g_reply, 14) &&
Check(g_reply, 14) &&
Merge(val, g_reply+10, 2));
}
+
的优先级比 >>
、 <<
高
int ret = num1 + num2 >> 8;
等价于
int ret = (num1 + num2) >> 8; // √
int ret = num1 + (num2 >> 8); // ×
即先执行加法,再将结果右移8位。并非先把num2
右移8位,在加上num1
!!!
linux
平台下,使用vim
向文件 data 中写入如下内容(共3行)
address=0xffffffff
password=none
serial=/dev/ttyAMA0
文件格式为:内容+EOF 。
其中 EOF为vim文件内容的结束标志。
当用C语言读取文件内容时,文件指针要指向字符EOF之后才能判断文件已经结束。
当用fgets()
读完文件最后一行后,文件指针指向EOF字符,但此时使用feof()
判断,发现文件并未结束,而实际上已经结束,只有再次调用feof()
,才会返回false
。
假如现在要输出文件data中的内容
int main() {
FILE* fp = fopen("./data", "r");
char buf[32] = { 0 };
int lineCount = 0;
while (!feof(fp)) {
fgets(buf, 32, fp);
printf("%s", buf);
lineCount++;
}
printf("Line count is %d\n", lineCount);
return 0;
}
在windows下这么写没有问题,但是,在Linux系统下
输出结果为
address=0xffffffff
password=none
serial=/dev/ttyAMA0
serial=/dev/ttyAMA0
Line count is 4
显然,最后一行输出了两次 !!
所以,应该这么写while循环
while (!feof(fp)) {
fgets(buf, 32, fp);
if (feof(fp)) // 判断一下是否读到了EOF字符(此时文件指针在EOF之后)
break;
printf("%s", buf);
lineCount++;
}
type value = va_arg(ap, type); // 取出一个参数
type
绝对不能为以下类型:
char、signed char、unsigned char short、unsigned short signed short、short int、signed short int、unsigned short int float
float
类型的实际参数将提升到double
char
、short
和相应的signed
、unsigned
类型的实际参数提升到int
如果int
不能存储原值,则提升到unsigned int
通过 printf("\r")
实现。'\r'
可以将光标移动到本行开头,继续打印的字符将覆盖本行的内容。
这里给出一个通用的函数,如果程序中有循环语句,调用该函数就能打印进度条。
/*
* 显示进度条
* 参数:done(已完成的量) all(总量)
*/
void PrintProcess(int done, int all) {
// 进度条,0~100%,50个字符
char process[64] = { 0 };
double stepSize = (double)all / 100;
int doneStep = done / stepSize;
// 如果步数是偶数,更新进度条
// 因为用50个字符显示100个进度值
for (int i = 0; i < doneStep / 2 - 1; ++i)
process[i] = '=';
process[doneStep/2 - 1] = '>';
printf("\rProcess:[%-50s]%d%% ", process, doneStep);
if (done < all)
fflush(stdout);
else
printf("\n");
}
for (int i = 1; i <= 180; ++i) {
PrintProcess(i, 180);
usleep(100000); // 暂停0.1s
}
输出结果如下
Author:iC
Email:[email protected]
Thanks for Reading !