本文章介紹使用Raspberry Pi Pico W建構 Bluetooth Classic SPP(serial port profile)的Piconet。使用兩個 Pi Pico W開發板、android phone與Linux(debian),一個Pi Pico W當SPP server,一個Pi Pico W當SPP client。如下圖所示。
所有SPP client(piconet slave)透過SPP server(piconet master)互相溝通。
一、Pico W SPP server:
建立SPP server需要用到bluetooth stack 的portocol與profile如下圖所示:
1. initialize HCI, L2CAP, SPD and RFCOMM protocol:
2.register HCP, RFCOMM packet handler:
3. 建立SPP service 的SDP service record。
二、Pico W SPP client:
建立SPP client需要用到bluetooth stack 的portocol與profile如下圖所示:
如同SPP server一樣要先init l2cap, rfcomm和指定packet handler。
執行SDP query查詢0x1101 SDP database找到SPP service。
接著建立connection。
其他詳細動態解說請觀看下面成果影片。
三、成果影片:
四、程式碼:
picow_spp_server
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ctype.h"
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#include "pico/util/queue.h"
#include "cJSON.h"
#define MAX_CLIENT_DEVICE 7
#define SWITCH_PIN 15
enum {
DEVICE_RELEASED=0,
DEVICE_OPENED,
};
typedef struct _spp_client_t_ {
uint8_t client_addr[18]; //string format
uint8_t device_name[21];
uint16_t rfcomm_channel_id;
uint16_t mtu;
uint8_t device_state;
} spp_client_t;
typedef struct _message_t {
uint8_t device_name[21];
uint8_t content[1124];
uint16_t content_len;
uint16_t channel_id;
uint8_t type[5];
} message_t;
queue_t message_queue;
spp_client_t spp_client[MAX_CLIENT_DEVICE];
#define RFCOMM_SERVER_CHANNEL 1
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static uint8_t spp_service_buffer[1024];
static btstack_packet_callback_registration_t hci_event_callback_registration;
void set_spp_client_channel(bd_addr_t addr, uint16_t rfcomm_channel_id, uint16_t mtu) {
uint8_t index;
bool found_device=false;
for (index=0; index < MAX_CLIENT_DEVICE; index++) {
if (strcmp(spp_client[index].client_addr, bd_addr_to_str(addr)) == 0) {
spp_client[index].rfcomm_channel_id = rfcomm_channel_id;
spp_client[index].mtu = mtu;
spp_client[index].device_state = DEVICE_OPENED;
found_device=true;
break;
}
}
if(!found_device) {
for (index=0; index < MAX_CLIENT_DEVICE; index++) {
if (spp_client[index].device_state == DEVICE_RELEASED) {
strcpy(spp_client[index].client_addr, bd_addr_to_str(addr));
spp_client[index].rfcomm_channel_id = rfcomm_channel_id;
spp_client[index].mtu = mtu;
spp_client[index].device_state = DEVICE_OPENED;
break;
}
}
}
message_t message;
sprintf(message.content, "Client Connected");
message.content_len=strlen(message.content);
sprintf(message.type,"info");
message.channel_id=rfcomm_channel_id;
queue_try_add(&message_queue, &message);
rfcomm_request_can_send_now_event(rfcomm_channel_id);
}
void set_spp_client_close(uint16_t rfcomm_channel_id) {
for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
if (spp_client[i].rfcomm_channel_id == rfcomm_channel_id) {
printf("client close, addr:%s\n", spp_client[i].client_addr);
spp_client[i].rfcomm_channel_id = 0;
memset(spp_client[i].client_addr, 0, 18);
spp_client[i].device_state = DEVICE_RELEASED;
break;
}
}
}
uint16_t get_spp_client_channel(uint8_t *device_name) {
uint8_t index;
uint16_t rfcomm_channel_id=0;
for (index=0; index < MAX_CLIENT_DEVICE; index++) {
if (strcmp(spp_client[index].device_name, device_name) == 0) {
rfcomm_channel_id = spp_client[index].rfcomm_channel_id;
break;
}
}
return rfcomm_channel_id;
}
void set_spp_client_name(uint8_t *addr, uint8_t *device_name) {
for (int i=0; i < strlen(addr); i++) {
addr[i]= (uint8_t)toupper(addr[i]);
}
for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
if (strcmp(spp_client[i].client_addr, addr) == 0) {
strcpy(spp_client[i].device_name, device_name);
}
}
}
void server_action(uint8_t *type, uint8_t *data) {
if (strcmp(type, "cmd")==0) {
if (strcmp(data, "switch") == 0) {
gpio_put(SWITCH_PIN, !gpio_get(SWITCH_PIN));
}
}
}
void send_message(message_t message) {
if (message.channel_id!=0) {
if (queue_try_add(&message_queue, &message)) {
rfcomm_request_can_send_now_event(message.channel_id);
}
} else {
printf("%s channel error\n", message.device_name);
}
}
void spp_process_packet(uint8_t *packet, uint16_t size) {
message_t message;
memset(message.content, 0, 256);
cJSON* json_obj = cJSON_CreateObject();
json_obj = cJSON_Parse(packet);
uint8_t *name = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "name"));
uint8_t *type = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "type"));
uint8_t *data = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "data"));
if(strcmp(name, "S0") == 0) {
server_action(type, data);
cJSON_Delete(json_obj);
return;
}
if (strcmp(type, "cmd")==0) { //cmd
strcpy(message.device_name, name);
strcpy(message.type, type);
message.channel_id = get_spp_client_channel(name);
if (strcmp(data, "list") == 0) {
strcpy(message.type, "info");
for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
if (spp_client[i].device_state == DEVICE_OPENED) {
strcat(message.content,spp_client[i].device_name);
strcat(message.content,",");
}
}
message.content_len = strlen(message.content);
} else {
strcpy(message.content, data);
message.content_len = strlen(data);
}
send_message(message);
cJSON_Delete(json_obj);
return;
}
if (strcmp(type, "set") == 0) {
set_spp_client_name(data, name);
cJSON_Delete(json_obj);
return;
}
if (strcmp(type, "txt")==0 || strcmp(type, "rgb")==0) {
strcpy(message.device_name, name);
strcpy(message.content, data);
strcpy(message.type, type);
message.channel_id = get_spp_client_channel(name);
message.content_len = strlen(data);
send_message(message);
cJSON_Delete(json_obj);
return;
}
}
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
uint16_t rfcomm_channel_id;
bd_addr_t event_addr;
uint8_t rfcomm_channel_nr;
uint16_t mtu;
message_t message;
uint8_t mesg_buff[256];
switch (packet_type) {
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
}
if (btstack_event_state_get_state(packet) == HCI_STATE_OFF) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
}
break;
case HCI_EVENT_PIN_CODE_REQUEST:
// inform about pin code request
printf("Pin code request - using '0000'\n");
hci_event_pin_code_request_get_bd_addr(packet, event_addr);
gap_pin_code_response(event_addr, "0000");
break;
case HCI_EVENT_USER_CONFIRMATION_REQUEST:
// ssp: inform about user confirmation request
printf("SSP User Confirmation Request with numeric value '%06"PRIu32"'\n", little_endian_read_32(packet, 8));
printf("SSP User Confirmation Auto accept\n");
break;
case RFCOMM_EVENT_INCOMING_CONNECTION:
rfcomm_event_incoming_connection_get_bd_addr(packet, event_addr);
rfcomm_channel_nr = rfcomm_event_incoming_connection_get_server_channel(packet);
rfcomm_channel_id = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);
printf("RFCOMM channel %u requested for %s\n", rfcomm_channel_nr, bd_addr_to_str(event_addr));
rfcomm_accept_connection(rfcomm_channel_id);
break;
case RFCOMM_EVENT_CHANNEL_OPENED:
if (rfcomm_event_channel_opened_get_status(packet)) {
printf("RFCOMM channel open failed, status 0x%02x\n", rfcomm_event_channel_opened_get_status(packet));
} else {
rfcomm_event_channel_opened_get_bd_addr(packet, event_addr);
rfcomm_channel_id = rfcomm_event_channel_opened_get_rfcomm_cid(packet);
mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
set_spp_client_channel(event_addr, rfcomm_channel_id, mtu);
printf("RFCOMM channel open succeeded. New RFCOMM Channel ID %u, max frame size %u\n", rfcomm_channel_id, mtu);
}
break;
case RFCOMM_EVENT_CAN_SEND_NOW:
rfcomm_channel_id = rfcomm_event_can_send_now_get_rfcomm_cid(packet);
if (!queue_is_empty(&message_queue)) {
if (queue_try_peek(&message_queue, &message)) {
if (message.channel_id == rfcomm_channel_id) {
sprintf(mesg_buff, "{\"type\":\"%s\", \"data\":\"%s\"}", message.type, message.content);
rfcomm_send(rfcomm_channel_id, (uint8_t*) mesg_buff, (uint16_t)strlen(mesg_buff));
queue_try_remove(&message_queue, &message);
}else {
busy_wait_ms(1);
rfcomm_request_can_send_now_event(rfcomm_channel_id);
}
} else {
printf("peek queue error\n");
}
}
break;
case RFCOMM_EVENT_CHANNEL_CLOSED:
printf("RFCOMM channel closed\n");
set_spp_client_close(rfcomm_event_channel_closed_get_rfcomm_cid(packet));
break;
default:
break;
}
break;
case RFCOMM_DATA_PACKET:
spp_process_packet(packet, size);
break;
default:
break;
}
}
int main()
{
stdio_init_all();
if (cyw43_arch_init())
{
printf("cyw43 init error\n");
return 0;
}
gpio_init(SWITCH_PIN);
gpio_set_dir(SWITCH_PIN, true);
gpio_put(SWITCH_PIN,false);
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
spp_client[i].device_state = DEVICE_RELEASED;
spp_client[i].rfcomm_channel_id=0;
}
queue_init(&message_queue, sizeof(message_t), 10);
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
l2cap_init();
rfcomm_init();
rfcomm_register_service(packet_handler, RFCOMM_SERVER_CHANNEL, 0xffff); // reserved channel, mtu limited by l2cap
sdp_init();
memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
spp_create_sdp_record(spp_service_buffer, 0x10001, RFCOMM_SERVER_CHANNEL, "PicoW SPP Service");
sdp_register_service(spp_service_buffer);
printf("SDP service record size: %u\n", de_get_len(spp_service_buffer));
gap_discoverable_control(1);
gap_ssp_set_io_capability(SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
gap_set_local_name("PicoW_SPP_S");
// turn on!
hci_power_control(HCI_POWER_ON);
while (1)
{
tight_loop_contents();
}
return 0;
}pico_spp_client
#include <stdio.h>
#include "pico/stdlib.h"
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "inttypes.h"
#include "cJSON.h"
#include "ws2812.h"
#define QUERY_DURATION 5
#define WS2812_PIN 15
#define TOUCH_PIN 16
// Major device class: 0x01--(Computer) or 0x02--(Phone) minor device class 0x0c(Laptop)
#define PEER_COD 0b0001100001100
#define SPP_SERVICE_UUID 0x1101
enum {
START_INQUIRY=0,
STOP_INQUIRY,
PEER_NAME_INQUIRY,
START_SDP_INQUERY,
SDP_CHANNEL_QUERY,
};
static uint8_t state;
static bd_addr_t peer_addr;
uint8_t peer_name[240];
uint8_t client_buff[2048];
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static uint8_t spp_server_channel=0;
static uint16_t rfcomm_cid;
static uint16_t rfcomm_mtu;
static uint8_t page_scan_repetition_mode;
static uint16_t clock_offset;
static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_context_callback_registration_t handle_sdp_client_query_request;
void set_client_device_name(bd_addr_t addr) {
if (rfcomm_cid == 0) {
printf("server channel 0\n");
}
sprintf(client_buff, "{\"name\":\"C0\",\"type\":\"set\",\"data\":\"%s\"}",
bd_addr_to_str(addr));
rfcomm_request_can_send_now_event(rfcomm_cid);
}
void process_data(uint8_t *packet, uint16_t size) {
cJSON* json_obj = cJSON_CreateObject();
json_obj = cJSON_Parse(packet);
uint8_t *type = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "type"));
uint8_t *data = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "data"));
if(strcmp(type, "rgb") == 0) {
ws2812_play(data, strlen(data));
cJSON_Delete(json_obj);
return;
}
}
static void handle_query_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
switch (hci_event_packet_get_type(packet)){
case SDP_EVENT_QUERY_RFCOMM_SERVICE:
spp_server_channel = sdp_event_query_rfcomm_service_get_rfcomm_channel(packet);
break;
case SDP_EVENT_QUERY_COMPLETE:
if (sdp_event_query_complete_get_status(packet)){
state = START_SDP_INQUERY;
sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID);
break;
}
if (spp_server_channel == 0){
state = START_INQUIRY;
gap_inquiry_start(QUERY_DURATION);
break;
}
rfcomm_create_channel(packet_handler, peer_addr, spp_server_channel, NULL);
break;
default:
break;
}
}
static void handle_start_sdp_client_query(void * context){
UNUSED(context);
if (state != START_SDP_INQUERY) return;
state = SDP_CHANNEL_QUERY;
sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID);
}
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
bd_addr_t event_addr;
uint8_t rfcomm_channel_nr;
uint32_t class_of_device;
uint8_t name_len;
switch (packet_type) {
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return;
state = START_INQUIRY;
gap_inquiry_start(QUERY_DURATION);
break;
case GAP_EVENT_INQUIRY_RESULT:
if (state != START_INQUIRY) break;
class_of_device = gap_event_inquiry_result_get_class_of_device(packet);
gap_event_inquiry_result_get_bd_addr(packet, event_addr);
memcpy(peer_addr, event_addr, 6);
page_scan_repetition_mode = gap_event_inquiry_result_get_page_scan_repetition_mode(packet);
clock_offset = gap_event_inquiry_result_get_clock_offset(packet);
if (gap_event_inquiry_result_get_name_available(packet)){
int name_len = gap_event_inquiry_result_get_name_len(packet);
memcpy(peer_name, gap_event_inquiry_result_get_name(packet), name_len);
peer_name[name_len] = 0;
if (strcmp(peer_name, "PicoW_SPP_S")==0) {
state = STOP_INQUIRY;
}
} else {
state = PEER_NAME_INQUIRY;
}
break;
case GAP_EVENT_INQUIRY_COMPLETE:
switch (state){
case PEER_NAME_INQUIRY:
gap_remote_name_request(peer_addr ,page_scan_repetition_mode, clock_offset | 0x8000);
gap_inquiry_start(QUERY_DURATION);
break;
case START_INQUIRY:
gap_inquiry_start(QUERY_DURATION);
break;
case STOP_INQUIRY:
gap_inquiry_stop();
break;
default:
break;
}
break;
case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:
strcpy(peer_name, &packet[9]);
if (strcmp(peer_name, "PicoW_SPP_S")==0) {
state = STOP_INQUIRY;
printf("Start to connect and query for SPP service\n");
sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID);
} else {
state = START_INQUIRY;
gap_inquiry_start(QUERY_DURATION);
}
break;
case HCI_EVENT_PIN_CODE_REQUEST:
// inform about pin code request
printf("Pin code request - using '0000'\n");
hci_event_pin_code_request_get_bd_addr(packet, event_addr);
gap_pin_code_response(event_addr, "0000");
break;
case HCI_EVENT_USER_CONFIRMATION_REQUEST:
// inform about user confirmation request
printf("SSP User Confirmation Request with numeric value '%06"PRIu32"'\n", little_endian_read_32(packet, 8));
printf("SSP User Confirmation Auto accept\n");
break;
/*
case RFCOMM_EVENT_INCOMING_CONNECTION:
rfcomm_event_incoming_connection_get_bd_addr(packet, event_addr);
rfcomm_channel_nr = rfcomm_event_incoming_connection_get_server_channel(packet);
rfcomm_cid = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);
printf("RFCOMM channel 0x%02x requested for %s\n", rfcomm_channel_nr, bd_addr_to_str(event_addr));
rfcomm_accept_connection(rfcomm_cid);
break;
*/
case RFCOMM_EVENT_CHANNEL_OPENED:
if (rfcomm_event_channel_opened_get_status(packet)) {
printf("RFCOMM channel open failed, status 0x%02x\n", rfcomm_event_channel_opened_get_status(packet));
state = START_INQUIRY;
gap_inquiry_start(QUERY_DURATION);
} else {
rfcomm_cid = rfcomm_event_channel_opened_get_rfcomm_cid(packet);
rfcomm_mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
bd_addr_t add_buff;
gap_local_bd_addr(add_buff);
set_client_device_name(add_buff);
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
// disable page/inquiry scan to get max performance
//gap_discoverable_control(0);
//gap_connectable_control(0);
}
break;
case RFCOMM_EVENT_CAN_SEND_NOW:
rfcomm_send(rfcomm_cid, client_buff, strlen(client_buff));
break;
case RFCOMM_EVENT_CHANNEL_CLOSED:
printf("RFCOMM channel closed\n");
rfcomm_cid = 0;
spp_server_channel=0;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
// re-enable page/inquiry scan again
state = START_INQUIRY;
gap_inquiry_start(QUERY_DURATION);
//gap_discoverable_control(0);
//gap_connectable_control(0);
break;
default:
break;
}
break;
case RFCOMM_DATA_PACKET:
// data in packet
process_data(packet, size);
break;
default:
break;
}
}
void switch_callback(uint gpio, uint32_t event_mask) {
if (gpio == TOUCH_PIN && event_mask == GPIO_IRQ_EDGE_RISE) {
sprintf(client_buff, "{\"name\":\"S0\",\"type\":\"cmd\",\"data\":\"switch\"}");
rfcomm_request_can_send_now_event(spp_server_channel);
}
}
int main()
{
stdio_init_all();
if (cyw43_arch_init()) {
puts("cyw43 init error");
return 0;
}
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
gpio_set_irq_enabled_with_callback(TOUCH_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &switch_callback);
ws2812_init(pio0, 0, WS2812_PIN);
l2cap_init();
rfcomm_init();
handle_sdp_client_query_request.callback = &handle_start_sdp_client_query;
sdp_client_register_query_callback(&handle_sdp_client_query_request);
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
//gap_ssp_set_io_capability(SSP_IO_CAPABILITY_DISPLAY_YES_NO);
gap_ssp_set_io_capability(SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
gap_set_local_name("PiocW_Client_0");
gap_discoverable_control(0);
gap_connectable_control(0);
// turn on!
hci_power_control(HCI_POWER_ON);
while(1) {
tight_loop_contents();
}
return 0;
}

Please Can you share a working project (zip format) file including .vscode, btstack_config, cmakelists.txt, server.c, client.c
回覆刪除am getting lot issue.
and am new into the picow.