I want to make a small gift "Friend Tag" for children, if every child has a "Friend Tag", which could show which friend is nearby. Because it is close to Christmas, the shape is made into the shape of a Christmas ball.
This "Friend Tag" is implemented based on BLE, using a very old BLE chip TI's CC2541. It is very cheap.
The first difficulty encountered in the development is that BLE is divided into peripheral and central devices, and they can communicate with each other. My application does not need to establish a communication connection, but only central can send search requests, and only peripherals can send response broadcasts, so I need to switch this label between peripheral and central. I am based on the "MasterSlaveSwitch" provided by TI Modified on a routine basis.
Hardware:
I used 6*8 LEDs to make a snowflake-shaped LED array, and each LED light can be controlled individually.
5 IOs are used to connect LEDs of different colors to distinguish different TAGs
There is a button that triggers the search action
The workflow is as follows:
1. After power-on, according to the selection of the pull-up resistor, determine the attributes of the tag itself and the response message. By default, it works in peripheral mode;
2. After the button is pressed, switch to central mode and trigger the search action;
3. According to the search results, light up the LED corresponding to the searched "Friend Tag";
4. After the search is completed, switch back to the peripheral mode to answer the search requests sent by other "Friend Tags";
5. After working for 10 minutes, in order to save power, turn off all LED lights;
I will explain the process step by step;
1. After power-on, according to the selection of the pull-up resistor, determine the attributes of the tag itself and the response message. By default, it works in peripheral mode;I first initialize the IOs P2.0 P2.1 P2.2 P2.3 P2.4 as inputs, and pull them up through hardware resistors to determine who the owner of this Tag is.
First initialized P2.0 P2.1 P2.2 P2.3 P2.4 as input:
#define KEY_RACHEL P2_4
#define KEY_MATTHEW P2_3
#define KEY_DOUBLE P2_2
#define KEY_HANA P2_0
#define KEY_EMMA P2_1
void merry_christmas_initkey(void)
{
P2SEL &= ~(BV(0) | BV(1) | BV(2) | BV(3) | BV(4));//Set P2.0/P2.1/P2.2/P2.3/P2.4 as General IO
P2DIR &= ~(BV(0) | BV(1) | BV(2) | BV(3) | BV(4));//Set P2.0/P2.1/P2.2/P2.3/P2.4 as input mode
P2INP &= ~(BV(0) | BV(1) | BV(2) | BV(3) | BV(4));//Set P2.0/P2.1/P2.2/P2.3/P2.4 as pull low mode
P2INP |= BV(7);
}
According to the resistor selection, determine the friend_num of the current tag:
if(KEY_RACHEL)
{
friend_num |= BV(0);
}
else if(KEY_MATTHEW)
{
friend_num |= BV(1);
}
else if(KEY_DOUBLE)
{
friend_num |= BV(2);
}
else if(KEY_HANA)
{
friend_num |= BV(3);
}
else if(KEY_EMMA)
{
friend_num |= BV(4);
}
Re-initialize io as an output, light up the LED corresponding to the tag master.
void merry_christmas_initled(void)
{
P2INP = 0x00;
P2DIR |=BV(0) | BV(1) | BV(2) | BV(3) | BV(4);
P2SEL &= ~( BV(0) | BV(1) | BV(2) | BV(3) | BV(4));
P2INP &= ~(BV(0) | BV(1) | BV(2) | BV(3) | BV(4));
P0DIR |=BV(0) | BV(1) | BV(2) | BV(3) | BV(4)| BV(5);
P0SEL &= ~( BV(0) | BV(1) | BV(2) | BV(3) | BV(4)| BV(5));
P1DIR |=BV(0) | BV(1) | BV(2) | BV(3) | BV(4)| BV(5)| BV(6)| BV(7);
P1SEL &= ~( BV(0) | BV(1) | BV(2) | BV(3) | BV(4)| BV(5)| BV(6)| BV(7));
}
light up the LED corresponding to the tag Owner.
void merry_christmas_update_led(void)
{
LED_RACHEL = 0;
LED_MATTHEW = 0;
LED_DOUBLE = 0;
LED_HANA = 0;
LED_EMMA = 0;
if((friend_num>>4)&0x01)
{
LED_RACHEL =1;
}
if((friend_num>>3)&0x01)
{
LED_MATTHEW =1;
}
if((friend_num>>2)&0x01)
{
LED_DOUBLE =1;
}
if((friend_num>>1)&0x01)
{
LED_HANA =1;
}
if((friend_num>>0)&0x01)
{
LED_EMMA =1;
}
}
ROLE is initialized as peripheral, and the broadcast response message of this label is determined according to friend_num.
It can be seen that the response messages of these tags are defined as:
"MerryChristmasRachel"
"MerryChristmasMatthew"
"MerryChristmasDouble"
"MerryChristmasHana"
"MerryChristmasEmma"
void MasterSlaveSwitch_Init( uint8 task_id )
{
masterSlaveSwitch_TaskID = task_id;
scanRspData[1] = 0x09;//GAP_ADTYPE_LOCAL_NAME_COMPLETE
scanRspData[2]='M';
scanRspData[3]='e';
scanRspData[4]='r';
scanRspData[5]='r';
scanRspData[6]='y';
scanRspData[7]='C';
scanRspData[8]='h';
scanRspData[9]='r';
scanRspData[10]='i';
scanRspData[11]='s';
scanRspData[12]='t';
scanRspData[13]='m';
scanRspData[14]='a';
scanRspData[15]='s';
if((friend_num>>4)&0x01)
{
my_name_len = 20;
scanRspData[0] = my_name_len+1;
scanRspData[16] = 'R';
scanRspData[17] = 'a';
scanRspData[18] = 'c';
scanRspData[19] = 'h';
scanRspData[20] = 'e';
scanRspData[21] = 'l';
}
if((friend_num>>3)&0x01)
{
my_name_len = 21;
scanRspData[0] = my_name_len+1;
scanRspData[16] = 'M';
scanRspData[17] = 'a';
scanRspData[18] = 't';
scanRspData[19] = 't';
scanRspData[20] = 'h';
scanRspData[21] = 'e';
scanRspData[22] = 'w';
}
if((friend_num>>2)&0x01)
{
my_name_len = 20;
scanRspData[0] = my_name_len+1;
scanRspData[16] = 'D';
scanRspData[17] = 'o';
scanRspData[18] = 'u';
scanRspData[19] = 'b';
scanRspData[20] = 'l';
scanRspData[21] = 'e';
}
if((friend_num>>1)&0x01)
{
my_name_len = 18;
scanRspData[0] = my_name_len+1;
scanRspData[16] = 'H';
scanRspData[17] = 'a';
scanRspData[18] = 'n';
scanRspData[19] = 'a';
}
if((friend_num>>0)&0x01)
{
my_name_len = 18;
scanRspData[0] = my_name_len+1;
scanRspData[16] = 'E';
scanRspData[17] = 'm';
scanRspData[18] = 'm';
scanRspData[19] = 'a';
}
scanRspData[my_name_len+2]=0x02;
scanRspData[my_name_len+3]=0x0a;
scanRspData[my_name_len+4]=0;
// Setup the GAP Peripheral Role Profile
{
// For other hardware platforms, device starts advertising upon initialization
uint8 initial_advertising_enable = FALSE; // We set this to FALSE to bypass peripheral.c's auto-start
// By setting this to zero, the device will go into the waiting state after
// being discoverable for 30.72 second, and will not being advertising again
// until the enabler is set back to TRUE
uint16 gapRole_AdvertOffTime = 0;
uint8 enable_update_request = DEFAULT_ENABLE_UPDATE_REQUEST;
uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;
uint16 desired_slave_latency = DEFAULT_DESIRED_SLAVE_LATENCY;
uint16 desired_conn_timeout = DEFAULT_DESIRED_CONN_TIMEOUT;
// Set the GAP Role Parameters
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );
GAPRole_SetParameter( GAPROLE_ADVERT_OFF_TIME, sizeof( uint16 ), &gapRole_AdvertOffTime );
//GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( scanRspData ), scanRspData );
GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, my_name_len+5, scanRspData );
GAPRole_SetParameter( GAPROLE_ADVERT_DATA, sizeof( advertData ), advertData );
GAPRole_SetParameter( GAPROLE_PARAM_UPDATE_ENABLE, sizeof( uint8 ), &enable_update_request );
GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );
GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );
GAPRole_SetParameter( GAPROLE_SLAVE_LATENCY, sizeof( uint16 ), &desired_slave_latency );
GAPRole_SetParameter( GAPROLE_TIMEOUT_MULTIPLIER, sizeof( uint16 ), &desired_conn_timeout );
}
This is the "MerryChristmasDouble" tag found on PC's Bluetooth.
After the button is pressed, the snowflake-shaped LED lights up first, indicating that it is searching.
Then switch to ROLE_CENTRAL mode, which triggers the search action.
static void masterSlaveSwitch_HandleKeys( uint8 shift, uint8 keys )
{
(void)shift; // Intentionally unreferenced parameter
if ( keys & HAL_KEY_SW_6 )
{
asm("nop");
if(key_busy ==0)
{
key_busy = 1;
if((friend_num>>4)&0x01)
{
P2_4 =1;
}
if((friend_num>>3)&0x01)
{
P2_3 =1;
}
if((friend_num>>2)&0x01)
{
P2_2 =1;
}
if((friend_num>>1)&0x01)
{
P2_0 =1;
}
if(friend_num&0x01)
{
P2_1 =1;
}
sleep_cnt_clr_flg = 1;
for (int i=0; i<5; i++)
{
P0_0 = 0;
P0_1 = 0;
P0_2 = 0;
P0_3 = 0;
P0_4 = 0;
P0_5 = 0;
P1_7 = 1;
DelayMS(300);
P1_6 = 1;
DelayMS(300);
P1_6 = 1;
P1_4 = 1;
DelayMS(300);
P1_3 = 1;
DelayMS(300);
P1_2 = 1;
P1_1 = 1;
DelayMS(300);
P1_0 = 1;
DelayMS(300);
P1_0 = 0;
DelayMS(300);
P1_1 = 0;
P1_2 = 0;
DelayMS(300);
P1_3 = 0;
DelayMS(300);
P1_4 = 0;
P1_5 = 0;
DelayMS(300);
P1_6 = 0;
DelayMS(300);
P1_7 = 0;
DelayMS(300);
}
if (deviceRole == ROLE_PERIPHERAL)
{
deviceRole = ROLE_CENTRAL;
uint8 adv_enabled_status = FALSE;
// Disable advertising if active
if (gapPeripheralState == GAPROLE_ADVERTISING )
{
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &adv_enabled_status ); // To stop advertising
// Role will be switched on Peripheral callback
}
else if (gapPeripheralState == GAPROLE_CONNECTED)
{
GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &adv_enabled_status ); // To avoid auto-reenabling of advertising
GAPRole_TerminateConnection();
// Role will be switched on Peripheral callback
}
else
{
osal_set_event(masterSlaveSwitch_TaskID, MSS_CHANGE_ROLE_EVT);
}
}
}
}
}
Added a MSS_CHANGE_ROLE_EVT task in osal_set_event(masterSlaveSwitch_TaskID, MSS_CHANGE_ROLE_EVT);
the role of the task is to switch from Peripheral to CENTRAL mode, and I trigger the search action after the switch is completed.
{
VOID GAPCentralRole_StartDevice( (gapCentralRoleCB_t *) &simpleBLERoleCB );
if ( !simpleBLEScanning )
{
simpleBLEScanning = TRUE;
simpleBLEScanRes = 0;
GAPCentralRole_StartDiscovery( DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST );
}
else
{
GAPCentralRole_CancelDiscovery();
}
}
3. According to the search results, light up the LED corresponding to the searched "Friend Tag";When there is a tag nearby, it will respond to the discovery request, and we need to judge which child the tag of the response belongs to based on the response message.
The 5 "Friend Tag" are:
"MerryChristmasRachel"
"MerryChristmasMatthew"
"MerryChristmasDouble"
"MerryChristmasHana"
"MerryChristmasEmma"
We only need to judge the characters after “MerryChristmas".
/*********************************************************************
* @fn simpleBLEFindSvcUuid
*
* @brief Find a given UUID in an advertiser's service UUID list.
*
* @return TRUE if service UUID found
*/
static bool simpleBLEFindSvcUuid( uint16 uuid, uint8 *pData, uint8 dataLen )
{
uint8 adLen;
uint8 adType;
uint8 *pEnd;
pEnd = pData + dataLen - 1;
// While end of data not reached
while ( pData < pEnd )
{
// Get length of next AD item
adLen = *pData++;
if ( adLen > 0 )
{
adType = *pData;
if(adType == GAP_ADTYPE_LOCAL_NAME_COMPLETE)
{
adLen--;
uint8 point = 0;
char temp_1[32] = {0};
point = memchr(pData + 15, 0x02, 8);
memcpy(temp_1, pData + 15, adLen-14);
if(!strcmp(temp_1,"Rachel"))
{
P2_4 =1;
}
if(!strcmp(temp_1,"Matthew"))
{
P2_3 =1;
}
if(!strcmp(temp_1,"Double"))
{
P2_2 =1;
}
if(!strcmp(temp_1,"Hana"))
{
P2_0 =1;
}
if(!strcmp(temp_1,"Emma"))
{
P2_1 =1;
}
}
// If AD type is for 16-bit service UUID
else if ( adType == GAP_ADTYPE_16BIT_MORE || adType == GAP_ADTYPE_16BIT_COMPLETE )
{
pData++;
adLen--;
// For each UUID in list
while ( adLen >= 2 && pData < pEnd )
{
// Check for match
if ( pData[0] == LO_UINT16(uuid) && pData[1] == HI_UINT16(uuid) )
{
// Match found
return TRUE;
}
// Go to next
pData += 2;
adLen -= 2;
}
// Handle possible erroneous extra byte in UUID list
if ( adLen == 1 )
{
pData++;
}
}
else
{
// Go to next item
pData += adLen;
}
}
}
// Match not found
return FALSE;
}
When there is a "friend tag"nearby,the corresponding LED will be lit according to the answer search answer.
In the task processing flow of Central"simpleBLECentralEventCB", once the search is over in case "GAP_DEVICE_DISCOVERY_EVENT", the ROLE switching process is automatically triggered to switch to peripheral, so that it can be searched by other tags.
/*********************************************************************
* @fn simpleBLECentralEventCB
*
* @brief Central event callback function.
*
* @param pEvent - pointer to event structure
*
* @return none
*/
static void simpleBLECentralEventCB( gapCentralRoleEvent_t *pEvent )
{
switch ( pEvent->gap.opcode )
{
case GAP_DEVICE_INIT_DONE_EVENT:
{
gapCentralState = GAPROLE_CENTRAL_INIT_DONE;
}
break;
case GAP_DEVICE_INFO_EVENT:
{
// if filtering device discovery results based on service UUID
if ( DEFAULT_DEV_DISC_BY_SVC_UUID == TRUE )
{
if ( simpleBLEFindSvcUuid( WANTED_SERVICE_UUID,
pEvent->deviceInfo.pEvtData,
pEvent->deviceInfo.dataLen ) )
{
simpleBLEAddDeviceInfo( pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType );
}
}
}
break;
case GAP_DEVICE_DISCOVERY_EVENT:
{
// discovery complete
simpleBLEScanning = FALSE;
// if not filtering device discovery results based on service UUID
if ( DEFAULT_DEV_DISC_BY_SVC_UUID == FALSE )
{
// Copy results
simpleBLEScanRes = pEvent->discCmpl.numDevs;
osal_memcpy( simpleBLEDevList, pEvent->discCmpl.pDevList,
(sizeof( gapDevRec_t ) * pEvent->discCmpl.numDevs) );
}
if ( simpleBLEScanRes > 0 )
{
}
// initialize scan index to last device
simpleBLEScanIdx = simpleBLEScanRes;
deviceRole = ROLE_PERIPHERAL;
if (gapCentralState == GAPROLE_CENTRAL_INIT_DONE || gapCentralState == GAPROLE_CENTRAL_DISCONNECTED )
{
osal_set_event(masterSlaveSwitch_TaskID, MSS_CHANGE_ROLE_EVT);
}
else
{
// disconnect all
GAPCentralRole_TerminateLink( GAP_CONNHANDLE_ALL );
// Expect the Central disconnect callback to switch the role
}
}
break;
case GAP_LINK_ESTABLISHED_EVENT:
{
if ( pEvent->gap.hdr.status == SUCCESS )
{
simpleBLEProcedureInProgress = TRUE;
gapCentralState = GAPROLE_CENTRAL_CONNECTED;
connHandle = pEvent->linkCmpl.connectionHandle;
//ghostyu add
// If service discovery not performed initiate service discovery
if ( simpleBLECharHdl == 0 )
{
osal_start_timerEx( masterSlaveSwitch_TaskID, START_DISCOVERY_EVT, DEFAULT_SVC_DISCOVERY_DELAY );
}
}
else
{
gapCentralState = GAPROLE_CENTRAL_DISCONNECTED;
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
connHandle = INVALID_CONNHANDLE;
}
}
break;
case GAP_LINK_TERMINATED_EVENT:
{
gapCentralState = GAPROLE_CENTRAL_DISCONNECTED;
connHandle = INVALID_CONNHANDLE;
//simpleBLERssi = FALSE;
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
simpleBLECharHdl = 0;
simpleBLEProcedureInProgress = FALSE;
// Start role switching if applicable
if (deviceRole == ROLE_PERIPHERAL)
{
osal_set_event(masterSlaveSwitch_TaskID, MSS_CHANGE_ROLE_EVT);
}
else
{
// Do normal Central link terminated tasks. E.g restart scanning.
}
}
break;
case GAP_LINK_PARAM_UPDATE_EVENT:
{
}
break;
default:
break;
}
}
5. After working for 10 minutes, in order to save power, turn off all LED lights;
Because the CR2230 is used for power supply, the power is very precious, and the search response part in the background also needs power supply all the time, so I can only turn off the LED after 5~10 minutes, so as to prolong the use time as much as possible.in "OSAL_PwrMgr.C",I modified the following part:
void osal_pwrmgr_powerconserve( void )
{
uint32 next;
halIntState_t intState;
// Should we even look into power conservation
if ( pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON )
{
// Are all tasks in agreement to conserve
if ( pwrmgr_attribute.pwrmgr_task_state == 0 )
{
// Hold off interrupts.
HAL_ENTER_CRITICAL_SECTION( intState );
// Get next time-out
next = osal_next_timeout();
// Re-enable interrupts.
HAL_EXIT_CRITICAL_SECTION( intState );
// Put the processor into sleep mode
OSAL_SET_CPU_INTO_SLEEP( next);
sleep_cnt ++;
if(sleep_cnt_clr_flg)
{
sleep_cnt = 0;
sleep_cnt_clr_flg = 0;
}
if(sleep_cnt == 600)
{
}
if(sleep_cnt == 3000)
{
P2_4 =0;
P2_3 =0;
P2_2 =0;
P2_1 =0;
P2_0 =0;
//uint8 advertising_enable = FALSE;
//GAPRole_SetParameter( 0x305, sizeof( uint8 ), &advertising_enable );
sleep_cnt = 0;
}
}
}
}
Done!
Comments
Please log in or sign up to comment.