Funpack4-1 - 基于PSoC 4100S Plus 实现蓝牙翻页器
该项目使用了PSoC 4100S Plus ,使用c语言进行开发,ide为PSOC creator,实现了蓝牙翻页器的设计,它的主要功能为:板子通过蓝牙连接电脑,利用板载的触摸按键,控制电脑上下翻页。
标签
Funpack活动
PSoC 4100S Plus
lshy
更新2025-04-08
37

项目描述

本项目基于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™ 解决方案,超可靠的低功耗电容式感应解决方案,可在嘈杂环境和液体中 "正常工作"。这是它有别于其他单片机的地方,开发板上也专门给他设计了两块触摸板。本工程也将使用该触摸板实现控制功能。

image.png


蓝牙为CYBLE-022001-00,它可以独立的编程调试。和CY8C4147AZI-S475单片机一样,它也支持英飞凌的CAPSENSE™技术。但是在本开发板中,并没有将它和板子上电触摸模块连接在一起,所以无法测试该功能。蓝牙模块内核为cortex-m0,系统时钟最大为48MHz,flash 128KB,sram 16KB。由下图可知,蓝牙模组有21个引脚,可用的GPIO有16个,可以在蓝牙功能外,做一些基本的控制功能。

image.png


这款开发板的独特之处在于并非一个单一的主控,单片机及蓝牙都可以独立开发,并通过板载的调试器进行烧录与调试。通过板子上的sw4波动开关,去控制调试器的调试目标,当开关如同下图向上波动,调试的对象为蓝牙模组。

单片机和蓝牙模组之间可以通过两种通信接口进行连接:iic和串口。由于串口作为主控间的通信更为方便,所以本项目中采用了串口,作为单片机向蓝牙模组发送指令的通信方式。

但是需要注意的是,原理图中标注了R17和R18是没有的,也就是说单片机和蓝牙模组的串口是断开的,所以需要通过焊接将它们连通。可以采用低阻值的电阻,也可以直接用焊锡将它们连接。


开发环境

本项目对单片机和蓝牙都进行了开发,它们使用的IDE皆是PSoC Creator 4.4。

image.png

例子可以从File->Code Example中获取。

image.png


蓝牙模组的例子可以在PRoC BLE中获取。

image.png

单片机的例子可以在PSoC 4100S Plus中获取。

image.png

本工程使用的蓝牙工程为BLE_HID_Keyboard

image.png

单片机使用的工程为

image.png

代码及工程说明

单片机

打开例程后,打开TopDesign.cysch文件,在右边找到Component Catalog->Cypress->communications->UART,将串口模块拖入文件中。

image.png

image.png

image.png

打开Design Wide Resources文件将串口的rx和tx进行如下配置

image.png

image.png

上述UART模块可以双击打开,但是并不需要对里面的参数进行修改。接下来可以直接进行编译。下方从左到右依次是编译、烧录、调试。

image.png

编译完成后,将开发板接上电脑,并将SW4开关向三个独立触摸按键的方向拨动。点击烧录按键,就可以将代码烧录到单片机。如果出现下方的情况,就说明sw4方向不对,反向拨动。

image.png

如果是向下放这样显示就说明单片机已经接上电脑。

image.png

接下来说一下代码上的改动。原始工程已经实现了触摸按键的检测,需要新增的功能就是在按键按下时,用模组向蓝牙模组发送信息。

下方代码实现了当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;
    }
}


下方是上述代码流程图,说明按键只有在刚按下时,会发送指令,如果一直长按是不会连续发送指令,只有松开再按下才会重新发送。得益于触摸按键的准确行,函数内部并没有对按键信号做滤波处理,实际的效果依然很好。

image.png

串口的初始化函数也需要在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.image.png

选择列表第一个。

image.png

选择好后,就可以正常编译烧录。同上面一样打开TopDesign.cysch文件。选择Digtital Output Pin 、LED、Resistor。

image.png

image.png

image.png

双击gpio模组符号,将内部参数像如下更改,然后将3个原件连接在一起。

image.png

image.png

打开Design Wide Resources文件,将GPIO配置为P1_6,这是板子上led接到的引脚。这个led将会作为蓝牙是否连接上电脑的指示。

image.png

image.png

由于例程会发送电量信息给电脑,电脑可能会提示低电量告警,可以通过将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口,用户可以说完全不用了解硬件本身的一些信息,由于我目前只是体验了本项目相关功能,对它没有更深入的了解,它是否可以仅凭这两个步骤就可以实现整个项目的初始化,我并不确认,但就本项目的功能来说,是可以的。

配置完相关文件后,点击编译,就会在工程中,创建模块的相关程序文件。对于我这种该软件的新手来说,生成的程序完全不知道如何使用。每个模块下都有大几个文件,里面函数也有别于常见的单片机类似模块的驱动函数,如何使用是一大难题。通过打开相关模组例程,简单了解使用的相关函数,可以初步将功能跑起来。

本次活动让我了解到了现在厂商对于改进开发环境方面的努力,这与过往用户寄存器级的开发有着巨大差别。虽然同样是单片机,但是这种新的开发方式,已经将应用软件和实际硬件平台进行了分离,同时用户如果在本开发环境下完成了多次项目,也会产生对于开发环境的依赖,提高用户粘性。




附件下载
CE220891_CapSense_with_Breathing_LED02.cydsn.zip
单片机固件
BLE_HID_Keyboard04.cydsn.zip
蓝牙固件
团队介绍
团队成员
lshy
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号