项目介绍
这次项目基于 PIC-IoT WA 开发板,通过板上光照传感器识别挥过的手指数量,并发送到串口,然后在上位机显示。
PIC-IoT WA
PIC-IoT WA(EV54Y39A) 是 Microchip 推出的一款 IoT 开发板。板上搭载了 PIC24FJ128GA705 MCU,CryptoAuthentication 安全 IC 和 ATWINC1510 Wi-Fi 模块。除此还有温度传感器,光照传感器,按键,LED,调试器。
本项目只用到光照传感器,LED 和 串口。
程序实现
主循环
main 函数在初始化 ADC, Timer, LED 之后会进入到一个循环。循环会检查 ADC 转换结果,如果有结果就更新状态机。如果状态机有输出就发到串口。
volatile int conversion
static FingerCounter fc;
int main(void) {
// initialize the device
SYSTEM_Initialize();
// initialize ADC
ADC1_Initialize();
ADC1_Enable();
ADC1_ChannelSelect(LIGHT);
FingerCounter_Reset(&fc, 0);
int conv = 0;
while (1) {
conv = conversion;
if (conv != 0) {
conversion = 0;
FingerCounter_Update(&fc, conv);
}
int count = FingerCounter_ReadOutput(&fc);
if (count != 0) {
printf("✅Fingers:%2d\n", count);
}
}
return 1;
}
ADC
我使用了 Timer 定时触发 ADC 转换,并且打开 ADC 中断,可以在中断里读取 ADC 转换结果。这样在主循环里就不需要循环等待 ADC 转换,可以直接处理其他事件。
初始化代码上面已经列出,下面是中断处理函数,把 ADC 结果写到全局的 volatile 变量就可以了:
volatile int conversion;
void ADC1_CallBack(void) {
conversion = ADC1_ConversionResultGet(LIGHT);
}
数据分析和算法设计
可以通过 ADC 读到这样的数据,上面是 4 个手指缓慢挥过的情况,下面是1个手指快速挥过的情况。
如果使用尖峰检测算法,需要保存比较多的数据来应对缓慢通过的情况:
观察数据可以发现一次下降和一次上升就表示一直手指通过了,而噪声、缓慢通过、停留可以当做 0 斜率处理:
结合状态机就能方便地计算上升,下降的次数和手指数:
数据记录和滑动平均
因为计算斜率只需两个点,可以通过一个结构体保存两个数据:
typedef struct {
int prev;
int curr;
} Measure;
记录数据时做一下滑动平均:
void Measure_Push(Measure *m, int i) {
m->prev = (m->prev * 3 + m->curr) >> 2;
m->curr = (m->curr * 3 + i) >> 2;
}
左边是原始数据,右边是平滑之后的数据:
放大可以发现,其实还不是很平滑
所有在求斜率方向的时候,再做一次滤波,低于阈值(10)的斜率都当作 0 处理:
typedef enum {
Zero = 0, Up = 1, Down = -1
} Direction;
Direction Measure_Direction(Measure *m, State s) {
int derivate = m->curr - m->prev;
if (abs(derivate) <= DERIVATE_THRESHOLD) {
return Zero;
}
if (derivate > 0) {
return Up;
} else {
return Down;
}
}
处理后得到 0, -1, 1 三种数值,可以输入到状态机进行处理:
状态机实现
定义结构体保存所有状态和数据记录:
typedef enum {
Idle = 0, Negative = -1, Positive = 1
} State;
typedef struct {
State state;
Measure m;
int fingers;
int zeros;
int output;
int events;
} FingerCounter;
状态机实现就是根据当前状态和输入的值,更新变量和迁移到下一个状态。根据图写代码即可:
void FingerCounter_Update(FingerCounter *fc, int i) {
Measure_Push(&fc->m, i);
Direction d = Measure_Direction(&fc->m, fc->state);
switch (fc->state) {
case Idle:
switch (d) {
case Down: // finger enter
LED_RED_On();
fc->state = Negative;
fc->fingers = 1;
fc->zeros = 0;
break;
case Up: // environment light increased, ignore
break;
case Zero: // noise or no changes, ignore
break;
}
break;
case Negative:
switch (d) {
case Up: // finger passed
fc->state = Positive;
fc->zeros = 0;
break;
case Down: // already negative, ignore
break;
case Zero: // noise or no changes, ignore
fc->zeros += 1;
if (fc->zeros >= 2 * IDLE_HRESHOLD) {
FingerCounter_Reset(fc, 0);
}
break;
}
break;
case Positive:
switch (d) {
case Down: // one finger enter
fc->state = Negative;
fc->fingers += 1;
fc->zeros = 0;
break;
case Up: // already positive, ignore
break;
case Zero: // noise or no changes
fc->zeros += 1;
if (fc->zeros >= IDLE_HRESHOLD) {
FingerCounter_Reset(fc, fc->fingers);
}
break;
}
break;
}
}
这里我还加上了 LED,开始进入 Negative 状态时打开 LED 表示开始识别,结束一次识别之后关闭 LED。
因为整个程序的状态都在 FingerCounter 里,而且实现非常简单,所有内存使用非常少:
后续优化
- 优化平滑函数,减少噪声影响
- 使用 ringbuffer 保存更多的数据再做平滑
- 优化弱光环境下的效果,动态调节斜率阈值
- 使用 WiFi 把识别到的数据发送到云端
总结
这次活动是我第一次使用 PIC MCU,也是第一次使用光照传感器。Microchip 的 IDE 可以很方便地配置和生成代码,可以把精力放在功能实现上。PIC-IoT WA 作为一块 IoT 开发版,但是这次项目没有使用到它的 WiFI 和加密 IC 功能,比较可惜,后续可以把 WiFI 也结合进去做一个远程的手指识别。