1.任务要求
诸如台式机和笔记本电脑,键盘上常常集成有音量调节或屏幕亮度调整的便捷控制键。想象一下,若我们采用一种独特的板卡,它有着一个滑动控制器,当轻轻用手指在滑动控制器上移动时,无论是电脑的音量大小、屏幕亮度,还是视频播放的进度条,都能随着指尖滑动而流畅变化。这样的设计不仅让操作更加直观和便捷,同时也为日常的数字生活增添了一份优雅与乐趣。
2.项目功能
为实现上述想法,RT-Thread 联合英飞凌(Infineon)推出了一款集成32位双核CPU( ARM Cortex-M4 和 ARM Cortex-M0)子系统的开发板,它自带了五段式的电容触摸滑条,可以帮助我们将各种创意想法变为现实。在英飞凌为自家产品设计的开发工具ModusToolBox的帮助下,我成功实现了通过USB通信控制电脑音量的功能,并且拥有相对值模式和绝对值模式两种模式,在main.c文件中更改不同的宏定义即可切换。
3.设计思路
3.1寻找例程
ModusToolBox中为每一块开发板提供了丰富的例程供开发者参考,在直播教学和一篇帖子([PSoC™] Make a Capsense Slider demo in ModusToolBox with Infineon PSoC 62 with CAPSENSE evaluation kit)的帮助下,我首先找到了通过滑条控制板上LED亮度的例程,并进行了引脚配置,根据开发板的实际情况成功跑通了例程。
接下来,我又找到了符合我需求的USB鼠标的例程,按照同样的方法配置好后实现了鼠标指针的左右移动,但这是自动进行的,无法受到我们的控制。
3.2移植代码
看起来这两个例程分别实现了两个需求——滑块控制和USB HID设备,那么我需要将其中一个例程移植到另一个例程中,因为USB HID设计到通信协议问题,我决定以USB鼠标的例程为主,将滑块控制的例程移植过去。但是,我需要学习HID报告描述符有关内容以更改鼠标的报告描述符,以下两篇文章对这个过程有帮助:
4.实现过程
4.1程序流程图
4.2代码解析
4.2.1 重要参数——HID报告描述符
/*********************************************************************
* usb_HIDReport
* This report is generated according to HID spec and
* HID Usage Tables specifications.
**********************************************************************/
const U8 usb_HIDReport[] = {
0x05, 0x0c, // USAGE_PAGE (Consumer Devices)
0x09, 0x01, // USAGE (Consumer Control)
0xa1, 0x01, // COLLECTION (Application)
0x79, 0x00, // STRING_MINIMUM (0)
0x89, 0x01, // STRING_MAXIMUM (1)
0x09, 0xe9, // USAGE (Volume Up)
0x09, 0xea, // USAGE (Volume Down)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x81, 0x01, // INPUT (Cnst,Ary,Abs)
0xc0 // END_COLLECTION
};
注意:
- 定义了一对按钮——音量+和音量-,在改变音量时必须先按下后松开,否则音量会直接增加/减小至最大值/最小值
- 在定义了两个按钮之后(占两位)后,还需要添加6位的常量,使报告描述符凑够八位,即一字节,否则在设备连接之后设备会警告HID设备报告中字节未对齐
4.2.2相对值模式
#ifdef Relative
cy_stc_capsense_touch_t *slider_touch_info;
uint16_t slider_pos;
uint8_t slider_touch_status;
static uint16_t slider_pos_prev;
/* Get slider status */
slider_touch_info = Cy_CapSense_GetTouchInfo(
CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);
slider_touch_status = slider_touch_info->numPosition;
slider_pos = slider_touch_info->ptrPosition->x;
/* Detect the new touch on slider */
if ((0 != slider_touch_status) &&
(slider_pos != slider_pos_prev))
{
if (slider_pos < slider_pos_prev)
{
button = 0x01;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
button = 0x00;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
}
if (slider_pos > slider_pos_prev)
{
button = 0x02;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
button = 0x00;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
}
}
/* Update previous touch status */
slider_pos_prev = slider_pos;
#endif
可以看到,如果本次触摸位置的值小于上一次的,即往下滑动,那么就会按下第一个按钮——音量+并松开,反之则会按下第二个按钮——音量-,从而实现音量的调节。但是,这种音量的调节只能相对于现在的音量进行调节,而且调节的多少与滑动的距离没有直接关系,因为检测位置的间隔是固定的,所以调整的多少与滑动时间成正比。也就是说如果滑动的很快,那么既是从一端滑到另一端也不会改变很多音量,反之,如果滑动较慢,则可以在滑动相同距离的前提下调整更大幅度的音量。
4.2.3绝对值模式
#ifdef Absolute
cy_stc_capsense_touch_t *slider_touch_info;
uint16_t slider_pos;
uint8_t slider_touch_status;
static uint16_t slider_pos_prev;
static uint32_t volume;
static uint32_t volume_prev;
static int Flag = 1;
/* Get slider status */
slider_touch_info = Cy_CapSense_GetTouchInfo(
CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);
slider_touch_status = slider_touch_info->numPosition;
slider_pos = slider_touch_info->ptrPosition->x;
volume =((slider_pos * 100)
/ cy_capsense_context.ptrWdConfig[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].xResolution) / 2;
if(Flag)
{
for(int i = 0;i<70;i++)
{
button = 0x02;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
button = 0x00;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
}
Flag = 0;
}
/* Detect the new touch on slider */
if ((0 != slider_touch_status) &&
(slider_pos != slider_pos_prev))
{
volume =((slider_pos * 100)
/ cy_capsense_context.ptrWdConfig[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].xResolution) / 2;
while (volume_prev > volume)
{
button = 0x01;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
button = 0x00;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
volume_prev--;
cyhal_system_delay_ms(10);
}
while (volume_prev < volume)
{
button = 0x02;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
button = 0x00;
USBD_HID_Write(usb_hidContext, &button, 1U , 0);
cyhal_system_delay_ms(10);
volume_prev++;
cyhal_system_delay_ms(10);
}
}
/* Update previous touch status */
slider_pos_prev = slider_pos;
volume_prev = volume;
#endif
绝对值模式的整体流程与相对值模式相似,只是把滑动的位置转化为了目标音量大小,如果目前音量始终没有到达目标音量则会持续调整,直到与目标音量相等。这样的效果就是滑条好似成为了一个音量条,你的手指滑到上方四分之一处音量就会调到75%,可谓是量随指动,同时还支持点按,轻触滑条一个位置就可以调整到那个音量。这一切看起来比相对值模式好的多,但是为什么还要设置两个模式呢?自然是因为本模式有如下问题:
- HID设备无法读取电脑/手机目前音量,所以连接后必须将音量清零,从而保证电脑音量与程序中的变量的值相等
- 绝对值模式由于每次检测和音量调整之间有间隔,可能会出现电脑音量与程序中存储的目前音量产生差值,出现明明滑到最上方了,本应调到100%却只有85%,其实程序里的音量已经是100了,但是之前可能电脑音量还没调到目标音量程序中的变量就被覆盖了。
- 绝对值模式效果与设备的系统也有关系,比如WIN11中按一次键盘上的音量+/-调整2%的音量,但是手机上可能是一次调整10%,所以程序中储存的音量的值需要做出相应的调整。
- 绝对值模式调整有一些波动,比如我滑到75%,现实是可能增大音量过度达到88%又下降到75%,带来使用的不适。
总上所述,理论上绝对值模式是更符合使用逻辑的,但是实现起来困难比较大,而且效果不尽如人意,所以我做了两种模式,可供选择与调试,也算是提供一种优化方向吧。
5.遇到的主要难题
5.1HID报告描述符问题
在我上方提到的网站(HID音量控制报告描述符)提供的HID报告描述符中,只有一个按钮,但是可以让它的状态变为1或-1,以模拟音量的加与减,但是在实际调试过程中让它的值为-1并不能减小音量。
解决方法:再添加一个按钮,让两个按钮分别控制音量的加减,按钮状态为1是按下,为0是松开。
6.未来的计划建议
我的绝对值模式仍存在波动,偏差等诸多问题,希望以后能够优化一下绝对值模式,毕竟这种模式比相对值模式更符合操作逻辑。
7.库文件网盘链接
如果使用ModusToolBox打开项目,应该会自动添加mtb_shared文件夹,如果没有自动添加或者使用方式查看代码,请通过以下链接下载库文件(文件过大,无法直接上传)。
链接:https://pan.baidu.com/s/1zEF1E4_sMdmIBrvpdOj8yg?pwd=rnb7
提取码:rnb7