项目描述
本项目基于PSoC 4100S Plus 开发板进行设计。开发板带有蓝牙模块及触摸按键。本项目的设计思路就是,当电脑连接到开发板上的蓝牙后,通过触控按键控制蓝牙发送控制信号给电脑,实现翻页的功能。
硬件说明
PSoC 4100S Plus 开发板可以分为四大部分:1、调试部分;2、单片机部分;3、蓝牙部分;4、触摸按键部分;
单片机为CY8C4147AZI-S475,具有一个 Arm Cortex-M0+ 内核和高达 128KB 闪存、16KB SRAM、9 个可编程模拟块、13 个可编程数字块以及 54 个通用 I/O,包括 24 个智能 I/O。 它还采用英飞凌第四代低功耗 CAPSENSE™ 解决方案,超可靠的低功耗电容式感应解决方案,可在嘈杂环境和液体中 "正常工作"。这是它有别于其他单片机的地方,开发板上也专门给他设计了两块触摸板。本工程也将使用该触摸板实现控制功能。
蓝牙为CYBLE-022001-00,它可以独立的编程调试。和CY8C4147AZI-S475单片机一样,它也支持英飞凌的CAPSENSE™技术。但是在本开发板中,并没有将它和板子上电触摸模块连接在一起,所以无法测试该功能。蓝牙模块内核为cortex-m0,系统时钟最大为48MHz,flash 128KB,sram 16KB。由下图可知,蓝牙模组有21个引脚,可用的GPIO有16个,可以在蓝牙功能外,做一些基本的控制功能。
这款开发板的独特之处在于并非一个单一的主控,单片机及蓝牙都可以独立开发,并通过板载的调试器进行烧录与调试。通过板子上的sw4波动开关,去控制调试器的调试目标,当开关如同下图向上波动,调试的对象为蓝牙模组。
单片机和蓝牙模组之间可以通过两种通信接口进行连接:iic和串口。由于串口作为主控间的通信更为方便,所以本项目中采用了串口,作为单片机向蓝牙模组发送指令的通信方式。
但是需要注意的是,原理图中标注了R17和R18是没有的,也就是说单片机和蓝牙模组的串口是断开的,所以需要通过焊接将它们连通。可以采用低阻值的电阻,也可以直接用焊锡将它们连接。
开发环境
本项目对单片机和蓝牙都进行了开发,它们使用的IDE皆是PSoC Creator 4.4。
例子可以从File->Code Example中获取。
蓝牙模组的例子可以在PRoC BLE中获取。
单片机的例子可以在PSoC 4100S Plus中获取。
本工程使用的蓝牙工程为BLE_HID_Keyboard
单片机使用的工程为
代码及工程说明
单片机
打开例程后,打开TopDesign.cysch文件,在右边找到Component Catalog->Cypress->communications->UART,将串口模块拖入文件中。
打开Design Wide Resources文件将串口的rx和tx进行如下配置
上述UART模块可以双击打开,但是并不需要对里面的参数进行修改。接下来可以直接进行编译。下方从左到右依次是编译、烧录、调试。
编译完成后,将开发板接上电脑,并将SW4开关向三个独立触摸按键的方向拨动。点击烧录按键,就可以将代码烧录到单片机。如果出现下方的情况,就说明sw4方向不对,反向拨动。
如果是向下放这样显示就说明单片机已经接上电脑。
接下来说一下代码上的改动。原始工程已经实现了触摸按键的检测,需要新增的功能就是在按键按下时,用模组向蓝牙模组发送信息。
下方代码实现了当3个独立按键中任何一个被按下后,都会用串口发送一个字节不同指令。滑动条的上下滑动,也会被转成上下滑动界面的指令,通过串口发送。
void uart_send_handle(void)
{
static uint8_t a = 0, b = 0, c = 0;
uint32 centroid;
centroid = CapSense_GetCentroidPos(CapSense_SLD_WDGT_ID);
static uint32_t last_centroid = 0xffffffff;
if (CapSense_IsWidgetActive(CapSense_BTN0_WDGT_ID) && (a == 0))
{
a = 1;
UART_BLE_UartPutChar('a');
}
else if (CapSense_IsWidgetActive(CapSense_BTN0_WDGT_ID) == 0)
{
a = 0;
}
if (CapSense_IsWidgetActive(CapSense_BTN1_WDGT_ID) && (b == 0))
{
b = 1;
UART_BLE_UartPutChar('b');
}
else if (CapSense_IsWidgetActive(CapSense_BTN1_WDGT_ID) == 0)
{
b = 0;
}
if (CapSense_IsWidgetActive(CapSense_BTN2_WDGT_ID) && (c == 0))
{
c = 1;
UART_BLE_UartPutChar('c');
}
else if (CapSense_IsWidgetActive(CapSense_BTN2_WDGT_ID) == 0)
{
c = 0;
}
if (CapSense_IsWidgetActive(CapSense_SLD_WDGT_ID))
{
if (last_centroid != 0xffffffff)
{
uint32_t diff;
if (last_centroid > centroid)
{
diff = last_centroid - centroid;
if (diff > STEP_SIZE)
{
last_centroid = centroid;
UART_BLE_UartPutChar('a');
}
}
else
{
diff = centroid - last_centroid;
if (diff > STEP_SIZE)
{
last_centroid = centroid;
UART_BLE_UartPutChar('c');
}
}
}
else
{
last_centroid = centroid;
}
}
else
{
last_centroid = 0xffffffff;
}
}
下方是上述代码流程图,说明按键只有在刚按下时,会发送指令,如果一直长按是不会连续发送指令,只有松开再按下才会重新发送。得益于触摸按键的准确行,函数内部并没有对按键信号做滤波处理,实际的效果依然很好。
串口的初始化函数也需要在while前调用
UART_BLE_Start();
下方是本工程的main函数内容
int main()
{
#if !ENABLE_TUNER
/*Used as loop counter and widget ID*/
uint8 widgetID = 0;
/*Contains the buttons status, one bit per button
bit0= BTN0 status, bit1 = BTN1 status, bit2 = BTN2 status*/
uint8 buttonStatus = 0;
#endif
/* Variable to hold the current device state
* State machine starts with Sensor_Scan state after power-up
*/
DEVICE_STATE currentState = SENSOR_SCAN;
/* Enable global interrupts. */
CyGlobalIntEnable;
/* Start EZI2C block */
EZI2C_Start();
/* Start CapSense block */
CapSense_Start();
/* Start the TCPWM components, TCPWM1 generates PWM signal of frequency of 100 Hz
TCPWM2 generates PWM signal of frequency of 101 Hz*/
TCPWM_1_Start();
TCPWM_2_Start();
/* Start the SmartIO component, the two PWM signals are XORed to get the
LED breathing effect of 1 Hz*/
SmartIO_Start();
#if ENABLE_TUNER
/* Set up I2C communication data buffer with CapSense data structure
to be exposed to I2C master on a primary slave address request
*/
EZI2C_EzI2CSetBuffer1(sizeof(CapSense_dsRam),\
sizeof(CapSense_dsRam),(uint8 *)&CapSense_dsRam);
#else
/*Set up communication data buffer with CapSense slider centroid
position and button status to be exposed to EZ-BLE Module on CY8CKIT-149 PSoC 4100S Plus Prototyping Kit*/
EZI2C_EzI2CSetBuffer1(sizeof(i2cBuffer), sizeof(i2cBuffer),i2cBuffer);
#endif
UART_BLE_Start();
for(;;)
{
/* Switch between SENSOR_SCAN->WAIT_FOR_SCAN_COMPLETE->PROCESS_DATA states */
switch(currentState)
{
case SENSOR_SCAN:
/* Initiate new scan only if the CapSense block is idle */
if(CapSense_NOT_BUSY == CapSense_IsBusy())
{
#if ENABLE_TUNER
/* Update CapSense parameters set via CapSense tuner before the
beginning of CapSense scan
*/
CapSense_RunTuner();
#endif
/* Scan widget configured by CSDSetupWidget API */
CapSense_ScanAllWidgets();
/* Set next state to WAIT_FOR_SCAN_COMPLETE */
currentState = WAIT_FOR_SCAN_COMPLETE;
}
break;
case WAIT_FOR_SCAN_COMPLETE:
/* Put the device to CPU Sleep until CapSense scanning is complete*/
if(CapSense_NOT_BUSY != CapSense_IsBusy())
{
CySysPmSleep();
}
/* If CapSense scanning is complete, process the CapSense data */
else
{
currentState = PROCESS_DATA;
}
break;
case PROCESS_DATA:
/* Process data on all the enabled widgets */
CapSense_ProcessAllWidgets();
/* Controls LEDs Status based on the result of Widget processing. */
LED_Control();
uart_send_handle();
#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;
#endif
/* Set the device state to SENSOR_SCAN */
currentState = SENSOR_SCAN;
break;
/*******************************************************************
* Unknown power mode state. Unexpected situation.
******************************************************************/
default:
break;
}
}
}
蓝牙模块
打开蓝牙hid工程,软件给出的工程是别的模组,首先需要进行模组的切换。右键点击项目名,选择Device selector.
选择列表第一个。
选择好后,就可以正常编译烧录。同上面一样打开TopDesign.cysch文件。选择Digtital Output Pin 、LED、Resistor。
双击gpio模组符号,将内部参数像如下更改,然后将3个原件连接在一起。
打开Design Wide Resources文件,将GPIO配置为P1_6,这是板子上led接到的引脚。这个led将会作为蓝牙是否连接上电脑的指示。
由于例程会发送电量信息给电脑,电脑可能会提示低电量告警,可以通过将BAS_MEASURE_ENABLE和BAS_SIMULATE_ENABLE配置为0关闭。
下代码为蓝牙模组接收单片机的指令,如果没有指令,该函数返回0。
uint8_t uart_data_read(uint8_t *data)
{
*data = UART_DEB_UartGetChar();
return *data;
}
例程中的SimulateKeyboard默认周期发送字符到电脑,需要将其逻辑改成当串口接收到指令后,蓝牙才向电脑发送有效指令。
void SimulateKeyboard(void)
{
extern uint8_t cmd;
uint8 keyboard_data[KEYBOARD_DATA_SIZE]={0,0,0,0,0,0,0,0};
CYBLE_API_RESULT_T apiResult;
static uint32 keyboardTimer = KEYBOARD_TIMEOUT;
static uint8 simKey;
static uint8 capsLockPress = 0u;
uint8 i;
/* Scan SW2 key each connection interval */
if(0u == SW2_Read())
{
if(capsLockPress < KEYBOARD_JITTER_SIZE)
{
capsLockPress++;
}
else if(capsLockPress == KEYBOARD_JITTER_SIZE)
{
keyboard_data[2u] = CAPS_LOCK; /* Set up keyboard data */
keyboardTimer = 1u; /* Clear Simulation timer to send data */
capsLockPress++;
}
else /* Ignore long key pressing */
{
}
}
else
{
capsLockPress = 0u;
}
if((CyBle_GattGetBusyStatus() == CYBLE_STACK_STATE_FREE))
{
keyboardTimer = KEYBOARD_TIMEOUT;
uart_data_read(&cmd);
if(cmd != 0)
{
if(cmd == 'a')
{
keyboard_data[2] =75;
}
else if(cmd == 'c')
{
keyboard_data[2] =78;
}
else {
keyboard_data[2] =74;
}
}
else
{
}
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)
{
memset(keyboard_data,0,KEYBOARD_DATA_SIZE);
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;
}
}
}
}
项目心得
本次项目中,单片机的例程相对来说是比较好找到的,但是蓝牙模组的例程就比较难找了,直接搜开发板的名称,里面是没有蓝牙的例程,当从蓝牙例程里面查找时,又会遇到默认模组对不上的问题,需要重新选择模组。
官方的PSoC Creator 4.4的开发方式是我第一次见。通过绘制类似原理图的文件,实现模组的配置,然后再为模组选择对应的io口,用户可以说完全不用了解硬件本身的一些信息,由于我目前只是体验了本项目相关功能,对它没有更深入的了解,它是否可以仅凭这两个步骤就可以实现整个项目的初始化,我并不确认,但就本项目的功能来说,是可以的。
配置完相关文件后,点击编译,就会在工程中,创建模块的相关程序文件。对于我这种该软件的新手来说,生成的程序完全不知道如何使用。每个模块下都有大几个文件,里面函数也有别于常见的单片机类似模块的驱动函数,如何使用是一大难题。通过打开相关模组例程,简单了解使用的相关函数,可以初步将功能跑起来。
本次活动让我了解到了现在厂商对于改进开发环境方面的努力,这与过往用户寄存器级的开发有着巨大差别。虽然同样是单片机,但是这种新的开发方式,已经将应用软件和实际硬件平台进行了分离,同时用户如果在本开发环境下完成了多次项目,也会产生对于开发环境的依赖,提高用户粘性。