
基于Flex2.2和立创开发板的数据手套
简介
基于地文星CW32F030C8T6开发板的一个小项目,用5路ADC读取弯曲传感器的电压,输出给上位机,在上位机中换算成手指角度,实现手部动捕功能。
简介:基于地文星CW32F030C8T6开发板的一个小项目,用5路ADC读取弯曲传感器的电压,输出给上位机,在上位机中换算成手指角度,实现手部动捕功能。开源协议
:GPL 3.0
(未经作者授权,禁止转载)描述
项目简介
基于地文星CW32F030C8T6开发板的一个小项目,用5路ADC读取弯曲传感器FLEX2.2的电压,输出给上位机,在上位机中换算成手指角度,实现手部动捕功能。
项目功能
此处可填写项目的相关功能及应用场景,示例:
本设计是基于地文星CW32F030C8T6开发板设计的VR数据手套;设置有5个弯曲传感器,用来获取手指角度的绝对值,通过BT01蓝牙模块将数据传输给Untiy上位机,另外拓展板上设有锂电池充电电路,可以通过锂电池供电;
项目参数
此处可填写项目的相关功能参数介绍,示例:
- 本设计采用CW32F030C8T6开发板;
- 本设计采用Flex2.2弯曲传感器,并用LM358运算放大器来降低弯曲传感器的阻抗误差;
- 选用BT01蓝牙模块;
- 采用ip5306电源管理芯片,稳定输出5V电压,支持边充边放,4个LED灯显示剩余电量;
原理解析(硬件说明)
硬件分为两个部分,弯曲传感器电路和主控板电路(拓展板)。
弯曲传感器电路
首先弯曲传感器是一种低成本、使用简单的传感器,可用于测量偏转或者弯曲量,常见有两种尺寸,2.2英寸和4.5英寸,本次使用的是2.2的版本。要注意传感器只能往印有字符的一面弯曲,反方向弯曲有可能会损坏传感器。
传感器的外围电路主要是参考其数据手册,如下图所示。
可以看出,弯曲传感器基本可以等效地看作是一个可变的电阻,数据手册中写了推荐使用LM358或者LM324来作为阻抗缓冲器,因为运算放大器的低偏置电流可以减少由于柔性传感器作为分压器的源阻抗而导致的误差(百度翻译的,我也不懂,数电模电基础很烂)
这里用一个20KΩ的电阻来分压,注意电阻最好用高精度的,我自己用的是0.1%精度的。弯曲传感器在常态下的阻值一般是25k左右,在弯曲到90度的时候可达100kΩ,其阻值变化大致是线性的,需要注意的是传感器的制造误差是比较大的,在使用前可以用万用表来测试其实际的阻值。
然后就是接口,这里用的是PH2.0-3P的接口,3个引脚分别是3V3 GND ADC。根据数据手册原理图的说法,我应该获取的是弯曲传感器那一段的电压,但实际上获得的是20kΩ那一段的电压,有点搞不懂这个运算放大器电路了。不过也一样能用就是了。
主控电路
主控电路其实是比较简单的,IP5306的外围电路完全是照搬数据手册的原理图。除此之外,原本还有1个振动电机的接口电路,以及1个IIC屏幕的接口电路。但因为10月份一直忙着秋招找工作(甚至至今无Offer,只有一个华为的池子在泡),就没什么时间弄了。
软件代码
代码其实是用立创的代码案例 ADC采集实验来改的。虽然我在武汉芯源半导体的官网下载CW32F030的固件库和样例代码,但感觉对学生不是很友好,看着有点费力。
比如我要实现5路ADC采集,于是就想配置一个ADC扫描模式+DMA,翻阅了手册才知道这个MCU的扫描模式最多只支持4个通道,如果要实现更多通道的扫描模式,那就得重复地配置和初始化ADC序列结构体才行,这就有点麻烦。而且我一开始在立创的源码基础上,想要把芯源半导体的Examples/ADC/adc_sqr_irq_sw中的断续扫描模式的代码移植过来,但莫名其妙的的事情发生了。
ADC配置完全照搬芯源半导体,但到了启动ADC的那一步会直接卡死,ADC_SoftwareStartConvCmd(ENABLE)后面的代码都不执行了,debug也找不出原因,直接摆大烂。
干脆不配扫描模式了,还是用单通道模式,只是每完成一次ADC通道的转换后,就更换一个通道,这样固然效率要低,但是能跑通代码就行,已经彻底疯狂。
下面是ADC初始化的方法,其实就是传入一个参数,让它可以初始化不同的通道。
/**********************************************************
* 函 数 名 称:ADC_Config
* 函 数 功 能:初始化ADC配置
* 传 入 参 数:ADC通道(这个是改动)
* 函 数 返 回:无
* 作 者:LCKFB 还有我(我改了)
* 备 注:
**********************************************************/
void ADC_Config(uint32_t Ch)
{
/* GPIO配置 */
__RCC_GPIOA_CLK_ENABLE(); // 开启GPIOA时钟
__RCC_ADC_CLK_ENABLE(); // 开启ADC时钟
PA00_ANALOG_ENABLE(); // PA00设定为模拟输入
PA01_ANALOG_ENABLE(); // PA01设定为模拟输入
PA02_ANALOG_ENABLE(); // PA02设定为模拟输入
PA03_ANALOG_ENABLE(); // PA03设定为模拟输入
PA04_ANALOG_ENABLE(); // PA04设定为模拟输入
/* ADC配置 */
ADC_InitTypeDef ADC_InitStructure; // ADC初始化结构体
ADC_WdtTypeDef ADC_WdtStructure; // ADC看门狗结构体
ADC_SingleChTypeDef ADC_SingleChStructure; // ADC单通道转换结构体
// 配置ADC初始化结构体
ADC_InitStructure.ADC_OpMode = ADC_SingleChOneMode; //单通道单次转换模式
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div4; // 时钟频率 = PCLK / 4 = 64MHz / 4 = 16MHz
ADC_InitStructure.ADC_SampleTime = ADC_SampTime5Clk; //5个ADC时钟周期
ADC_InitStructure.ADC_VrefSel = ADC_Vref_VDDA; //VDDA参考电压
ADC_InitStructure.ADC_InBufEn = ADC_BufDisable; //关闭跟随器
ADC_InitStructure.ADC_TsEn = ADC_TsDisable; //关闭内置温度传感器
ADC_InitStructure.ADC_DMAEn = ADC_DmaDisable; //不触发DMA
ADC_InitStructure.ADC_Align = ADC_AlignRight; //ADC转换结果右对齐
ADC_InitStructure.ADC_AccEn = ADC_AccDisable; //转换结果累加不使能
//ADC模拟看门狗通道初始化
ADC_WdtInit(&ADC_WdtStructure);
//配置单通道转换模式
ADC_SingleChStructure.ADC_DiscardEn = ADC_DiscardNull; // 单通道ADC转换结果溢出保存
ADC_SingleChStructure.ADC_Chmux = Ch; // 选择ADC转换通道,ADC_ExInputCH0
ADC_SingleChStructure.ADC_InitStruct = ADC_InitStructure; // ADC初始化结构体
ADC_SingleChStructure.ADC_WdtStruct = ADC_WdtStructure; // ADC看门狗结构体
ADC_SingleChOneModeCfg(&ADC_SingleChStructure); // 初始化配置
ADC_Enable(); //ADC使能
}
主循环里面一直读取5个ADC通道即可
int value[5];//声明一个长度为5的数组
int32_t main(void)
{
board_init(); // 开发板初始化
uart1_init(115200); // 串口1波特率115200
ADC_Config(ADC_ExInputCH0); // ADC初始化
while(1)
{
ADC_Config(ADC_ExInputCH0);
value[0]=Get_Adc_Average(5);//每个ADC通道都是读取5次然后取平均值
ADC_Config(ADC_ExInputCH1);
value[1]=Get_Adc_Average(5);
ADC_Config(ADC_ExInputCH2);
value[2]=Get_Adc_Average(5);
ADC_Config(ADC_ExInputCH3);
value[3]=Get_Adc_Average(5);
ADC_Config(ADC_ExInputCH4);
value[4]=Get_Adc_Average(5);
printf("value,%d,%d,%d,%d,%d\r\n",value[0],value[1],value[2],value[3],value[4]);
//delay_ms(30);
}
}
至于蓝牙模块,基本不用管,本身就是可以直接用UART初始化配置的代码直接驱动的,开发板连蓝牙模块的代码和连CH340串口通信模块的代码,可以说没有什么区别。
Untiy上是用C#写了一个类似串口助手的脚本来接收信息,一部分代码如图,其实这种比较常见,CSDN之类的地方也能找到。
至于控制手指运动的脚本,则是MoveTest,我在这里面写了几个方法,一是GetADCValue,这是用来把蓝牙传过来的字符串重新转回整数。C#中要进行字符串分割还是比较方便的,有专门的函数Split(),之前串口打印的信息特意用逗号隔开了,这里就可以直接用逗号作为分割
private void GetADCValue()
{
if (PortControl.DmpData.Count != 0)
{
string a = PortControl.DmpData.Dequeue();
string[] b = a.Split(",");
if (b[0] == "value")
{
for(int i=0;i<5;++i)
{
ADCvalue[i] = Convert.ToInt32(b[i + 1]);
}
}
}
}
这个方法是用来计算每个手指的角度,因为之前提到了,每个弯曲传感器有不同的制造误差,所以初始的ADC读数都是不同,这里只是简单处理了一下,如果需要更高精度,需要测量一下弯曲传感器在不同角度下的阻值,然后用最小二乘法之类的数学方法来近似拟合比较好。
private void CalculateAngle()
{
fingel[0] = (float)90*(1860 - ADCvalue[0])/700.0f;
fingel[1] = (float)90 * (1830 - ADCvalue[1]) / 580.0f;
fingel[2] = (float)90 * (1830 - ADCvalue[2]) / 640.0f;
fingel[3] = (float)90 * (1825 - ADCvalue[3]) / 525.0f;
fingel[4] = (float)90 * (1750 - ADCvalue[4]) / 450.0f;
Debug.Log(fingel[0] + " " + fingel[1] + " " + fingel[2] + " " + fingel[3] + " " + fingel[4]);
}
最后就是更新手指的角度信息,在Update()里面一直调用这三个方法就行了。
private void Finger()
{
pinky1.localEulerAngles = new Vector3(pinky1.localEulerAngles.x,pinky1.localEulerAngles.y, -fingel[0]);
pinky2.localEulerAngles = new Vector3(pinky2.localEulerAngles.x, pinky2.localEulerAngles.y, -fingel[0]);
ring1.localEulerAngles = new Vector3(ring1.localEulerAngles.x, ring1.localEulerAngles.y, -fingel[1]);
ring2.localEulerAngles = new Vector3(ring2.localEulerAngles.x, ring2.localEulerAngles.y, -fingel[1]);
middle1.localEulerAngles = new Vector3(middle1.localEulerAngles.x, middle1.localEulerAngles.y, -fingel[2]);
middle2.localEulerAngles = new Vector3(middle2.localEulerAngles.x, middle2.localEulerAngles.y, -fingel[2]);
index1.localEulerAngles = new Vector3(index1.localEulerAngles.x, index1.localEulerAngles.y, -fingel[3]);
index2.localEulerAngles = new Vector3(index2.localEulerAngles.x, index2.localEulerAngles.y, -fingel[3]);
thumb1.localEulerAngles = new Vector3(thumb1.localEulerAngles.x, thumb1.localEulerAngles.y, -fingel[4]);
thumb2.localEulerAngles = new Vector3(thumb2.localEulerAngles.x, thumb2.localEulerAngles.y, -fingel[4]);
}
注意事项
1.ip5306在低负载的情况下会自动关机,不输出电流,如果有这种情况可以尝试换成IP5306-CK,这个版本默认一直开机。
2.ip5306的GND焊点在芯片正下方,只能用加热台或热风枪来焊接,GND如果不焊上的话是没办法输出的。
3.蓝牙和PC配对的方法: 更蓝牙设置->COM端口->添加->传出->浏览,然后选中你的蓝牙模块,就可以配对上了,配对上之后,自动生成一个虚拟的COM端口,我这里是COM17
点击浏览后选中蓝牙模块就可以了。
实物图
虽然之前在立创里面做了3D外壳,但最后没用上。
之前其实做了一个带电池的版本,如下图所示。但最后发现排母不幸焊歪了,导致开发板很难插进去,直接整个板子废掉,又觉得电池没什么空间放了,因为没有外壳,测试的时候干脆直接用USB供电。
不足之处
振动电机的话是用PWM输出来控制电机振动,在untiy中如果有交互的话,可以通过蓝牙回传一个信息,用触发串口中断的方式来执行PWM输出的任务,引发电机振动,实现一个简单的力反馈效果,但前面已经说了,我不过是一个找不到工作的可悲研究生罢了,没啥时间和心思搞这个了。如果有谁有兴趣的话可以自己试一下。
因为附件大小不能超过50M,所以上位机的程序传不上来,而且上位机里面的手模型是在unity商店里面买的,擅自传播可能也不太好,于是我只上传了两个unity的脚本,有兴趣的话可以参考一下。
设计图

BOM


评论