项目介绍
这里是我参加Funpack第四季第一期活动的任务总结报告,我所完成的是任务二,采集滑动条和按键状态,使用BLE连接并调节手机或电脑音量控制,屏幕亮度,上下翻页,要求三个按键分别控制不同的对象。
项目实施步骤
1. 准备软件与工具
- 访问官网,下载必要软件:
- SDK 开发包:包含示例代码和开发所需的库文件。
- 安装下载的软件,并确保电脑能识别连接的开发板。
2. 硬件连接与设备识别
- 将开发板通过 USB 线连接到电脑。
- 打开“设备管理器”,确认开发板(如“J-Link”)已正确识别。如未识别,请检查连接或安装驱动程序。
3. 熟悉示例代码
- 打开 SDK,浏览蓝牙连接和 CapSense 示例代码。
- 在 IDE 中打开示例项目,配置开发板型号,编译并烧录代码,观察运行效果。
4. 修改代码以满足需求
- 根据任务需求,修改蓝牙数据传输和 CapSense 功能的代码。
- 调试代码,优化性能,确保功能正常运行。
硬件介绍
CY8CKIT-149 是一款基于 PSoC™ 4100S Plus 的原型开发套件,适用于评估和开发 PSoC™ 4100S Plus 器件。
- 核心芯片:PSoC™ 4100S Plus,带 Arm Cortex-M0+ 内核,128KB 闪存,16KB SRAM。
- CapSense™:支持自电容和互电容,包含三键按键板和六段滑块板。
- 接口:
- 所有 GPIO 通过扩展头引出。
- EZ-BLE 模块,带蓝色 LED,支持 BLE 开发。
- USB Micro-B 连接器,用于供电和编程。
- 编程与调试:
- 板载 KitProg2 编程器/调试器,支持 USB-UART 和 UART-I2C 桥接。
- 5 引脚编程接头,可用于外部编程。
- 其他特性:
- 4 MHz ECO 晶体和 32.768 kHz WCO 晶体。
- 用户 LED 和按钮,用于开发和调试。
- 复位按钮和电源指示 LED。
软件介绍
该开发板有多种开发方式,arduino,NCS,SEGGER Embedded Studio,我则是使用的第三种。
Nordic 官方提供了多个示例程序,其中与本项目相关的有以下三个:
- BLE_HID_Keyboard
该示例程序使设备通过蓝牙连接到 PC,并模拟成键盘。在此基础上,我们可以修改代码,以发送自定义信息到 PC。 - CE220891_CapSense_with_Breathing_LED
此示例程序专注于板卡上的 CapSense 触摸模块应用,并包含两个主控之间的通信部分。它通过 I2CHW 发送触摸数据,可用于实现触摸模块的功能。 - CE210709_EZ-BLE_Peripheral
该示例程序展示了蓝牙主控如何处理来自另一个主控的消息,并通过蓝牙将数据发送给连接的设备。原例程是将触摸位置发送到官方 app,但我们可以修改其功能。
大概思路是通过综合这三个示例程序,我们可以实现以下功能:
当 CapSense 模块被触发后,PSoC™ 4100S 通过 I2CHW 向蓝牙主控发送触摸数据。蓝牙主控接收到数据后,将其转换为相应的按键功能,并通过蓝牙发送给 PC。这样,CapSense 模块的触摸事件就能在 PC 上以键盘按键的形式呈现。
这种方案充分利用了现有的示例程序,降低了开发难度,同时实现了所需的功能。
case PROCESS_DATA:
/* Process data on all the enabled widgets */
CapSense_ProcessAllWidgets();
/* Controls LEDs Status based on the result of Widget processing. */
LED_Control();
#if !ENABLE_TUNER
/*If tuner is not enabled expose the CapSense slider centroid position and button status to
EZ-BLE Module on CY8CKIT-149 PSoC 4100S Plus Prototyping Kit via I2C interface*/
/*Update the I2C buffer with slider centroid position*/
i2cBuffer[SLIDER_CENTROID_INDEX] = (uint8) CapSense_GetCentroidPos(CapSense_SLD_WDGT_ID);
/*Calculate the button status mask and update the I2C buffer
bit0= BTN0 status, bit1 = BTN1 status, bit2 = BTN2 status*/
for(widgetID = 0; widgetID < TOTAL_CAPSENSE_BUTTONS ; widgetID++)
{
if(CapSense_IsWidgetActive(widgetID))
{
SET_BIT(buttonStatus, widgetID);
}
else
{
CLEAR_BIT(buttonStatus, widgetID);
}
}
i2cBuffer[BUTTON_STATUS_INDEX1] = buttonStatus;
// if(){
//
// }
// temp_i2cBuffer[0]=
#endif
// CyDelay(1500);
SW_Tx_UART_PutString("SLIDER:");
SW_Tx_UART_PutHexInt(i2cBuffer[SLIDER_CENTROID_INDEX]);
SW_Tx_UART_PutCRLF();
SW_Tx_UART_PutString("BUTTON:");
SW_Tx_UART_PutHexInt(i2cBuffer[BUTTON_STATUS_INDEX1]);
SW_Tx_UART_PutCRLF();
/* Set the device state to SENSOR_SCAN */
currentState = SENSOR_SCAN;
break;
上面代码的中间部分,就是关键的4100S主控通过I2CHW发送给蓝牙蓝牙主控触摸数据。可以看到该变量前一个是滑动触摸的位置信息,后一个则是三个按钮被按下的信息。
在BLE_HID_Keyboard的例程,main函数前面都是相关的蓝牙初始化部分,例程帮我们坐好了,不用管它,其次要自己加上对I2CHW的初始化,因为要用到。
在蓝牙事件中,将原来的函数屏蔽了,加上自己的处理触摸数据的部分。
I2CHW_I2CMasterReadBuf(I2C_SLAVE_ADDRESS, i2cBuffer, I2C_BUF_SIZE, I2CHW_I2C_MODE_COMPLETE_XFER);
while (0u == (I2CHW_I2CMasterStatus() & I2CHW_I2C_MSTAT_RD_CMPLT))
{
CyBle_ProcessEvents();
}
通过 I2C 从从设备(触摸模块)读取数据到 i2cBuffer
。使用 I2CHW_I2CMasterReadBuf
发起读取请求,并通过 I2CHW_I2CMasterStatus
检查读取是否完成。在读取完成之前,调用 CyBle_ProcessEvents()
处理 BLE 事件,以确保 BLE 通信不会被阻塞。
sliderValue = i2cBuffer[0];
if (sliderValue != 0x00ff)
{
if (sliderValue >= 0x0043)
{
page_up();
}
else if (sliderValue <= 0x0028)
{
page_down();
}
}
if (prevSliderValue != sliderValue)
{
// SendDataOverCapSenseSliderNotification(sliderValue);
}
prevSliderValue = sliderValue;
从 i2cBuffer
中读取滑块的值(i2cBuffer[0]
)。如果滑块值不是默认值(0x00ff
),根据滑块值的范围执行相应的操作:如果滑块值大于或等于 0x0043
,调用 page_up()
函数,如果滑块值小于或等于 0x0028
,调用 page_down()
函数。如果滑块值发生变化(prevSliderValue != sliderValue
),可以发送滑块通知(注释掉的代码)。更新 prevSliderValue
为当前滑块值。
temp_buttonValue = i2cBuffer[2];
if (temp_buttonValue != 0x0000)
{
if (temp_buttonValue == 0x0001)
{
sound_up();
}
else if (temp_buttonValue == 0x0002)
{
sound_down();
}
else if (temp_buttonValue == 0x0004)
{
light_up();
}
}
从 i2cBuffer
中读取按钮的状态(i2cBuffer[2]
)。如果按钮状态不是默认值(0x0000
),根据按钮值执行相应的操作:如果按钮值为 0x0001
,调用 sound_up()
函数。如果按钮值为 0x0002
,调用 sound_down()
函数。如果按钮值为 0x0004
,调用 light_up()
函数。
void SendKeyboard(uint8 CapsKey, uint8 SimKey)
{
static uint8 keyboard_data[KEYBOARD_DATA_SIZE]={0,0,0,0,0,0,0,0};
CYBLE_API_RESULT_T apiResult;
uint8 i;
if(CapsKey == 1u)
{
keyboard_data[2u] = CAPS_LOCK;
}
keyboard_data[3u] = SimKey;
if(CyBle_GattGetBusyStatus() == CYBLE_STACK_STATE_FREE)
{
apiResult = CyBle_HidssGetCharacteristicValue(CYBLE_HUMAN_INTERFACE_DEVICE_SERVICE_INDEX,
CYBLE_HIDS_PROTOCOL_MODE, sizeof(protocol), &protocol);
if(apiResult == CYBLE_ERROR_OK)
{
DBG_PRINTF("HID notification: ");
for(i = 0; i < KEYBOARD_DATA_SIZE; i++)
{
DBG_PRINTF("%2.2x,", keyboard_data[i]);
}
DBG_PRINTF("\r\n");
if(protocol == CYBLE_HIDS_PROTOCOL_MODE_BOOT)
{
apiResult = CyBle_HidssSendNotification(cyBle_connHandle, CYBLE_HUMAN_INTERFACE_DEVICE_SERVICE_INDEX,
CYBLE_HIDS_BOOT_KYBRD_IN_REP, KEYBOARD_DATA_SIZE, keyboard_data);
}
else
{
apiResult = CyBle_HidssSendNotification(cyBle_connHandle, CYBLE_HUMAN_INTERFACE_DEVICE_SERVICE_INDEX,
CYBLE_HUMAN_INTERFACE_DEVICE_REPORT_IN, KEYBOARD_DATA_SIZE, keyboard_data);
}
if(apiResult == CYBLE_ERROR_OK)
{
keyboard_data[2u] = 0u; /* Set up keyboard data*/
keyboard_data[3u] = 0u; /* Set up keyboard data*/
if(protocol == CYBLE_HIDS_PROTOCOL_MODE_BOOT)
{
apiResult = CyBle_HidssSendNotification(cyBle_connHandle, CYBLE_HUMAN_INTERFACE_DEVICE_SERVICE_INDEX,
CYBLE_HIDS_BOOT_KYBRD_IN_REP, KEYBOARD_DATA_SIZE, keyboard_data);
}
else
{
apiResult = CyBle_HidssSendNotification(cyBle_connHandle, CYBLE_HUMAN_INTERFACE_DEVICE_SERVICE_INDEX,
CYBLE_HUMAN_INTERFACE_DEVICE_REPORT_IN, KEYBOARD_DATA_SIZE, keyboard_data);
}
}
if(apiResult != CYBLE_ERROR_OK)
{
DBG_PRINTF("HID notification API Error: %x \r\n", apiResult);
keyboardSimulation = DISABLED;
}
}
}
}
void page_up(){
SendKeyboard(0u, PAGE_UP);
}
void page_down(){
SendKeyboard(0u, PAGE_DOWN);
}
void sound_up(){
SendKeyboard(0u, SOUND_HIGH);
}
void sound_down(){
SendKeyboard(0u, SOUND_LOW);
}
void light_up(){
SendKeyboard(0u, LIGHT_HIGH);
}
void light_down(){
SendKeyboard(0u, LIGHT_LOW);
}
SendKeyboard借鉴一个大佬的代码,在 HandleCapSense
函数中,根据触摸模块的输入(滑块和按钮状态),调用相应的辅助函数(如 page_up
、sound_up
等),从而实现触摸事件到按键事件的映射。这样,触摸模块的输入就可以通过蓝牙发送到连接的设备,模拟键盘操作。
滑块与按键触发,再到蓝牙主控发送相应键的大概流程
i2cBuffer[SLIDER_CENTROID_INDEX] = (uint8) CapSense_GetCentroidPos(CapSense_SLD_WDGT_ID);
/*Calculate the button status mask and update the I2C buffer
bit0= BTN0 status, bit1 = BTN1 status, bit2 = BTN2 status*/
for(widgetID = 0; widgetID < TOTAL_CAPSENSE_BUTTONS ; widgetID++)
{
if(CapSense_IsWidgetActive(widgetID))
{
SET_BIT(buttonStatus, widgetID);
}
else
{
CLEAR_BIT(buttonStatus, widgetID);
}
}
i2cBuffer[BUTTON_STATUS_INDEX1] = buttonStatus;
此处是滑块,按键被触发后,主控获取到滑块的当前位置,按键的按下情况,将数据存到i2cbuffer中
/* Read entire data buffer from the slave device */
I2CHW_I2CMasterReadBuf(I2C_SLAVE_ADDRESS, i2cBuffer, I2C_BUF_SIZE,
I2CHW_I2C_MODE_COMPLETE_XFER);
while (0u == (I2CHW_I2CMasterStatus() & I2CHW_I2C_MSTAT_RD_CMPLT))
{
CyBle_ProcessEvents();
}
// if(startNotification & SLIDER_CCCD_NTF_BIT_MASK)
// {
sliderValue = i2cBuffer[0];
if(sliderValue != 0x00ff){
if(sliderValue >= 0x0043){
page_up();
}else if(sliderValue <=0x0028){
page_down();
}
}
if(prevSliderVaule != sliderValue)
{
// SendDataOverCapSenseSliderNotification(sliderValue);
}
prevSliderVaule = sliderValue;
// }
temp_buttonValue = i2cBuffer[2];
if(temp_buttonValue != 0x0000){
if(temp_buttonValue == 0x0001){
sound_up();
}else if(temp_buttonValue == 0x0002){
sound_down();
}else if(temp_buttonValue == 0x0004){
//light_up();
}
}
此处是蓝牙主控中,通过i2chw获取到另一个主控发来的滑块及按键数据,提取后,进行分析,并进行相应的键位发送
效果展示
总结
在本次活动中,学习了如何使设备模拟蓝牙键盘连接PC。在过程中遇到的问题,通过百度搜索都能找到适合的答案,使自我得到了提升,感谢硬禾学堂平台。